This commit is contained in:
Neil Alexander 2024-06-23 10:54:21 +01:00
parent 0bea3a1613
commit 1b9e84d056
No known key found for this signature in database
GPG key ID: A02A2019A2BB0944
9 changed files with 234 additions and 137 deletions

View file

@ -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")
.renderingMode(.template)
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(.primary)
.opacity(0.1)
.frame(maxWidth: 200, alignment: .bottom)
.padding(.all, 24)
/*Image("YggdrasilLogo")
.renderingMode(.template)
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(.primary)
.opacity(0.05)
.frame(maxWidth: 200, alignment: .bottom)
.padding(.all, 24)*/
}
.navigationSplitViewColumnWidth(200)
.listStyle(.sidebar)
@ -82,8 +82,8 @@ struct Application: App {
}
}
}
#if os(macOS)
#if os(macOS)
.windowStyle(.hiddenTitleBar)
#endif
#endif
}
}

View file

@ -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,8 +95,10 @@ struct PeersView: View {
.navigationTitle("Peers")
#if os(iOS)
.toolbar {
Button("Add", systemImage: "plus") {
appDelegate.yggdrasilConfig.peers.append("")
if editMode!.wrappedValue.isEditing {
Button("Add", systemImage: "plus") {
appDelegate.yggdrasilConfig.peers.append("")
}
}
EditButton()
.onChange(of: editMode!.wrappedValue) { edit in

View file

@ -8,7 +8,6 @@
import SwiftUI
struct SettingsView: View {
//@Binding public var yggdrasilConfiguration: ConfigurationProxy
@ObservedObject private var appDelegate = Application.appDelegate
@State private var deviceName = ""
@ -35,55 +34,55 @@ struct SettingsView: View {
})
/*
Section(content: {
VStack(alignment: .leading) {
Button("Import configuration") {
}
#if os(macOS)
.buttonStyle(.link)
#endif
.foregroundColor(.accentColor)
Text("Import configuration from another device, including the public key and Yggdrasil IP address.")
.font(.system(size: 11))
.foregroundColor(.gray)
}
VStack(alignment: .leading) {
Button("Export configuration") {
}
#if os(macOS)
.buttonStyle(.link)
#endif
.foregroundColor(.accentColor)
Text("Configuration will be exported as a file. Your configuration contains your private key which is extremely sensitive. Do not share it with anyone.")
.font(.system(size: 11))
.foregroundColor(.gray)
}
VStack(alignment: .leading) {
Button("Reset configuration") {
}
#if os(macOS)
.buttonStyle(.link)
#endif
.foregroundColor(.red)
Text("Resetting will overwrite with newly generated configuration. Your public key and Yggdrasil IP address will change.")
.font(.system(size: 11))
.foregroundColor(.gray)
}
}, header: {
Text("Configuration")
})
Section(content: {
VStack(alignment: .leading) {
Button("Import configuration") {
}
#if os(macOS)
.buttonStyle(.link)
#endif
.foregroundColor(.accentColor)
Text("Import configuration from another device, including the public key and Yggdrasil IP address.")
.font(.system(size: 11))
.foregroundColor(.gray)
}
VStack(alignment: .leading) {
Button("Export configuration") {
}
#if os(macOS)
.buttonStyle(.link)
#endif
.foregroundColor(.accentColor)
Text("Configuration will be exported as a file. Your configuration contains your private key which is extremely sensitive. Do not share it with anyone.")
.font(.system(size: 11))
.foregroundColor(.gray)
}
VStack(alignment: .leading) {
Button("Reset configuration") {
}
#if os(macOS)
.buttonStyle(.link)
#endif
.foregroundColor(.red)
Text("Resetting will overwrite with newly generated configuration. Your public key and Yggdrasil IP address will change.")
.font(.system(size: 11))
.foregroundColor(.gray)
}
}, header: {
Text("Configuration")
})
*/
}
.formStyle(.grouped)
.navigationTitle("Settings")
#if os(iOS)
#if os(iOS)
.navigationBarTitleDisplayMode(.large)
#endif
#endif
}
}

View file

@ -6,6 +6,7 @@
//
import SwiftUI
import CoreImage.CIFilterBuiltins
#if os(iOS)
typealias MyListStyle = DefaultListStyle
@ -18,16 +19,19 @@ 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
}
return .gray
}
private func getStatusBadgeText() -> String {
@ -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 {
Text(peer.remote)
.frame(maxWidth: .infinity, alignment: .leading)
.truncationMode(.tail)
.lineLimit(1)
.textSelection(.enabled)
.padding(.bottom, 2)
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
}
}