diff --git a/NetBird.xcodeproj/project.pbxproj b/NetBird.xcodeproj/project.pbxproj index 3688e16..a549230 100644 --- a/NetBird.xcodeproj/project.pbxproj +++ b/NetBird.xcodeproj/project.pbxproj @@ -76,7 +76,9 @@ 50CD81B02AD5B94D00CF830B /* PeerCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CD81AF2AD5B94D00CF830B /* PeerCard.swift */; }; 50CD81B12AD5B94D00CF830B /* PeerCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CD81AF2AD5B94D00CF830B /* PeerCard.swift */; }; 50CD84362AD82F9400CF830B /* ServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CD84352AD82F9400CF830B /* ServerView.swift */; }; - 50D402902BD8188C00D4AC5B /* NetBirdSDK.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50D4028F2BD8188C00D4AC5B /* NetBirdSDK.xcframework */; }; + 50D402922BD913D100D4AC5B /* DirectoryPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D402912BD913D100D4AC5B /* DirectoryPicker.swift */; }; + 50D402942BD9143900D4AC5B /* NetBirdSDK.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50D402932BD9143900D4AC5B /* NetBirdSDK.xcframework */; }; + 50D402952BD9143900D4AC5B /* NetBirdSDK.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50D402932BD9143900D4AC5B /* NetBirdSDK.xcframework */; }; 50E608132A7958B100BAF09B /* MainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E608122A7958B100BAF09B /* MainViewModel.swift */; }; 50E608202A7979D600BAF09B /* SideDrawer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E6081F2A7979D600BAF09B /* SideDrawer.swift */; }; 50E608242A79966600BAF09B /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E608232A79966600BAF09B /* AboutView.swift */; }; @@ -153,7 +155,8 @@ 50CD81A62AD5504B00CF830B /* StatusDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusDetails.swift; sourceTree = ""; }; 50CD81AF2AD5B94D00CF830B /* PeerCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerCard.swift; sourceTree = ""; }; 50CD84352AD82F9400CF830B /* ServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerView.swift; sourceTree = ""; }; - 50D4028F2BD8188C00D4AC5B /* NetBirdSDK.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = NetBirdSDK.xcframework; path = NetBird/NetBirdSDK.xcframework; sourceTree = ""; }; + 50D402912BD913D100D4AC5B /* DirectoryPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectoryPicker.swift; sourceTree = ""; }; + 50D402932BD9143900D4AC5B /* NetBirdSDK.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = NetBirdSDK.xcframework; path = NetBird/NetBirdSDK.xcframework; sourceTree = ""; }; 50E608022A7950CB00BAF09B /* Device.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Device.swift; sourceTree = ""; }; 50E608122A7958B100BAF09B /* MainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewModel.swift; sourceTree = ""; }; 50E6081F2A7979D600BAF09B /* SideDrawer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideDrawer.swift; sourceTree = ""; }; @@ -167,6 +170,7 @@ buildActionMask = 2147483647; files = ( 508BD8492AF140D50055E415 /* FirebaseAnalyticsSwift in Frameworks */, + 50D402952BD9143900D4AC5B /* NetBirdSDK.xcframework in Frameworks */, 50245A542A80431B0034792B /* NetworkExtension.framework in Frameworks */, 50003BBE2AFBCA7900E5EB6B /* FirebasePerformance in Frameworks */, 508BD84B2AF140D50055E415 /* FirebaseCrashlytics in Frameworks */, @@ -186,7 +190,7 @@ 50051DE02AE69A8100AFBDC4 /* FirebaseCrashlytics in Frameworks */, 50003BBC2AFBCA6B00E5EB6B /* FirebasePerformance in Frameworks */, 5051190F2AE03F68003027D3 /* FirebaseAnalytics in Frameworks */, - 50D402902BD8188C00D4AC5B /* NetBirdSDK.xcframework in Frameworks */, + 50D402942BD9143900D4AC5B /* NetBirdSDK.xcframework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -246,7 +250,7 @@ 50A8910E2A792A15007C48FC = { isa = PBXGroup; children = ( - 50D4028F2BD8188C00D4AC5B /* NetBirdSDK.xcframework */, + 50D402932BD9143900D4AC5B /* NetBirdSDK.xcframework */, 506331F72AF1676B00BC8F0E /* GoogleService-Info.plist */, 50245A0A2A7AA9390034792B /* NetBird-Bridging-Header.h */, 50A891192A792A15007C48FC /* NetBird */, @@ -325,6 +329,7 @@ 50E608092A79557A00BAF09B /* Components */ = { isa = PBXGroup; children = ( + 50D402912BD913D100D4AC5B /* DirectoryPicker.swift */, 50E6081F2A7979D600BAF09B /* SideDrawer.swift */, 502455BC2A79B0480034792B /* CustomBackButton.swift */, 502455BE2A79B4500034792B /* SolidButton.swift */, @@ -568,6 +573,7 @@ 50216D8E2ACB1905009574C9 /* NetworkChangeListener.swift in Sources */, 50CD81A72AD5504B00CF830B /* StatusDetails.swift in Sources */, 505118CE2AD96ECA003027D3 /* x25519.c in Sources */, + 50D402922BD913D100D4AC5B /* DirectoryPicker.swift in Sources */, 508BD8452AF04A990055E415 /* SafariView.swift in Sources */, 505118D02AD96ECA003027D3 /* key.c in Sources */, 50E608262A79968500BAF09B /* AdvancedView.swift in Sources */, diff --git a/NetBird/Source/App/ViewModels/MainViewModel.swift b/NetBird/Source/App/ViewModels/MainViewModel.swift index 715dfaf..25cb1d2 100644 --- a/NetBird/Source/App/ViewModels/MainViewModel.swift +++ b/NetBird/Source/App/ViewModels/MainViewModel.swift @@ -18,6 +18,7 @@ class ViewModel: ObservableObject { @Published var showInvalidServerAlert = false @Published var showInvalidSetupKeyHint = false @Published var showInvalidSetupKeyAlert = false + @Published var showLogLevelChangedAlert = false @Published var showInvalidPresharedKeyAlert = false @Published var showServerChangedInfo = false @Published var showPreSharedKeyChangedInfo = false @@ -39,6 +40,17 @@ class ViewModel: ObservableObject { @Published var extensionStateText = "Disconnected" @Published var connectPressed = false @Published var disconnectPressed = false + @Published var traceLogsEnabled: Bool { + didSet { + self.showLogLevelChangedAlert = true + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + self.showLogLevelChangedAlert = false + } + let logLevel = traceLogsEnabled ? "TRACE" : "INFO" + UserDefaults.standard.set(logLevel, forKey: "logLevel") + UserDefaults.standard.synchronize() + } + } var preferences = Preferences.newPreferences() var buttonLock = false let defaults = UserDefaults.standard @@ -48,6 +60,8 @@ class ViewModel: ObservableObject { private var cancellables = Set() init() { + let logLevel = UserDefaults.standard.string(forKey: "logLevel") ?? "INFO" + self.traceLogsEnabled = logLevel == "TRACE" self.rosenpassEnabled = self.getRosenpassEnabled() self.rosenpassPermissive = self.getRosenpassPermissive() diff --git a/NetBird/Source/App/Views/AdvancedView.swift b/NetBird/Source/App/Views/AdvancedView.swift index fa67253..594522a 100644 --- a/NetBird/Source/App/Views/AdvancedView.swift +++ b/NetBird/Source/App/Views/AdvancedView.swift @@ -11,6 +11,8 @@ struct AdvancedView: View { @EnvironmentObject var viewModel: ViewModel @Environment(\.presentationMode) var presentationMode: Binding + @State private var directoryPickerPresented = false + var body: some View { ZStack { Color("BgPage") @@ -44,7 +46,19 @@ struct AdvancedView: View { } } } + .padding(.top, 10) + Divider() + .padding([.top, .bottom]) + Toggle(isOn: $viewModel.traceLogsEnabled, label: { + Text("Enable Trace logs.") + .multilineTextAlignment(.leading) + .font(.system(size: 18, weight: .regular)) + .foregroundColor(Color("TextSecondary")) + .padding(.top, 3) .padding(.top, 5) + SolidButton(text: "Share logs") { + directoryPickerPresented = true + } Divider() .padding([.top, .bottom]) Toggle(isOn: $viewModel.rosenpassEnabled, label: { @@ -54,6 +68,7 @@ struct AdvancedView: View { .foregroundColor(Color("TextSecondary")) .padding(.top, 3) }) + .padding(.top, 10) .onChange(of: viewModel.rosenpassEnabled) { value in if !value { viewModel.rosenpassPermissive = false @@ -76,6 +91,20 @@ struct AdvancedView: View { Spacer() } .padding([.leading, .trailing], UIScreen.main.bounds.width * 0.10) + if viewModel.showLogLevelChangedAlert { + Color.black.opacity(0.4) + .edgesIgnoringSafeArea(.all) + .onTapGesture { + viewModel.buttonLock = true + viewModel.showLogLevelChangedAlert = false + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + viewModel.buttonLock = false + } + } + + LogLevelAlert(viewModel: viewModel, isPresented: $viewModel.showLogLevelChangedAlert) + .frame(maxWidth: UIScreen.main.bounds.width * 0.9) + } } .onAppear(perform: { viewModel.loadPreSharedKey() @@ -89,6 +118,38 @@ struct AdvancedView: View { .onTapGesture { UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } + .sheet(isPresented: $directoryPickerPresented) { + DirectoryPicker { url in + print("Directory selected: \(url)") + saveLogFile(at: url) + } + } + } + + func saveLogFile(at url: URL?) { + guard let url = url else { return } + + let fileManager = FileManager.default + guard let groupURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: "group.io.netbird.app") else { + print("Failed to retrieve the group URL") + return + } + + let logURL = groupURL.appendingPathComponent("logfile.log") + + do { + let logData = try String(contentsOf: logURL, encoding: .utf8) + let fileURL = url.appendingPathComponent("netbird.log") + do { + try logData.write(to: fileURL, atomically: true, encoding: .utf8) + print("Log file saved successfully.") + } catch { + print("Failed to save log file: \(error)") + } + } catch { + print("Failed to read log data: \(error)") + return + } } func checkForValidPresharedKey(text: String) { @@ -115,6 +176,32 @@ struct AdvancedView: View { } } +struct LogLevelAlert: View { + @StateObject var viewModel: ViewModel + @Binding var isPresented: Bool + + var body: some View { + VStack(spacing: 20) { + Image("exclamation-circle") + .padding(.top, 20) + Text("Changing Log Level") + .font(.title) + .foregroundColor(Color("TextAlert")) + Text("Changing log level will take effect after next connect.") + .foregroundColor(Color("TextAlert")) + .multilineTextAlignment(.center) + SolidButton(text: "Confirm") { + isPresented.toggle() + } + .padding(.top, 20) + } + .padding() + .background(Color("BgSideDrawer")) + .cornerRadius(15) + .shadow(radius: 10) + } +} + struct AdvancedView_Previews: PreviewProvider { static var previews: some View { AdvancedView() diff --git a/NetBird/Source/App/Views/Components/DirectoryPicker.swift b/NetBird/Source/App/Views/Components/DirectoryPicker.swift new file mode 100644 index 0000000..6213ad0 --- /dev/null +++ b/NetBird/Source/App/Views/Components/DirectoryPicker.swift @@ -0,0 +1,43 @@ +import SwiftUI +import UIKit + +struct DirectoryPicker: UIViewControllerRepresentable { + @Environment(\.presentationMode) var presentationMode + var onDirectoryPick: (URL) -> Void + + func makeUIViewController(context: Context) -> UIDocumentPickerViewController { + let picker = UIDocumentPickerViewController(forOpeningContentTypes: [.folder], asCopy: false) + picker.delegate = context.coordinator + picker.modalPresentationStyle = .formSheet + return picker + } + + func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) { + // No update action needed + } + + func makeCoordinator() -> Coordinator { + Coordinator(self) + } + + class Coordinator: NSObject, UIDocumentPickerDelegate { + var parent: DirectoryPicker + + init(_ picker: DirectoryPicker) { + self.parent = picker + } + + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + if let url = urls.first { + url.startAccessingSecurityScopedResource() + parent.onDirectoryPick(url) + url.stopAccessingSecurityScopedResource() + } + parent.presentationMode.wrappedValue.dismiss() + } + + func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { + parent.presentationMode.wrappedValue.dismiss() + } + } +} diff --git a/NetBird/Source/App/Views/MainView.swift b/NetBird/Source/App/Views/MainView.swift index 9007c51..5365730 100644 --- a/NetBird/Source/App/Views/MainView.swift +++ b/NetBird/Source/App/Views/MainView.swift @@ -7,6 +7,7 @@ import SwiftUI import Lottie +import NetworkExtension struct MainView: View { @EnvironmentObject var viewModel: ViewModel @@ -78,13 +79,15 @@ struct MainView: View { } Spacer() } -// Spacer() -// Button("print logs") { -// let fileManager = FileManager.default -// let groupURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: "group.io.netbird.app") -// let logURL = groupURL?.appendingPathComponent("logfile.log") -// printLogContents(from: logURL!) -// } + #if DEBUG + Spacer() + Button("print logs") { + let fileManager = FileManager.default + let groupURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: "group.io.netbird.app") + let logURL = groupURL?.appendingPathComponent("logfile.log") + printLogContents(from: logURL!) + } + #endif Spacer() Button(action: { if !viewModel.buttonLock { diff --git a/NetbirdKit/NetworkExtensionAdapter.swift b/NetbirdKit/NetworkExtensionAdapter.swift index 1c2c26e..69a81ba 100644 --- a/NetbirdKit/NetworkExtensionAdapter.swift +++ b/NetbirdKit/NetworkExtensionAdapter.swift @@ -118,8 +118,12 @@ public class NetworkExtensionAdapter: ObservableObject { public func startVPNConnection() { print("starting tunnel") + let logLevel = UserDefaults.standard.string(forKey: "logLevel") ?? "INFO" + print("Loglevel: " + logLevel) + let options: [String: NSObject] = ["logLevel": logLevel as NSObject] + do { - try self.session?.startVPNTunnel() + try self.session?.startVPNTunnel(options: options) print("VPN Tunnel started.") } catch let error { print("Failed to start VPN tunnel: \(error)") diff --git a/NetbirdNetworkExtension/PacketTunnelProvider.swift b/NetbirdNetworkExtension/PacketTunnelProvider.swift index b4e49ff..d813ad0 100644 --- a/NetbirdNetworkExtension/PacketTunnelProvider.swift +++ b/NetbirdNetworkExtension/PacketTunnelProvider.swift @@ -22,14 +22,18 @@ class PacketTunnelProvider: NEPacketTunnelProvider { private lazy var adapter: NetBirdAdapter = { return NetBirdAdapter(with: self.tunnelManager) }() - - override init() { - initializeLogging() - } override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) { - let options = FirebaseOptions(contentsOfFile: Bundle.main.path(forResource: "GoogleService-Info", ofType: "plist")!) - FirebaseApp.configure(options: options!) + let firebaseOptions = FirebaseOptions(contentsOfFile: Bundle.main.path(forResource: "GoogleService-Info", ofType: "plist")!) + FirebaseApp.configure(options: firebaseOptions!) + + if let options = options { + // For example, handle a specific option + if let logLevel = options["logLevel"] as? String { + initializeLogging(loglevel: logLevel) + } + } + if adapter.needsLogin() { DispatchQueue.main.asyncAfter(deadline: .now() + 2) { @@ -124,7 +128,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } } -func initializeLogging() { +func initializeLogging(loglevel: String) { let fileManager = FileManager.default let groupURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: "group.io.netbird.app") @@ -166,7 +170,7 @@ func initializeLogging() { if let logPath = logURL?.path { - success = NetBirdSDKInitializeLog("DEBUG", logPath, &error) + success = NetBirdSDKInitializeLog(loglevel, logPath, &error) } if !success, let actualError = error { print("Failed to initialize log: \(actualError.localizedDescription)")