diff --git a/Yggdrasil Network Cross-Platform/ConfigurationProxy.swift b/Yggdrasil Network Cross-Platform/ConfigurationProxy.swift index 2216490..6cd581b 100644 --- a/Yggdrasil Network Cross-Platform/ConfigurationProxy.swift +++ b/Yggdrasil Network Cross-Platform/ConfigurationProxy.swift @@ -13,6 +13,8 @@ import AppKit import SwiftUI import Yggdrasil import NetworkExtension +import Foundation +import CoreData #if os(iOS) class PlatformItemSource: NSObject, UIActivityItemSource { @@ -31,9 +33,10 @@ class PlatformItemSource: NSObject {} class ConfigurationProxy: PlatformItemSource { private var manager: NETunnelProviderManager? private var json: Data? = nil - private var dict: [String: Any]? = nil + private var dict: [String: Any]? = [:] + private var timer: Timer? - init(manager: NETunnelProviderManager? = nil) { + init(manager: NETunnelProviderManager) { self.manager = manager super.init() @@ -64,14 +67,13 @@ class ConfigurationProxy: PlatformItemSource { self.set("Listen", to: [] as [String]) self.set("AdminListen", to: "none") self.set("IfName", to: "dummy") - self.set("Peers", to: ["tcp://172.22.97.1.5190", "tls://172.22.97.1:5191"]) if self.get("AutoStart") == nil { self.set("AutoStart", to: ["Any": false, "WiFi": false, "Mobile": false, "Ethernet": false] as [String: Bool]) } let multicastInterfaces = self.get("MulticastInterfaces") as? [[String: Any]] ?? [] - if multicastInterfaces.count == 0 { + if multicastInterfaces.count != 1 { self.set("MulticastInterfaces", to: [ [ "Regex": "en.*", @@ -94,7 +96,7 @@ class ConfigurationProxy: PlatformItemSource { var multicastInterfaces = self.get("MulticastInterfaces") as? [[String: Any]] ?? [] multicastInterfaces[0]["Beacon"] = newValue self.set("MulticastInterfaces", to: multicastInterfaces) - self.trySave() + self.saveSoon() } } @@ -110,7 +112,23 @@ class ConfigurationProxy: PlatformItemSource { var multicastInterfaces = self.get("MulticastInterfaces") as? [[String: Any]] ?? [] multicastInterfaces[0]["Listen"] = newValue self.set("MulticastInterfaces", to: multicastInterfaces) - self.trySave() + self.saveSoon() + } + } + + public var multicastPassword: String { + get { + let multicastInterfaces = self.get("MulticastInterfaces") as? [[String: Any]] ?? [] + if multicastInterfaces.count == 0 { + return "" + } + return multicastInterfaces[0]["Password"] as? String ?? "" + } + set { + var multicastInterfaces = self.get("MulticastInterfaces") as? [[String: Any]] ?? [] + multicastInterfaces[0]["Password"] = newValue + self.set("MulticastInterfaces", to: multicastInterfaces) + self.saveSoon() } } @@ -120,7 +138,7 @@ class ConfigurationProxy: PlatformItemSource { } set { self.set("Any", inSection: "AutoStart", to: newValue) - self.trySave() + self.saveSoon() } } @@ -130,7 +148,7 @@ class ConfigurationProxy: PlatformItemSource { } set { self.set("WiFi", inSection: "AutoStart", to: newValue) - self.trySave() + self.saveSoon() } } @@ -140,7 +158,7 @@ class ConfigurationProxy: PlatformItemSource { } set { self.set("Ethernet", inSection: "AutoStart", to: newValue) - self.trySave() + self.saveSoon() } } @@ -150,7 +168,17 @@ class ConfigurationProxy: PlatformItemSource { } set { self.set("Mobile", inSection: "AutoStart", to: newValue) - self.trySave() + self.saveSoon() + } + } + + public var deviceName: String { + get { + return self.get("name", inSection: "NodeInfo") as? String ?? "" + } + set { + self.set("name", inSection: "NodeInfo", to: newValue) + self.saveSoon() } } @@ -160,7 +188,7 @@ class ConfigurationProxy: PlatformItemSource { } set { self.set("Peers", to: newValue) - self.trySave() + self.saveSoon() } } @@ -239,10 +267,22 @@ class ConfigurationProxy: PlatformItemSource { } } - private func trySave() { + private func saveSoon() { + self.timer?.invalidate() + self.timer = Timer.scheduledTimer( + timeInterval: 1.0, + target: self, + selector: #selector(saveFromTimer), + userInfo: nil, + repeats: false + ) + } + + @objc private func saveFromTimer() { if var manager = self.manager { try? self.save(to: &manager) } + self.timer = nil } func save(to manager: inout NETunnelProviderManager) throws { @@ -254,6 +294,8 @@ class ConfigurationProxy: PlatformItemSource { providerProtocol.serverAddress = "yggdrasil" providerProtocol.username = self.get("PublicKey") as? String ?? self.get("SigningPublicKey") as? String ?? "(unknown public key)" + NSLog(String(data: data, encoding: .utf8) ?? "(unknown)") + let disconnectrule = NEOnDemandRuleDisconnect() var rules: [NEOnDemandRule] = [disconnectrule] if self.get("Any", inSection: "AutoStart") as? Bool ?? false { @@ -283,10 +325,10 @@ class ConfigurationProxy: PlatformItemSource { manager.onDemandRules = rules manager.isOnDemandEnabled = rules.count > 1 providerProtocol.disconnectOnSleep = rules.count > 1 - + manager.protocolConfiguration = providerProtocol - manager.saveToPreferences(completionHandler: { (error:Error?) in + manager.saveToPreferences(completionHandler: { error in if let error = error { print(error) } else { diff --git a/Yggdrasil Network Cross-Platform/CrossPlatformAppDelegate.swift b/Yggdrasil Network Cross-Platform/CrossPlatformAppDelegate.swift index 013dd0d..68a1e0c 100644 --- a/Yggdrasil Network Cross-Platform/CrossPlatformAppDelegate.swift +++ b/Yggdrasil Network Cross-Platform/CrossPlatformAppDelegate.swift @@ -22,11 +22,12 @@ typealias ApplicationDelegateAdaptor = NSApplicationDelegateAdaptor class CrossPlatformAppDelegate: PlatformAppDelegate, ObservableObject { var vpnManager: NETunnelProviderManager = NETunnelProviderManager() - var yggdrasilConfig: ConfigurationProxy = ConfigurationProxy() + var yggdrasilConfig: ConfigurationProxy let yggdrasilComponent = "eu.neilalexander.yggdrasil.extension" private var adminTimer: DispatchSourceTimer? override init() { + self.yggdrasilConfig = ConfigurationProxy(manager: self.vpnManager) super.init() NotificationCenter.default.addObserver(forName: .NEVPNStatusDidChange, object: nil, queue: nil, using: { notification in @@ -34,7 +35,6 @@ class CrossPlatformAppDelegate: PlatformAppDelegate, ObservableObject { switch conn.status { case .connected: self.requestSummaryIPC() - self.requestStatusIPC() case .disconnecting, .disconnected: self.clearStatus() default: @@ -73,9 +73,7 @@ class CrossPlatformAppDelegate: PlatformAppDelegate, ObservableObject { @Published var yggdrasilSubnet: String = "N/A" @Published var yggdrasilCoords: String = "[]" - @Published var yggdrasilPeers: [[String: Any]] = [[:]] - @Published var yggdrasilDHT: [[String: Any]] = [[:]] - @Published var yggdrasilNodeInfo: [String: Any] = [:] + @Published var yggdrasilPeers: [YggdrasilPeer] = [] func yggdrasilVersion() -> String { return Yggdrasil.MobileGetVersion() @@ -111,7 +109,7 @@ class CrossPlatformAppDelegate: PlatformAppDelegate, ObservableObject { func updateStatus(conn: NEVPNConnection) { if conn.status == .connected { - self.requestStatusIPC() + self.requestSummaryIPC() } else if conn.status == .disconnecting || conn.status == .disconnected { self.clearStatus() } @@ -183,30 +181,14 @@ class CrossPlatformAppDelegate: PlatformAppDelegate, ObservableObject { if let session = self.vpnManager.connection as? NETunnelProviderSession { try? session.sendProviderMessage("summary".data(using: .utf8)!) { js in if let js = js, let summary = try? JSONDecoder().decode(YggdrasilSummary.self, from: js) { - self.yggdrasilEnabled = true + self.yggdrasilEnabled = summary.enabled self.yggdrasilIP = summary.address self.yggdrasilSubnet = summary.subnet self.yggdrasilPublicKey = summary.publicKey - } - } - } - } - - func requestStatusIPC() { - if self.vpnManager.connection.status != .connected { - return - } - if let session = self.vpnManager.connection as? NETunnelProviderSession { - try? session.sendProviderMessage("status".data(using: .utf8)!) { js in - if let js = js, let status = try? JSONDecoder().decode(YggdrasilStatus.self, from: js) { - self.yggdrasilCoords = status.coords - if let jsonResponse = try? JSONSerialization.jsonObject(with: status.peers, options: []) as? [[String: Any]] { - self.yggdrasilPeers = jsonResponse - } - if let jsonResponse = try? JSONSerialization.jsonObject(with: status.dht, options: []) as? [[String: Any]] { - self.yggdrasilDHT = jsonResponse - } - self.yggdrasilConnected = self.yggdrasilEnabled && self.yggdrasilPeers.count > 0 && self.yggdrasilDHT.count > 0 + self.yggdrasilPeers = summary.peers + self.yggdrasilConnected = summary.peers.count > 0 + + print(self.yggdrasilPeers) } } } @@ -218,6 +200,5 @@ class CrossPlatformAppDelegate: PlatformAppDelegate, ObservableObject { self.yggdrasilSubnet = "N/A" self.yggdrasilCoords = "[]" self.yggdrasilPeers = [] - self.yggdrasilDHT = [] } } diff --git a/Yggdrasil Network Cross-Platform/IPCResponses.swift b/Yggdrasil Network Cross-Platform/IPCResponses.swift index eb105cf..9f938b7 100644 --- a/Yggdrasil Network Cross-Platform/IPCResponses.swift +++ b/Yggdrasil Network Cross-Platform/IPCResponses.swift @@ -11,11 +11,29 @@ struct YggdrasilSummary: Codable { var address: String var subnet: String var publicKey: String + var enabled: Bool + var peers: [YggdrasilPeer] + + func list() -> [String] { + return peers.map { $0.remote } + } } -struct YggdrasilStatus: Codable { - var enabled: Bool - var coords: String - var peers: Data - var dht: Data +struct YggdrasilPeer: Codable, Identifiable { + var id: String { remote } // For Identifiable protocol + let remote: String + let up: Bool + let address: String + let key: String + let priority: UInt8 + let cost: UInt16? + + enum CodingKeys: String, CodingKey { + case remote = "URI" + case up = "Up" + case address = "IP" + case key = "Key" + case priority = "Priority" + case cost = "Cost" + } } diff --git a/Yggdrasil Network Extension/PacketTunnelProvider.swift b/Yggdrasil Network Extension/PacketTunnelProvider.swift index f1d982a..e96ff04 100644 --- a/Yggdrasil Network Extension/PacketTunnelProvider.swift +++ b/Yggdrasil Network Extension/PacketTunnelProvider.swift @@ -98,25 +98,28 @@ class PacketTunnelProvider: NEPacketTunnelProvider { let request = String(data: messageData, encoding: .utf8) switch request { case "summary": + let pj = self.yggdrasil.getPeersJSON() + var peers: [YggdrasilPeer] = [] + do { + peers = try JSONDecoder().decode( + [YggdrasilPeer].self, + from: pj.data(using: .utf8)! + ) + } catch { + NSLog("JSON Error: \(error)") + } let summary = YggdrasilSummary( address: self.yggdrasil.getAddressString(), subnet: self.yggdrasil.getSubnetString(), - publicKey: self.yggdrasil.getPublicKeyString() + publicKey: self.yggdrasil.getPublicKeyString(), + enabled: true, + peers: peers.sorted(by: { a, b in + a.remote < b.remote + }) ) if let json = try? JSONEncoder().encode(summary) { completionHandler?(json) } - - case "status": - let status = YggdrasilStatus( - enabled: true, - coords: self.yggdrasil.getCoordsString(), - peers: self.yggdrasil.getPeersJSON().data(using: .utf8) ?? Data(), - dht: self.yggdrasil.getDHTJSON().data(using: .utf8) ?? Data() - ) - if let json = try? JSONEncoder().encode(status) { - completionHandler?(json) - } default: completionHandler?(nil) diff --git a/Yggdrasil Network.xcodeproj/project.pbxproj b/Yggdrasil Network.xcodeproj/project.pbxproj index 08ff9a1..d6a1166 100644 --- a/Yggdrasil Network.xcodeproj/project.pbxproj +++ b/Yggdrasil Network.xcodeproj/project.pbxproj @@ -3,17 +3,18 @@ archiveVersion = 1; classes = { }; - objectVersion = 55; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 391B72A82A2FD90100896278 /* PacketTunnelProvider+FileDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391B72A62A2FD90100896278 /* PacketTunnelProvider+FileDescriptor.swift */; }; 3952ADB829945AF700B3835D /* ConfigurationProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3952ADB629945AF700B3835D /* ConfigurationProxy.swift */; }; - 3996AF39270328080070947D /* Yggdrasil.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3996AF37270328080070947D /* Yggdrasil.xcframework */; }; 39B51A4A2997062E0059D29D /* PeersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39B51A492997062E0059D29D /* PeersView.swift */; }; 39B51A4B29994F240059D29D /* ConfigurationProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3952ADB629945AF700B3835D /* ConfigurationProxy.swift */; }; 39B51A4C29994F350059D29D /* CrossPlatformAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3952ADB929945AFA00B3835D /* CrossPlatformAppDelegate.swift */; }; 39B51A4D299951D60059D29D /* IPCResponses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CC924B221DEDCE004960DC /* IPCResponses.swift */; }; + 39B9173C2BBD637200899F81 /* Yggdrasil.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3996AF37270328080070947D /* Yggdrasil.xcframework */; }; + 39B9173D2BBD637200899F81 /* Yggdrasil.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3996AF37270328080070947D /* Yggdrasil.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 39BF9FC72A2F6FFD000E7269 /* Yggdrasil.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3996AF37270328080070947D /* Yggdrasil.xcframework */; }; 39CC924D221DEDD3004960DC /* IPCResponses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CC924B221DEDCE004960DC /* IPCResponses.swift */; }; 39F0205E2996CD760093F603 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39F0205D2996CD760093F603 /* Application.swift */; }; @@ -22,7 +23,7 @@ 39F020662996CD770093F603 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 39F020652996CD770093F603 /* Preview Assets.xcassets */; }; 39F0206B2996CF260093F603 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39F0206A2996CF260093F603 /* SettingsView.swift */; }; 39F99B342A48F6E50045BD10 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39AE88382319C93F0010FFF6 /* NetworkExtension.framework */; }; - 39F99B382A48F74F0045BD10 /* YggdrasilNetworkExtension.appex in CopyFiles */ = {isa = PBXBuildFile; fileRef = E593CE971DF905AF00D7265D /* YggdrasilNetworkExtension.appex */; platformFilters = (ios, macos, ); settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 39F99B382A48F74F0045BD10 /* YggdrasilNetworkExtension.appex in CopyFiles */ = {isa = PBXBuildFile; fileRef = E593CE971DF905AF00D7265D /* YggdrasilNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; E593CE9C1DF905AF00D7265D /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E593CE9B1DF905AF00D7265D /* PacketTunnelProvider.swift */; }; /* End PBXBuildFile section */ @@ -37,6 +38,17 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + 39B9173E2BBD637200899F81 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 39B9173D2BBD637200899F81 /* Yggdrasil.xcframework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; 39F99B372A48F7380045BD10 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -87,7 +99,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 3996AF39270328080070947D /* Yggdrasil.xcframework in Frameworks */, + 39B9173C2BBD637200899F81 /* Yggdrasil.xcframework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -199,6 +211,7 @@ E593CE931DF905AF00D7265D /* Sources */, E593CE941DF905AF00D7265D /* Frameworks */, E593CE951DF905AF00D7265D /* Resources */, + 39B9173E2BBD637200899F81 /* Embed Frameworks */, ); buildRules = ( ); @@ -309,10 +322,6 @@ /* Begin PBXTargetDependency section */ 39F99B362A48F70F0045BD10 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - platformFilters = ( - ios, - macos, - ); target = E593CE961DF905AF00D7265D /* YggdrasilNetworkExtension */; targetProxy = 39F99B352A48F70F0045BD10 /* PBXContainerItemProxy */; }; @@ -363,6 +372,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -413,6 +423,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -548,9 +559,13 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "Yggdrasil Network Extension/YggdrasilNetworkExtension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 32; - DEVELOPMENT_TEAM = R9AV23TXF2; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = R9AV23TXF2; + "DEVELOPMENT_TEAM[sdk=macosx*]" = R9AV23TXF2; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -566,8 +581,12 @@ PRODUCT_BUNDLE_IDENTIFIER = eu.neilalexander.yggdrasil.extension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - SUPPORTS_MACCATALYST = YES; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Provisioning eu.neilalexander.yggdrasil.extension"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Provisioning2 eu.neilalexander.yggdrasil.extension"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "Yggdrasil Network Extension/YggdrasilNetworkExtension-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -580,9 +599,13 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "Yggdrasil Network Extension/YggdrasilNetworkExtension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 32; - DEVELOPMENT_TEAM = R9AV23TXF2; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = R9AV23TXF2; + "DEVELOPMENT_TEAM[sdk=macosx*]" = R9AV23TXF2; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -599,8 +622,12 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "8ce353d5-fd5f-4d5e-b664-8ad294091125"; PROVISIONING_PROFILE_SPECIFIER = ""; - SUPPORTS_MACCATALYST = YES; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Provisioning eu.neilalexander.yggdrasil.extension"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Provisioning2 eu.neilalexander.yggdrasil.extension"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "Yggdrasil Network Extension/YggdrasilNetworkExtension-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/YggdrasilSwiftUI/Application.swift b/YggdrasilSwiftUI/Application.swift index 404e7e5..038bfb7 100644 --- a/YggdrasilSwiftUI/Application.swift +++ b/YggdrasilSwiftUI/Application.swift @@ -23,7 +23,7 @@ struct Application: App { var body: some Scene { WindowGroup { NavigationSplitView { - ZStack { + VStack { List(selection: $selection) { NavigationLink(destination: StatusView()) { HStack { diff --git a/YggdrasilSwiftUI/PeersView.swift b/YggdrasilSwiftUI/PeersView.swift index 546a896..518528b 100644 --- a/YggdrasilSwiftUI/PeersView.swift +++ b/YggdrasilSwiftUI/PeersView.swift @@ -8,22 +8,25 @@ import SwiftUI struct PeersView: View { - // @Binding public var yggdrasilConfiguration: ConfigurationProxy @ObservedObject private var appDelegate = Application.appDelegate +#if os(iOS) + @Environment(\.editMode) var editMode +#endif var body: some View { Form { Section(content: { ForEach(Array(appDelegate.yggdrasilConfig.peers.enumerated()), id: \.offset) { index, peer in HStack() { - Text(peer) - //TextField("", text: $yggdrasilConfiguration.peers[index]) - // .multilineTextAlignment(.leading) + TextField("Peer", text: $appDelegate.yggdrasilConfig.peers[index]) + .labelsHidden() +#if os(iOS) + .disabled(!editMode!.wrappedValue.isEditing) +#endif #if os(macOS) Spacer() Button(role: .destructive) { - // appDelegate.yggdrasilConfig.peers.remove { $0 == peerURI } - // self.delete(at: appDelegate.yggdrasilConfig.peers.firstIndex { $0 == peerURI }) + appDelegate.yggdrasilConfig.peers.remove(at: index) } label: { Image(systemName: "xmark.circle.fill") } @@ -38,13 +41,15 @@ struct PeersView: View { appDelegate.yggdrasilConfig.peers.remove(atOffsets: indexSet) } +#if os(macOS) Button { - appDelegate.yggdrasilConfig.peers.append("foo") + appDelegate.yggdrasilConfig.peers.append("") } label: { Label("Add peer", systemImage: "plus") } .buttonStyle(.plain) .foregroundColor(.accentColor) +#endif Text("Yggdrasil will automatically attempt to connect to configured peers when started. If you configure more than one peer, your device may carry traffic on behalf of other network nodes. Avoid this by configuring only a single peer. Data charges may apply when using mobile data.") .font(.system(size: 11)) @@ -70,20 +75,27 @@ struct PeersView: View { .foregroundColor(.gray) } } + TextField("Multicast password", text: $appDelegate.yggdrasilConfig.multicastPassword) }, header: { Text("Local connectivity") }) } -#if os(iOS) - .toolbar { - // EditButton() - } -#endif .formStyle(.grouped) .navigationTitle("Peers") - #if os(iOS) +#if os(iOS) + .toolbar { + Button("Add", systemImage: "plus") { + appDelegate.yggdrasilConfig.peers.append("") + } + EditButton() + .onChange(of: editMode!.wrappedValue) { edit in + if !edit.isEditing { + try? appDelegate.yggdrasilConfig.save(to: &appDelegate.vpnManager) + } + } + } .navigationBarTitleDisplayMode(.large) - #endif +#endif } } diff --git a/YggdrasilSwiftUI/SettingsView.swift b/YggdrasilSwiftUI/SettingsView.swift index d0dbe6d..4b569f7 100644 --- a/YggdrasilSwiftUI/SettingsView.swift +++ b/YggdrasilSwiftUI/SettingsView.swift @@ -16,7 +16,7 @@ struct SettingsView: View { var body: some View { Form { Section(content: { - TextField("Device Name", text: $deviceName) + TextField("Device Name", text: $appDelegate.yggdrasilConfig.deviceName) }, header: { Text("Public Identity") }) diff --git a/YggdrasilSwiftUI/StatusView.swift b/YggdrasilSwiftUI/StatusView.swift index 0ef2ecd..5fadd1f 100644 --- a/YggdrasilSwiftUI/StatusView.swift +++ b/YggdrasilSwiftUI/StatusView.swift @@ -93,6 +93,8 @@ struct StatusView: View { Text(appDelegate.yggdrasilVersion()) .foregroundColor(Color.gray) } + }, header: { + Text("Status") }) Section(content: { @@ -114,7 +116,7 @@ struct StatusView: View { .lineLimit(1) .textSelection(.enabled) } - HStack { + /*HStack { Text("Coordinates") Spacer() Text(appDelegate.yggdrasilCoords) @@ -122,7 +124,7 @@ struct StatusView: View { .truncationMode(.tail) .lineLimit(1) .textSelection(.enabled) - } + }*/ HStack { Text("Public Key") Spacer() @@ -133,7 +135,35 @@ struct StatusView: View { .lineLimit(1) .textSelection(.enabled) } + }, header: { + Text("Details") }) + + if self.appDelegate.yggdrasilEnabled { + Section(content: { + List(self.appDelegate.yggdrasilPeers.sorted(by: { a, b in + a.key < a.key + }), id: \.remote) { peer in + VStack { + Text(peer.remote) + .frame(maxWidth: .infinity, alignment: .leading) + .truncationMode(.tail) + .lineLimit(1) + .textSelection(.enabled) + Text(peer.address) + .frame(maxWidth: .infinity, alignment: .leading) + .foregroundColor(Color.gray) + .font(.system(size: 11, design: .monospaced)) + .truncationMode(.tail) + .lineLimit(1) + .textSelection(.enabled) + } + .padding(.all, 2) + } + }, header: { + Text("Peers") + }) + } } .formStyle(.grouped) .navigationTitle("Yggdrasil")