You've already forked ios-client
mirror of
https://github.com/netbirdio/ios-client.git
synced 2026-05-22 17:10:12 -07:00
Add log export option (#7)
This commit is contained in:
@@ -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 = "<group>"; };
|
||||
50CD81AF2AD5B94D00CF830B /* PeerCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerCard.swift; sourceTree = "<group>"; };
|
||||
50CD84352AD82F9400CF830B /* ServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerView.swift; sourceTree = "<group>"; };
|
||||
50D4028F2BD8188C00D4AC5B /* NetBirdSDK.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = NetBirdSDK.xcframework; path = NetBird/NetBirdSDK.xcframework; sourceTree = "<group>"; };
|
||||
50D402912BD913D100D4AC5B /* DirectoryPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectoryPicker.swift; sourceTree = "<group>"; };
|
||||
50D402932BD9143900D4AC5B /* NetBirdSDK.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = NetBirdSDK.xcframework; path = NetBird/NetBirdSDK.xcframework; sourceTree = "<group>"; };
|
||||
50E608022A7950CB00BAF09B /* Device.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Device.swift; sourceTree = "<group>"; };
|
||||
50E608122A7958B100BAF09B /* MainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewModel.swift; sourceTree = "<group>"; };
|
||||
50E6081F2A7979D600BAF09B /* SideDrawer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideDrawer.swift; sourceTree = "<group>"; };
|
||||
@@ -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 */,
|
||||
|
||||
@@ -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<AnyCancellable>()
|
||||
|
||||
init() {
|
||||
let logLevel = UserDefaults.standard.string(forKey: "logLevel") ?? "INFO"
|
||||
self.traceLogsEnabled = logLevel == "TRACE"
|
||||
self.rosenpassEnabled = self.getRosenpassEnabled()
|
||||
self.rosenpassPermissive = self.getRosenpassPermissive()
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ struct AdvancedView: View {
|
||||
@EnvironmentObject var viewModel: ViewModel
|
||||
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
|
||||
|
||||
@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()
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)")
|
||||
|
||||
@@ -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)")
|
||||
|
||||
Reference in New Issue
Block a user