mirror of
https://github.com/yggdrasil-network/yggdrasil-ios.git
synced 2025-04-28 06:05:09 +03:00
Tweaks
This commit is contained in:
parent
0bea3a1613
commit
1b9e84d056
9 changed files with 234 additions and 137 deletions
|
@ -32,9 +32,14 @@ class PlatformItemSource: NSObject {}
|
|||
|
||||
class ConfigurationProxy: PlatformItemSource {
|
||||
private var manager: NETunnelProviderManager?
|
||||
private var json: Data? = nil
|
||||
private var json: Data? = nil {
|
||||
didSet {
|
||||
summary = MobileSummaryFromConfig(json)
|
||||
}
|
||||
}
|
||||
private var dict: [String: Any]? = [:]
|
||||
private var timer: Timer?
|
||||
public var summary: MobileParsedConfig?
|
||||
|
||||
init(manager: NETunnelProviderManager) {
|
||||
self.manager = manager
|
||||
|
@ -46,11 +51,11 @@ class ConfigurationProxy: PlatformItemSource {
|
|||
} catch {
|
||||
NSLog("ConfigurationProxy: Error deserialising JSON (\(error))")
|
||||
}
|
||||
#if os(iOS)
|
||||
#if os(iOS)
|
||||
self.set("name", inSection: "NodeInfo", to: UIDevice.current.name)
|
||||
#elseif os(OSX)
|
||||
#elseif os(OSX)
|
||||
self.set("name", inSection: "NodeInfo", to: Host.current().localizedName ?? "")
|
||||
#endif
|
||||
#endif
|
||||
self.fix()
|
||||
}
|
||||
|
||||
|
@ -303,25 +308,25 @@ class ConfigurationProxy: PlatformItemSource {
|
|||
wifirule.interfaceTypeMatch = .any
|
||||
rules.insert(wifirule, at: 0)
|
||||
}
|
||||
#if os(macOS)
|
||||
#if os(macOS)
|
||||
if self.get("Ethernet", inSection: "AutoStart") as? Bool ?? false {
|
||||
let wifirule = NEOnDemandRuleConnect()
|
||||
wifirule.interfaceTypeMatch = .ethernet
|
||||
rules.insert(wifirule, at: 0)
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
if self.get("WiFi", inSection: "AutoStart") as? Bool ?? false {
|
||||
let wifirule = NEOnDemandRuleConnect()
|
||||
wifirule.interfaceTypeMatch = .wiFi
|
||||
rules.insert(wifirule, at: 0)
|
||||
}
|
||||
#if canImport(UIKit)
|
||||
#if canImport(UIKit)
|
||||
if self.get("Mobile", inSection: "AutoStart") as? Bool ?? false {
|
||||
let mobilerule = NEOnDemandRuleConnect()
|
||||
mobilerule.interfaceTypeMatch = .cellular
|
||||
rules.insert(mobilerule, at: 0)
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
manager.onDemandRules = rules
|
||||
manager.isOnDemandEnabled = rules.count > 1
|
||||
providerProtocol.disconnectOnSleep = rules.count > 1
|
||||
|
@ -346,7 +351,7 @@ class ConfigurationProxy: PlatformItemSource {
|
|||
self.json = try JSONSerialization.data(withJSONObject: self.dict as Any, options: .prettyPrinted)
|
||||
}
|
||||
|
||||
#if canImport(UIKit)
|
||||
#if canImport(UIKit)
|
||||
override func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
|
||||
return "yggdrasil.conf"
|
||||
}
|
||||
|
@ -361,5 +366,5 @@ class ConfigurationProxy: PlatformItemSource {
|
|||
}
|
||||
return "yggdrasil.conf.json"
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -108,11 +108,12 @@ class CrossPlatformAppDelegate: PlatformAppDelegate, ObservableObject {
|
|||
func becameBackground() {}
|
||||
|
||||
func updateStatus(conn: NEVPNConnection) {
|
||||
if conn.status == .connected {
|
||||
if conn.status == .connected || conn.status == .connecting {
|
||||
self.yggdrasilEnabled = true
|
||||
self.requestSummaryIPC()
|
||||
} else if conn.status == .disconnecting || conn.status == .disconnected {
|
||||
self.clearStatus()
|
||||
self.yggdrasilEnabled = false
|
||||
self.clearStatus()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -188,6 +189,8 @@ class CrossPlatformAppDelegate: PlatformAppDelegate, ObservableObject {
|
|||
self.yggdrasilPublicKey = summary.publicKey
|
||||
self.yggdrasilPeers = summary.peers
|
||||
self.yggdrasilConnected = summary.peers.filter { $0.up }.count > 0
|
||||
|
||||
print("Response: \(String(data: js, encoding: .utf8))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,9 @@ struct YggdrasilPeer: Codable, Identifiable {
|
|||
let key: String?
|
||||
let priority: UInt8
|
||||
let cost: UInt16?
|
||||
let rxBytes: Double?
|
||||
let txBytes: Double?
|
||||
let uptime: Int64?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case remote = "URI"
|
||||
|
@ -40,6 +43,9 @@ struct YggdrasilPeer: Codable, Identifiable {
|
|||
case key = "Key"
|
||||
case priority = "Priority"
|
||||
case cost = "Cost"
|
||||
case rxBytes = "RXBytes"
|
||||
case txBytes = "TXBytes"
|
||||
case uptime = "Uptime"
|
||||
}
|
||||
|
||||
public func getStatusBadgeColor() -> SwiftUI.Color {
|
||||
|
|
|
@ -99,6 +99,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||
switch request {
|
||||
case "summary":
|
||||
let pj = self.yggdrasil.getPeersJSON()
|
||||
NSLog("JSON: \(pj)")
|
||||
var peers: [YggdrasilPeer] = []
|
||||
do {
|
||||
peers = try JSONDecoder().decode(
|
||||
|
|
|
@ -5,34 +5,8 @@
|
|||
<key>AvailableLibraries</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>LibraryIdentifier</key>
|
||||
<string>macos-arm64_x86_64</string>
|
||||
<key>LibraryPath</key>
|
||||
<string>Yggdrasil.framework</string>
|
||||
<key>SupportedArchitectures</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
<string>x86_64</string>
|
||||
</array>
|
||||
<key>SupportedPlatform</key>
|
||||
<string>macos</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>LibraryIdentifier</key>
|
||||
<string>ios-arm64_x86_64-maccatalyst</string>
|
||||
<key>LibraryPath</key>
|
||||
<string>Yggdrasil.framework</string>
|
||||
<key>SupportedArchitectures</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
<string>x86_64</string>
|
||||
</array>
|
||||
<key>SupportedPlatform</key>
|
||||
<string>ios</string>
|
||||
<key>SupportedPlatformVariant</key>
|
||||
<string>maccatalyst</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>BinaryPath</key>
|
||||
<string>Yggdrasil.framework/Versions/A/Yggdrasil</string>
|
||||
<key>LibraryIdentifier</key>
|
||||
<string>ios-arm64</string>
|
||||
<key>LibraryPath</key>
|
||||
|
@ -45,6 +19,8 @@
|
|||
<string>ios</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>BinaryPath</key>
|
||||
<string>Yggdrasil.framework/Versions/A/Yggdrasil</string>
|
||||
<key>LibraryIdentifier</key>
|
||||
<string>ios-arm64_x86_64-simulator</string>
|
||||
<key>LibraryPath</key>
|
||||
|
@ -59,6 +35,21 @@
|
|||
<key>SupportedPlatformVariant</key>
|
||||
<string>simulator</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>BinaryPath</key>
|
||||
<string>Yggdrasil.framework/Versions/A/Yggdrasil</string>
|
||||
<key>LibraryIdentifier</key>
|
||||
<string>macos-arm64_x86_64</string>
|
||||
<key>LibraryPath</key>
|
||||
<string>Yggdrasil.framework</string>
|
||||
<key>SupportedArchitectures</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
<string>x86_64</string>
|
||||
</array>
|
||||
<key>SupportedPlatform</key>
|
||||
<string>macos</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XFWK</string>
|
||||
|
|
|
@ -12,11 +12,11 @@ import NetworkExtension
|
|||
struct Application: App {
|
||||
@State private var selection: String? = "Status"
|
||||
|
||||
#if os(iOS)
|
||||
#if os(iOS)
|
||||
@UIApplicationDelegateAdaptor(CrossPlatformAppDelegate.self) static var appDelegate: CrossPlatformAppDelegate
|
||||
#elseif os(macOS)
|
||||
#elseif os(macOS)
|
||||
@NSApplicationDelegateAdaptor(CrossPlatformAppDelegate.self) static var appDelegate: CrossPlatformAppDelegate
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
|
||||
|
@ -53,14 +53,14 @@ struct Application: App {
|
|||
//.listStyle(.sidebar)
|
||||
//.navigationSplitViewColumnWidth(200)
|
||||
|
||||
Image("YggdrasilLogo")
|
||||
/*Image("YggdrasilLogo")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.foregroundColor(.primary)
|
||||
.opacity(0.1)
|
||||
.opacity(0.05)
|
||||
.frame(maxWidth: 200, alignment: .bottom)
|
||||
.padding(.all, 24)
|
||||
.padding(.all, 24)*/
|
||||
}
|
||||
.navigationSplitViewColumnWidth(200)
|
||||
.listStyle(.sidebar)
|
||||
|
@ -82,8 +82,8 @@ struct Application: App {
|
|||
}
|
||||
}
|
||||
}
|
||||
#if os(macOS)
|
||||
#if os(macOS)
|
||||
.windowStyle(.hiddenTitleBar)
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ struct PeersView: View {
|
|||
Section(content: {
|
||||
ForEach(Array(appDelegate.yggdrasilConfig.peers.enumerated()), id: \.offset) { index, peer in
|
||||
HStack() {
|
||||
TextField("Peer", text: $appDelegate.yggdrasilConfig.peers[index])
|
||||
TextField("tls://host:port", text: $appDelegate.yggdrasilConfig.peers[index])
|
||||
.labelsHidden()
|
||||
#if os(iOS)
|
||||
.disabled(!editMode!.wrappedValue.isEditing)
|
||||
|
@ -75,8 +75,18 @@ struct PeersView: View {
|
|||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
TextField("Multicast password", text: $appDelegate.yggdrasilConfig.multicastPassword)
|
||||
.labelStyle(.titleAndIcon)
|
||||
VStack {
|
||||
HStack {
|
||||
Text("Multicast password")
|
||||
TextField("None", text: $appDelegate.yggdrasilConfig.multicastPassword)
|
||||
.labelsHidden()
|
||||
.multilineTextAlignment(.trailing)
|
||||
}
|
||||
Text("If provided, this device will only automatically peer with other nodes that share the same password.")
|
||||
.font(.system(size: 11))
|
||||
.foregroundColor(.gray)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}, header: {
|
||||
Text("Local connectivity")
|
||||
})
|
||||
|
@ -85,9 +95,11 @@ struct PeersView: View {
|
|||
.navigationTitle("Peers")
|
||||
#if os(iOS)
|
||||
.toolbar {
|
||||
if editMode!.wrappedValue.isEditing {
|
||||
Button("Add", systemImage: "plus") {
|
||||
appDelegate.yggdrasilConfig.peers.append("")
|
||||
}
|
||||
}
|
||||
EditButton()
|
||||
.onChange(of: editMode!.wrappedValue) { edit in
|
||||
if !edit.isEditing {
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
import SwiftUI
|
||||
|
||||
struct SettingsView: View {
|
||||
//@Binding public var yggdrasilConfiguration: ConfigurationProxy
|
||||
@ObservedObject private var appDelegate = Application.appDelegate
|
||||
|
||||
@State private var deviceName = ""
|
||||
|
@ -81,9 +80,9 @@ struct SettingsView: View {
|
|||
}
|
||||
.formStyle(.grouped)
|
||||
.navigationTitle("Settings")
|
||||
#if os(iOS)
|
||||
#if os(iOS)
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CoreImage.CIFilterBuiltins
|
||||
|
||||
#if os(iOS)
|
||||
typealias MyListStyle = DefaultListStyle
|
||||
|
@ -18,17 +19,20 @@ struct StatusView: View {
|
|||
|
||||
@State private var statusBadgeColor: SwiftUI.Color = .gray
|
||||
@State private var statusBadgeText: String = "Not enabled"
|
||||
@State private var showingPublicKeyPopover = false
|
||||
|
||||
let context = CIContext()
|
||||
let filter = CIFilter.qrCodeGenerator()
|
||||
|
||||
private func getStatusBadgeColor() -> SwiftUI.Color {
|
||||
if !appDelegate.yggdrasilSupported {
|
||||
return .gray
|
||||
} else if appDelegate.yggdrasilConnected {
|
||||
if appDelegate.yggdrasilConnected {
|
||||
return .green
|
||||
} else if appDelegate.yggdrasilEnabled {
|
||||
return .yellow
|
||||
}
|
||||
} else {
|
||||
return .gray
|
||||
}
|
||||
}
|
||||
|
||||
private func getStatusBadgeText() -> String {
|
||||
if !appDelegate.yggdrasilSupported {
|
||||
|
@ -42,6 +46,40 @@ struct StatusView: View {
|
|||
}
|
||||
}
|
||||
|
||||
func formatBytes(bytes: Double) -> String {
|
||||
guard bytes > 0 else {
|
||||
return "N/A"
|
||||
}
|
||||
|
||||
// Adapted from http://stackoverflow.com/a/18650828
|
||||
let suffixes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
|
||||
let k: Double = 1024
|
||||
let i = floor(log(bytes) / log(k))
|
||||
|
||||
// Format number with thousands separator and everything below 1 GB with no decimal places.
|
||||
let numberFormatter = NumberFormatter()
|
||||
numberFormatter.maximumFractionDigits = i < 3 ? 0 : 1
|
||||
numberFormatter.numberStyle = .decimal
|
||||
|
||||
let numberString = numberFormatter.string(from: NSNumber(value: bytes / pow(k, i))) ?? "Unknown"
|
||||
let suffix = suffixes[Int(i)]
|
||||
return "\(numberString) \(suffix)"
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
func generateQRCode(from string: String) -> UIImage {
|
||||
filter.message = Data(string.utf8)
|
||||
|
||||
if let outputImage = filter.outputImage {
|
||||
if let cgImage = context.createCGImage(outputImage, from: outputImage.extent) {
|
||||
return UIImage(cgImage: cgImage)
|
||||
}
|
||||
}
|
||||
|
||||
return UIImage(systemName: "xmark.circle") ?? UIImage()
|
||||
}
|
||||
#endif
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section(content: {
|
||||
|
@ -125,6 +163,25 @@ struct StatusView: View {
|
|||
.truncationMode(.tail)
|
||||
.lineLimit(1)
|
||||
.textSelection(.enabled)
|
||||
#if os(iOS)
|
||||
if appDelegate.yggdrasilPublicKey != "N/A" {
|
||||
Button("QR Code", systemImage: "qrcode", action: {
|
||||
showingPublicKeyPopover = true
|
||||
})
|
||||
.labelStyle(.iconOnly)
|
||||
.popover(isPresented: $showingPublicKeyPopover) {
|
||||
Text("Public Key")
|
||||
.font(.headline)
|
||||
.padding()
|
||||
Image(uiImage: generateQRCode(from: "\(appDelegate.yggdrasilPublicKey)"))
|
||||
.interpolation(.none)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 200, height: 200)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}, header: {
|
||||
Text("Details")
|
||||
|
@ -146,25 +203,48 @@ struct StatusView: View {
|
|||
return a.remote < b.remote
|
||||
}), id: \.remote) { peer in
|
||||
VStack {
|
||||
HStack {
|
||||
Text(peer.remote)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.truncationMode(.tail)
|
||||
.lineLimit(1)
|
||||
.textSelection(.enabled)
|
||||
.padding(.bottom, 2)
|
||||
}
|
||||
HStack {
|
||||
Image(systemName: "circlebadge.fill")
|
||||
.foregroundColor(peer.getStatusBadgeColor())
|
||||
.onChange(of: peer.up) { newValue in
|
||||
statusBadgeColor = peer.getStatusBadgeColor()
|
||||
}
|
||||
Text(peer.up ? peer.address ?? "Unknown IP address" : "Not connected")
|
||||
Text(peer.up ? "Connected" : "Not connected")
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundColor(Color.gray)
|
||||
.font(.system(size: 11))
|
||||
.truncationMode(.tail)
|
||||
.lineLimit(1)
|
||||
.textSelection(.enabled)
|
||||
if peer.up {
|
||||
Spacer()
|
||||
if let uptime = peer.uptime {
|
||||
Label(Duration(secondsComponent: uptime/1000000000, attosecondsComponent: 0).formatted(), systemImage: "clock")
|
||||
.font(.system(size: 11))
|
||||
.labelStyle(.titleAndIcon)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
if let rxBytes = peer.rxBytes {
|
||||
Label(formatBytes(bytes: rxBytes), systemImage: "arrowshape.down")
|
||||
.font(.system(size: 11))
|
||||
.labelStyle(.titleAndIcon)
|
||||
.foregroundStyle(.teal)
|
||||
}
|
||||
if let txBytes = peer.txBytes {
|
||||
Label(formatBytes(bytes: txBytes), systemImage: "arrowshape.up")
|
||||
.font(.system(size: 11))
|
||||
.labelStyle(.titleAndIcon)
|
||||
.foregroundStyle(.purple)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.all, 2)
|
||||
|
@ -178,9 +258,9 @@ struct StatusView: View {
|
|||
}
|
||||
.formStyle(.grouped)
|
||||
.navigationTitle("Yggdrasil")
|
||||
#if os(iOS)
|
||||
#if os(iOS)
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue