mirror of
https://github.com/netbirdio/ios-client.git
synced 2026-09-23 09:28:33 -07:00
* fix ui state for airplaine mode * fix slide bar * Keep VPN tunnel alive during network unavailability - Add isNetworkUnavailable flag to NetBirdAdapter to track network state - Modify ConnectionListener to stay in 'connecting' state when network is unavailable instead of transitioning to 'disconnected' - Update PacketTunnelProvider to set network unavailable flag and trigger automatic reconnect when network returns - Fix CustomLottieView to show grey 'Disconnected' state immediately when network is lost, without closing the VPN tunnel - Ensure UI shows correct state after app foreground/background cycle This allows the VPN tunnel to survive temporary network outages (e.g. airplane mode) and automatically reconnect when network returns. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix UI state after app foreground/background cycle Show correct connected/disconnected state immediately when app returns from background, without replaying animations. Use extensionStatus (iOS VPN state) as the source of truth for UI state. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * netbird credential * Update MainView.swift * Separate button by state * Add gogoleServiceInfo plist reference * Remove dead code in animation state machine and fix copy-to-clipboard UX - Remove unreachable shouldForceReset block in CustomLottieView (already handled by earlier return) - Guard against copying empty fqdn/ip strings when disconnected - Use consistent .smooth animation for both fqdn and ip copy feedback - Remove unreachable shouldForceReset block in CustomLottieView (already handled by earlier return) - Guard against copying empty fqdn/ip strings when disconnected - Use consistent .smooth animation for both fqdn and ip copy feedback * Tab bar * Update peer view * Add offline state handling and network warning banner - Show "Offline" instead of "Connected" when VPN tunnel is active but device has no internet - Add NetworkWarningBanner with "Network Issues" warning when connected without internet - Remove InternetStatusView (online/offline indicator) from connection screen - Add FirstLaunchView onboarding screen - Remove unused components (CustomBackButton, Extensions, JustifiedText, SolidButton, TransparentGradientButton) - Update AboutView, AdvancedView, ServerView, PeerTabView, RouteTabView, iOSNetworksView styling - Add EmptyTabPlaceholder component * Add AppButton component with liquid glass support for iOS 26+ * Update FirstLaunchView.swift * old version * Update project.pbxproj * Code refactoring * Update project.pbxproj * Update MainViewModel.swift --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
194 lines
7.0 KiB
Swift
194 lines
7.0 KiB
Swift
//
|
|
// ServerView.swift
|
|
// NetBirdiOS
|
|
//
|
|
// Created by Pascal Fischer on 12.10.23.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct ServerView: View {
|
|
@EnvironmentObject var viewModel: ViewModel
|
|
@StateObject private var serverViewModel = ServerViewModel(configurationFilePath: Preferences.configFile() ?? "", deviceName: Device.getName())
|
|
|
|
private let defaultManagementServerUrl = "https://api.netbird.io"
|
|
|
|
@State private var showSetupKeyField = false
|
|
@State private var managementServerUrl = ""
|
|
@State private var setupKey = ""
|
|
@State private var isButtonDisabled = false
|
|
@State private var isAddDeviceToggleDisabled = false
|
|
|
|
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
|
|
|
|
var body: some View {
|
|
Form {
|
|
Section(header: Text("Server")) {
|
|
ZStack(alignment: .leading) {
|
|
TextField("https://example-api.domain.com:443", text: $managementServerUrl)
|
|
.foregroundColor(Color("TextPrimary"))
|
|
.disableAutocorrection(true)
|
|
.autocapitalization(.none)
|
|
.keyboardType(.URL)
|
|
}
|
|
.onChange(of: managementServerUrl) { _ in
|
|
serverViewModel.clearErrorsFor(field: .url)
|
|
}
|
|
|
|
if let error = serverViewModel.viewErrors.urlError, !error.isEmpty {
|
|
Text(error).foregroundColor(.red).font(.footnote)
|
|
}
|
|
if let error = serverViewModel.viewErrors.generalError, !error.isEmpty {
|
|
Text(error).foregroundColor(.red).font(.footnote)
|
|
}
|
|
}
|
|
|
|
Section {
|
|
DisclosureGroup("Add this device with a setup key", isExpanded: $showSetupKeyField) {
|
|
TextField("0EF79C2F-DEE1-419B-BFC8-1BF529332998", text: $setupKey)
|
|
.disableAutocorrection(true)
|
|
.autocapitalization(.allCharacters)
|
|
.disabled(isAddDeviceToggleDisabled)
|
|
.onChange(of: setupKey) { _ in
|
|
serverViewModel.clearErrorsFor(field: .setupKey)
|
|
}
|
|
|
|
if let error = serverViewModel.viewErrors.setupKeyError, !error.isEmpty {
|
|
Text(error).foregroundColor(.red).font(.footnote)
|
|
}
|
|
|
|
if let error = serverViewModel.viewErrors.ssoNotSupportedError, !error.isEmpty {
|
|
Text(error).foregroundColor(.red).font(.footnote)
|
|
}
|
|
|
|
Text("Using setup keys for user devices is not recommended. SSO with MFA provides stronger security, proper user-device association, and periodic re-authentication.")
|
|
.font(.footnote)
|
|
.foregroundColor(.accentColor)
|
|
.padding(.vertical, 4)
|
|
}
|
|
}
|
|
.onChange(of: showSetupKeyField) { expanded in
|
|
if !expanded {
|
|
setupKey = ""
|
|
serverViewModel.clearErrorsFor(field: .setupKey)
|
|
}
|
|
}
|
|
|
|
Section {
|
|
if isButtonDisabled {
|
|
HStack {
|
|
Spacer()
|
|
ProgressView()
|
|
.padding(.trailing, 8)
|
|
Text("Validating...")
|
|
Spacer()
|
|
}
|
|
} else {
|
|
Button {
|
|
dismissKeyboard()
|
|
performChange()
|
|
} label: {
|
|
HStack {
|
|
Spacer()
|
|
Text("Change")
|
|
Spacer()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Section {
|
|
Button {
|
|
dismissKeyboard()
|
|
managementServerUrl = defaultManagementServerUrl
|
|
performUseNetBird()
|
|
} label: {
|
|
HStack {
|
|
Spacer()
|
|
Image("icon-netbird-button")
|
|
.resizable()
|
|
.scaledToFit()
|
|
.frame(width: 22, height: 22)
|
|
Text("Use NetBird server")
|
|
Spacer()
|
|
}
|
|
}
|
|
.disabled(isButtonDisabled)
|
|
}
|
|
}
|
|
.navigationTitle("Change Server")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.onChange(of: serverViewModel.viewErrors.ssoNotSupportedError) { error in
|
|
if error != nil {
|
|
showSetupKeyField = true
|
|
}
|
|
}
|
|
.onChange(of: serverViewModel.isOperationSuccessful) { success in
|
|
if success {
|
|
presentationMode.wrappedValue.dismiss()
|
|
viewModel.showServerChangedInfo = true
|
|
}
|
|
}
|
|
.onChange(of: serverViewModel.isUiEnabled) { isEnabled in
|
|
if isEnabled {
|
|
isButtonDisabled = false
|
|
isAddDeviceToggleDisabled = false
|
|
} else {
|
|
isButtonDisabled = true
|
|
isAddDeviceToggleDisabled = true
|
|
}
|
|
}
|
|
}
|
|
|
|
private func dismissKeyboard() {
|
|
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
|
}
|
|
|
|
private func performChange() {
|
|
if managementServerUrl.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
&& setupKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
|
return
|
|
}
|
|
|
|
var serverUrl = managementServerUrl.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
|
if serverUrl.isEmpty {
|
|
serverUrl = defaultManagementServerUrl
|
|
}
|
|
managementServerUrl = serverUrl
|
|
|
|
let key = setupKey.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
serverViewModel.clearErrorsFor(field: .all)
|
|
|
|
Task {
|
|
await Task.yield()
|
|
if !serverUrl.isEmpty && !key.isEmpty {
|
|
await serverViewModel.loginWithSetupKey(managementServerUrl: serverUrl, setupKey: key)
|
|
} else if !serverUrl.isEmpty {
|
|
await serverViewModel.changeManagementServerAddress(managementServerUrl: serverUrl)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func performUseNetBird() {
|
|
let serverUrl = defaultManagementServerUrl
|
|
let key = setupKey.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
serverViewModel.clearErrorsFor(field: .all)
|
|
|
|
Task {
|
|
await Task.yield()
|
|
if key.isEmpty {
|
|
await serverViewModel.changeManagementServerAddress(managementServerUrl: serverUrl)
|
|
} else {
|
|
await serverViewModel.loginWithSetupKey(managementServerUrl: serverUrl, setupKey: key)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
ServerView()
|
|
.environmentObject(ViewModel())
|
|
}
|