diff --git a/DNSecure.xcodeproj/project.pbxproj b/DNSecure.xcodeproj/project.pbxproj index 145fe49..4f28bf5 100644 --- a/DNSecure.xcodeproj/project.pbxproj +++ b/DNSecure.xcodeproj/project.pbxproj @@ -9,6 +9,13 @@ /* Begin PBXBuildFile section */ 890B80D5251DC3A20046BAA0 /* DetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 890B80D4251DC3A20046BAA0 /* DetailView.swift */; }; 890B80DF251DC6B50046BAA0 /* Presets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 890B80DE251DC6B50046BAA0 /* Presets.swift */; }; + 893AA853258F99630060B022 /* NEOnDemandRuleInterfaceType+CaseIterable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA852258F99630060B022 /* NEOnDemandRuleInterfaceType+CaseIterable.swift */; }; + 893AA858258F996F0060B022 /* NEOnDemandRuleInterfaceType+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA857258F996F0060B022 /* NEOnDemandRuleInterfaceType+CustomStringConvertible.swift */; }; + 893AA85D258F997A0060B022 /* NEOnDemandRuleInterfaceType+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA85C258F997A0060B022 /* NEOnDemandRuleInterfaceType+Codable.swift */; }; + 893AA862258F998C0060B022 /* NEOnDemandRuleInterfaceType+ssidIsUsed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA861258F998C0060B022 /* NEOnDemandRuleInterfaceType+ssidIsUsed.swift */; }; + 893AA867258F99990060B022 /* NEOnDemandRuleAction+CaseIterable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA866258F99990060B022 /* NEOnDemandRuleAction+CaseIterable.swift */; }; + 893AA86C258F99A10060B022 /* NEOnDemandRuleAction+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA86B258F99A10060B022 /* NEOnDemandRuleAction+CustomStringConvertible.swift */; }; + 893AA871258F99AD0060B022 /* NEOnDemandRuleAction+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA870258F99AD0060B022 /* NEOnDemandRuleAction+Codable.swift */; }; 8940023C24ACBD2700EBE74B /* DNSecureApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8940023B24ACBD2700EBE74B /* DNSecureApp.swift */; }; 8940023E24ACBD2700EBE74B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8940023D24ACBD2700EBE74B /* ContentView.swift */; }; 8940024024ACBD2800EBE74B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8940023F24ACBD2800EBE74B /* Assets.xcassets */; }; @@ -16,6 +23,7 @@ 8940024E24ACBD2800EBE74B /* DNSecureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8940024D24ACBD2800EBE74B /* DNSecureTests.swift */; }; 8940025924ACBD2800EBE74B /* DNSecureUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8940025824ACBD2800EBE74B /* DNSecureUITests.swift */; }; 8940026924ACBE4900EBE74B /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8940026824ACBE4900EBE74B /* NetworkExtension.framework */; }; + 894958AD2548405E009691D5 /* RuleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 894958AC2548405E009691D5 /* RuleView.swift */; }; 8963FDFB251DF1BC00E3DFE7 /* Bundle+displayName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8963FDFA251DF1BC00E3DFE7 /* Bundle+displayName.swift */; }; 8986CDCF251D9B3400D947CD /* Resolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8986CDCE251D9B3400D947CD /* Resolver.swift */; }; 89CB922125209DD100B6983C /* HowToActivateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89CB922025209DD100B6983C /* HowToActivateView.swift */; }; @@ -42,6 +50,13 @@ 890B80D4251DC3A20046BAA0 /* DetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailView.swift; sourceTree = ""; }; 890B80DE251DC6B50046BAA0 /* Presets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Presets.swift; sourceTree = ""; }; 8924EDF324C9CDF1004AF871 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 893AA852258F99630060B022 /* NEOnDemandRuleInterfaceType+CaseIterable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleInterfaceType+CaseIterable.swift"; sourceTree = ""; }; + 893AA857258F996F0060B022 /* NEOnDemandRuleInterfaceType+CustomStringConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleInterfaceType+CustomStringConvertible.swift"; sourceTree = ""; }; + 893AA85C258F997A0060B022 /* NEOnDemandRuleInterfaceType+Codable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleInterfaceType+Codable.swift"; sourceTree = ""; }; + 893AA861258F998C0060B022 /* NEOnDemandRuleInterfaceType+ssidIsUsed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleInterfaceType+ssidIsUsed.swift"; sourceTree = ""; }; + 893AA866258F99990060B022 /* NEOnDemandRuleAction+CaseIterable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleAction+CaseIterable.swift"; sourceTree = ""; }; + 893AA86B258F99A10060B022 /* NEOnDemandRuleAction+CustomStringConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleAction+CustomStringConvertible.swift"; sourceTree = ""; }; + 893AA870258F99AD0060B022 /* NEOnDemandRuleAction+Codable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleAction+Codable.swift"; sourceTree = ""; }; 8940023824ACBD2700EBE74B /* DNSecure.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DNSecure.app; sourceTree = BUILT_PRODUCTS_DIR; }; 8940023B24ACBD2700EBE74B /* DNSecureApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSecureApp.swift; sourceTree = ""; }; 8940023D24ACBD2700EBE74B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -56,6 +71,7 @@ 8940025A24ACBD2800EBE74B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 8940026624ACBE4900EBE74B /* DNSecure.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DNSecure.entitlements; sourceTree = ""; }; 8940026824ACBE4900EBE74B /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; + 894958AC2548405E009691D5 /* RuleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleView.swift; sourceTree = ""; }; 8963FDFA251DF1BC00E3DFE7 /* Bundle+displayName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+displayName.swift"; sourceTree = ""; }; 8986CDCE251D9B3400D947CD /* Resolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Resolver.swift; sourceTree = ""; }; 89CB922025209DD100B6983C /* HowToActivateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HowToActivateView.swift; sourceTree = ""; }; @@ -93,6 +109,7 @@ 8940023D24ACBD2700EBE74B /* ContentView.swift */, 89CB922025209DD100B6983C /* HowToActivateView.swift */, 890B80D4251DC3A20046BAA0 /* DetailView.swift */, + 894958AC2548405E009691D5 /* RuleView.swift */, ); path = Views; sourceTree = ""; @@ -110,6 +127,13 @@ isa = PBXGroup; children = ( 8963FDFA251DF1BC00E3DFE7 /* Bundle+displayName.swift */, + 893AA852258F99630060B022 /* NEOnDemandRuleInterfaceType+CaseIterable.swift */, + 893AA857258F996F0060B022 /* NEOnDemandRuleInterfaceType+CustomStringConvertible.swift */, + 893AA85C258F997A0060B022 /* NEOnDemandRuleInterfaceType+Codable.swift */, + 893AA861258F998C0060B022 /* NEOnDemandRuleInterfaceType+ssidIsUsed.swift */, + 893AA866258F99990060B022 /* NEOnDemandRuleAction+CaseIterable.swift */, + 893AA86B258F99A10060B022 /* NEOnDemandRuleAction+CustomStringConvertible.swift */, + 893AA870258F99AD0060B022 /* NEOnDemandRuleAction+Codable.swift */, ); path = Extensions; sourceTree = ""; @@ -317,9 +341,17 @@ 8986CDCF251D9B3400D947CD /* Resolver.swift in Sources */, 89CB922125209DD100B6983C /* HowToActivateView.swift in Sources */, 890B80D5251DC3A20046BAA0 /* DetailView.swift in Sources */, + 893AA853258F99630060B022 /* NEOnDemandRuleInterfaceType+CaseIterable.swift in Sources */, + 893AA871258F99AD0060B022 /* NEOnDemandRuleAction+Codable.swift in Sources */, + 893AA85D258F997A0060B022 /* NEOnDemandRuleInterfaceType+Codable.swift in Sources */, 8940023E24ACBD2700EBE74B /* ContentView.swift in Sources */, + 894958AD2548405E009691D5 /* RuleView.swift in Sources */, 890B80DF251DC6B50046BAA0 /* Presets.swift in Sources */, + 893AA858258F996F0060B022 /* NEOnDemandRuleInterfaceType+CustomStringConvertible.swift in Sources */, 8940023C24ACBD2700EBE74B /* DNSecureApp.swift in Sources */, + 893AA862258F998C0060B022 /* NEOnDemandRuleInterfaceType+ssidIsUsed.swift in Sources */, + 893AA867258F99990060B022 /* NEOnDemandRuleAction+CaseIterable.swift in Sources */, + 893AA86C258F99A10060B022 /* NEOnDemandRuleAction+CustomStringConvertible.swift in Sources */, 8963FDFB251DF1BC00E3DFE7 /* Bundle+displayName.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/DNSecure/Extensions/NEOnDemandRuleAction+CaseIterable.swift b/DNSecure/Extensions/NEOnDemandRuleAction+CaseIterable.swift new file mode 100644 index 0000000..2ae320f --- /dev/null +++ b/DNSecure/Extensions/NEOnDemandRuleAction+CaseIterable.swift @@ -0,0 +1,14 @@ +// +// NEOnDemandRuleAction+CaseIterable.swift +// DNSecure +// +// Created by Kenta Kubo on 12/20/20. +// + +import NetworkExtension + +extension NEOnDemandRuleAction: CaseIterable { + public static var allCases: [Self] { + [.connect, .disconnect, .evaluateConnection, .ignore] + } +} diff --git a/DNSecure/Extensions/NEOnDemandRuleAction+Codable.swift b/DNSecure/Extensions/NEOnDemandRuleAction+Codable.swift new file mode 100644 index 0000000..0ae8ebc --- /dev/null +++ b/DNSecure/Extensions/NEOnDemandRuleAction+Codable.swift @@ -0,0 +1,10 @@ +// +// NEOnDemandRuleAction+Codable.swift +// DNSecure +// +// Created by Kenta Kubo on 12/20/20. +// + +import NetworkExtension + +extension NEOnDemandRuleAction: Codable {} diff --git a/DNSecure/Extensions/NEOnDemandRuleAction+CustomStringConvertible.swift b/DNSecure/Extensions/NEOnDemandRuleAction+CustomStringConvertible.swift new file mode 100644 index 0000000..4b309b6 --- /dev/null +++ b/DNSecure/Extensions/NEOnDemandRuleAction+CustomStringConvertible.swift @@ -0,0 +1,25 @@ +// +// NEOnDemandRuleAction+CustomStringConvertible.swift +// DNSecure +// +// Created by Kenta Kubo on 12/20/20. +// + +import NetworkExtension + +extension NEOnDemandRuleAction: CustomStringConvertible { + public var description: String { + switch self { + case .connect: + return "Apply settings" + case .disconnect: + return "Do not apply settings" + case .evaluateConnection: + return "Apply with excluded domains" + case .ignore: + return "As is" + default: + return "Unknown" + } + } +} diff --git a/DNSecure/Extensions/NEOnDemandRuleInterfaceType+CaseIterable.swift b/DNSecure/Extensions/NEOnDemandRuleInterfaceType+CaseIterable.swift new file mode 100644 index 0000000..2460f00 --- /dev/null +++ b/DNSecure/Extensions/NEOnDemandRuleInterfaceType+CaseIterable.swift @@ -0,0 +1,18 @@ +// +// NEOnDemandRuleInterfaceType+CaseIterable.swift +// DNSecure +// +// Created by Kenta Kubo on 12/20/20. +// + +import NetworkExtension + +extension NEOnDemandRuleInterfaceType: CaseIterable { + public static var allCases: [Self] { + #if os(macOS) + return [.any, .ethernet, .wiFi] + #else + return [.any, .wiFi, .cellular] + #endif + } +} diff --git a/DNSecure/Extensions/NEOnDemandRuleInterfaceType+Codable.swift b/DNSecure/Extensions/NEOnDemandRuleInterfaceType+Codable.swift new file mode 100644 index 0000000..8889f31 --- /dev/null +++ b/DNSecure/Extensions/NEOnDemandRuleInterfaceType+Codable.swift @@ -0,0 +1,10 @@ +// +// NEOnDemandRuleInterfaceType+Codable.swift +// DNSecure +// +// Created by Kenta Kubo on 12/20/20. +// + +import NetworkExtension + +extension NEOnDemandRuleInterfaceType: Codable {} diff --git a/DNSecure/Extensions/NEOnDemandRuleInterfaceType+CustomStringConvertible.swift b/DNSecure/Extensions/NEOnDemandRuleInterfaceType+CustomStringConvertible.swift new file mode 100644 index 0000000..cce4a92 --- /dev/null +++ b/DNSecure/Extensions/NEOnDemandRuleInterfaceType+CustomStringConvertible.swift @@ -0,0 +1,29 @@ +// +// NEOnDemandRuleInterfaceType+CustomStringConvertible.swift +// DNSecure +// +// Created by Kenta Kubo on 12/20/20. +// + +import NetworkExtension + +extension NEOnDemandRuleInterfaceType: CustomStringConvertible { + public var description: String { + switch self { + case .any: + return "Any" + #if os(macOS) + case .ethernet: + return "Ethernet" + #endif + case .wiFi: + return "Wi-Fi" + #if os(iOS) + case .cellular: + return "Cellular" + #endif + default: + return "Unknown" + } + } +} diff --git a/DNSecure/Extensions/NEOnDemandRuleInterfaceType+ssidIsUsed.swift b/DNSecure/Extensions/NEOnDemandRuleInterfaceType+ssidIsUsed.swift new file mode 100644 index 0000000..0da483e --- /dev/null +++ b/DNSecure/Extensions/NEOnDemandRuleInterfaceType+ssidIsUsed.swift @@ -0,0 +1,19 @@ +// +// NEOnDemandRuleInterfaceType+ssidIsUsed.swift +// DNSecure +// +// Created by Kenta Kubo on 12/20/20. +// + +import NetworkExtension + +extension NEOnDemandRuleInterfaceType { + var ssidIsUsed: Bool { + switch self { + case .any, .wiFi: + return true + default: + return false + } + } +} diff --git a/DNSecure/Models/Resolver.swift b/DNSecure/Models/Resolver.swift index 782fc89..6c19022 100644 --- a/DNSecure/Models/Resolver.swift +++ b/DNSecure/Models/Resolver.swift @@ -19,6 +19,10 @@ struct DoTConfiguration { } } +extension DoTConfiguration: Equatable {} + +extension DoTConfiguration: Hashable {} + extension DoTConfiguration: Codable {} struct DoHConfiguration { @@ -32,6 +36,10 @@ struct DoHConfiguration { } } +extension DoHConfiguration: Equatable {} + +extension DoHConfiguration: Hashable {} + extension DoHConfiguration: Codable {} enum Configuration { @@ -48,6 +56,10 @@ enum Configuration { } } +extension Configuration: Equatable {} + +extension Configuration: Hashable {} + extension Configuration: Codable { enum CodingKeys: String, CodingKey { case base, dotConfiguration, dohConfiguration @@ -94,15 +106,106 @@ extension Configuration: CustomStringConvertible { } } +struct OnDemandRule { + var id = UUID() + var name: String + var action: NEOnDemandRuleAction = .ignore + var interfaceType: NEOnDemandRuleInterfaceType = .any + var ssidMatch: [String] = [] + var dnsSearchDomainMatch: [String] = [] + var dnsServerAddressMatch: [String] = [] + var probeURL: URL? +} + +extension OnDemandRule: Identifiable {} + +extension OnDemandRule: Equatable {} + +extension OnDemandRule: Hashable {} + +extension OnDemandRule: Codable {} + +extension Array where Self.Element == OnDemandRule { + func toNEOnDemandRules() -> [NEOnDemandRule] { + self.lazy + .map { rule in + switch rule.action { + case .connect: + let newRule = NEOnDemandRuleConnect() + newRule.interfaceTypeMatch = rule.interfaceType + if rule.interfaceType.ssidIsUsed { + newRule.ssidMatch = rule.ssidMatch + } + newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch + newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch + newRule.probeURL = rule.probeURL + return newRule + case .disconnect: + let newRule = NEOnDemandRuleDisconnect() + newRule.interfaceTypeMatch = rule.interfaceType + if rule.interfaceType.ssidIsUsed { + newRule.ssidMatch = rule.ssidMatch + } + newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch + newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch + newRule.probeURL = rule.probeURL + return newRule + case .evaluateConnection: + let newRule = NEOnDemandRuleEvaluateConnection() + newRule.interfaceTypeMatch = rule.interfaceType + if rule.interfaceType.ssidIsUsed { + newRule.ssidMatch = rule.ssidMatch + } + newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch + newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch + newRule.probeURL = rule.probeURL + return newRule + case .ignore: + let newRule = NEOnDemandRuleIgnore() + newRule.interfaceTypeMatch = rule.interfaceType + if rule.interfaceType.ssidIsUsed { + newRule.ssidMatch = rule.ssidMatch + } + newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch + newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch + newRule.probeURL = rule.probeURL + return newRule + default: + preconditionFailure("Unexpected NEOnDemandRuleAction") + } + } + } +} + struct Resolver { var id = UUID() var name: String var configuration: Configuration + var onDemandRules: [OnDemandRule] = [] } extension Resolver: Identifiable {} -extension Resolver: Codable {} +extension Resolver: Equatable {} + +extension Resolver: Hashable {} + +extension Resolver: Codable { + enum CodingKeys: String, CodingKey { + case id, name, configuration, onDemandRules + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(UUID.self, forKey: .id) + self.name = try container.decode(String.self, forKey: .name) + self.configuration = try container.decode(Configuration.self, forKey: .configuration) + self.onDemandRules = try container.decodeIfPresent( + [OnDemandRule].self, + forKey: .onDemandRules + ) ?? [] + } +} typealias Resolvers = [Resolver] diff --git a/DNSecure/Views/ContentView.swift b/DNSecure/Views/ContentView.swift index 8145868..106176b 100644 --- a/DNSecure/Views/ContentView.swift +++ b/DNSecure/Views/ContentView.swift @@ -41,12 +41,17 @@ struct ContentView { func removeServers(at indexSet: IndexSet) { if let current = self.selection, indexSet.contains(where: { $0 <= current }) { - self.selection = nil + // FIXME: This is a workaround not to crash on deletion. + self.selection = -1 } if indexSet.map({ self.servers[$0].id.uuidString }).contains(self.usedID) { self.removeSettings() } - self.servers.remove(atOffsets: indexSet) + // FIXME: This is a workaround not to crash on deletion. + // Wait for closing DetailView. + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) { + self.servers.remove(atOffsets: indexSet) + } } func moveServers(from src: IndexSet, to dst: Int) { @@ -76,6 +81,7 @@ struct ContentView { #if !targetEnvironment(simulator) let manager = NEDNSSettingsManager.shared() manager.dnsSettings = server.configuration.toDNSSettings() + manager.onDemandRules = server.onDemandRules.toNEOnDemandRules() manager.saveToPreferences { saveError in if let saveError = saveError as NSError? { guard saveError.domain != "NEConfigurationErrorDomain" @@ -125,21 +131,17 @@ extension ContentView: View { var body: some View { NavigationView { List { + NavigationLink( + "Instructions", + destination: HowToActivateView(isSheet: false), + tag: -1, + selection: self.$selection + ) Section(header: Text("Servers")) { ForEach(0.. Binding { + guard let index = self.server.onDemandRules.firstIndex(of: rule) else { + preconditionFailure("Can't find rule in array") + } + return self.$server.onDemandRules[index] + } } extension DetailView: View { @@ -157,6 +164,25 @@ extension DetailView: View { } } } + Section( + header: EditButton() + .foregroundColor(.accentColor) + .frame(maxWidth: .infinity, alignment: .trailing) + .overlay(Text("On Demand Rules"), alignment: .leading) + ) { + ForEach(self.server.onDemandRules) { rule in + NavigationLink( + rule.name, + destination: RuleView(rule: self.binding(for: rule)) + ) + } + .onDelete { self.server.onDemandRules.remove(atOffsets: $0) } + .onMove { self.server.onDemandRules.move(fromOffsets: $0, toOffset: $1) } + Button("Add New Rule") { + self.server.onDemandRules + .append(OnDemandRule(name: "New Rule")) + } + } } .navigationTitle(self.server.name) } diff --git a/DNSecure/Views/RuleView.swift b/DNSecure/Views/RuleView.swift new file mode 100644 index 0000000..637b654 --- /dev/null +++ b/DNSecure/Views/RuleView.swift @@ -0,0 +1,141 @@ +// +// RuleView.swift +// DNSecure +// +// Created by Kenta Kubo on 10/27/20. +// + +import NetworkExtension +import SwiftUI + +struct RuleView { + @Binding var rule: OnDemandRule +} + +extension RuleView: View { + var body: some View { + Form { + Section { + HStack { + Text("Name") + TextField("Name", text: self.$rule.name) + .multilineTextAlignment(.trailing) + } + Picker("Action", selection: self.$rule.action) { + ForEach(NEOnDemandRuleAction.allCases, id: \.self) { + Text($0.description) + } + } + } + + Section( + header: Text("Interface Type Match"), + footer: Text("If the current primary network interface is of this type and all of the other conditions in the rule match, then the rule matches.") + ) { + Picker("Interface Type", selection: self.$rule.interfaceType) { + ForEach(NEOnDemandRuleInterfaceType.allCases, id: \.self) { + Text($0.description) + } + } + } + + if self.rule.interfaceType.ssidIsUsed { + Section( + header: EditButton() + .foregroundColor(.accentColor) + .frame(maxWidth: .infinity, alignment: .trailing) + .overlay(Text("SSID Match"), alignment: .leading), + footer: Text("If the Service Set Identifier (SSID) of the current primary connected network matches one of the strings in this array and all of the other conditions in the rule match, then the rule matches.") + ) { + ForEach(0..