Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -2,7 +2,7 @@ import Foundation
|
|||||||
|
|
||||||
let launchdLabel = "com.steipete.clawdis"
|
let launchdLabel = "com.steipete.clawdis"
|
||||||
let onboardingVersionKey = "clawdis.onboardingVersion"
|
let onboardingVersionKey = "clawdis.onboardingVersion"
|
||||||
let currentOnboardingVersion = 3
|
let currentOnboardingVersion = 4
|
||||||
let pauseDefaultsKey = "clawdis.pauseEnabled"
|
let pauseDefaultsKey = "clawdis.pauseEnabled"
|
||||||
let iconAnimationsEnabledKey = "clawdis.iconAnimationsEnabled"
|
let iconAnimationsEnabledKey = "clawdis.iconAnimationsEnabled"
|
||||||
let swabbleEnabledKey = "clawdis.swabbleEnabled"
|
let swabbleEnabledKey = "clawdis.swabbleEnabled"
|
||||||
|
|||||||
@@ -125,18 +125,9 @@ struct GeneralSettings: View {
|
|||||||
TextField("user@host[:22]", text: self.$state.remoteTarget)
|
TextField("user@host[:22]", text: self.$state.remoteTarget)
|
||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
Menu {
|
MasterDiscoveryMenu(discovery: self.masterDiscovery) { master in
|
||||||
if self.masterDiscovery.masters.isEmpty {
|
self.applyDiscoveredMaster(master)
|
||||||
Button(self.masterDiscovery.statusText) {}.disabled(true)
|
|
||||||
} else {
|
|
||||||
ForEach(self.masterDiscovery.masters) { master in
|
|
||||||
Button(master.displayName) { self.applyDiscoveredMaster(master) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "dot.radiowaves.left.and.right")
|
|
||||||
}
|
}
|
||||||
.help("Discover Clawdis masters on your LAN")
|
|
||||||
Button {
|
Button {
|
||||||
Task { await self.testRemote() }
|
Task { await self.testRemote() }
|
||||||
} label: {
|
} label: {
|
||||||
|
|||||||
23
apps/macos/Sources/Clawdis/MasterDiscoveryMenu.swift
Normal file
23
apps/macos/Sources/Clawdis/MasterDiscoveryMenu.swift
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct MasterDiscoveryMenu: View {
|
||||||
|
@ObservedObject var discovery: MasterDiscoveryModel
|
||||||
|
var onSelect: (MasterDiscoveryModel.DiscoveredMaster) -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Menu {
|
||||||
|
if self.discovery.masters.isEmpty {
|
||||||
|
Button(self.discovery.statusText) {}
|
||||||
|
.disabled(true)
|
||||||
|
} else {
|
||||||
|
ForEach(self.discovery.masters) { master in
|
||||||
|
Button(master.displayName) { self.onSelect(master) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "dot.radiowaves.left.and.right")
|
||||||
|
}
|
||||||
|
.help("Discover Clawdis masters on your LAN")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -44,16 +44,19 @@ struct OnboardingView: View {
|
|||||||
@State private var cliStatus: String?
|
@State private var cliStatus: String?
|
||||||
@State private var copied = false
|
@State private var copied = false
|
||||||
@State private var monitoringPermissions = false
|
@State private var monitoringPermissions = false
|
||||||
|
@State private var monitoringDiscovery = false
|
||||||
@State private var cliInstalled = false
|
@State private var cliInstalled = false
|
||||||
@State private var cliInstallLocation: String?
|
@State private var cliInstallLocation: String?
|
||||||
@State private var gatewayStatus: GatewayEnvironmentStatus = .checking
|
@State private var gatewayStatus: GatewayEnvironmentStatus = .checking
|
||||||
@State private var gatewayInstalling = false
|
@State private var gatewayInstalling = false
|
||||||
@State private var gatewayInstallMessage: String?
|
@State private var gatewayInstallMessage: String?
|
||||||
|
@StateObject private var masterDiscovery = MasterDiscoveryModel()
|
||||||
@ObservedObject private var state = AppStateStore.shared
|
@ObservedObject private var state = AppStateStore.shared
|
||||||
@ObservedObject private var permissionMonitor = PermissionMonitor.shared
|
@ObservedObject private var permissionMonitor = PermissionMonitor.shared
|
||||||
|
|
||||||
private let pageWidth: CGFloat = 680
|
private let pageWidth: CGFloat = 680
|
||||||
private let contentHeight: CGFloat = 520
|
private let contentHeight: CGFloat = 520
|
||||||
|
private let connectionPageIndex = 1
|
||||||
private let permissionsPageIndex = 3
|
private let permissionsPageIndex = 3
|
||||||
private var pageCount: Int { 7 }
|
private var pageCount: Int { 7 }
|
||||||
private var buttonTitle: String { self.currentPage == self.pageCount - 1 ? "Finish" : "Next" }
|
private var buttonTitle: String { self.currentPage == self.pageCount - 1 ? "Finish" : "Next" }
|
||||||
@@ -91,12 +94,18 @@ struct OnboardingView: View {
|
|||||||
.background(Color(NSColor.windowBackgroundColor))
|
.background(Color(NSColor.windowBackgroundColor))
|
||||||
.onAppear {
|
.onAppear {
|
||||||
self.currentPage = 0
|
self.currentPage = 0
|
||||||
self.updatePermissionMonitoring(for: 0)
|
self.updateMonitoring(for: 0)
|
||||||
}
|
}
|
||||||
.onChange(of: self.currentPage) { _, newValue in
|
.onChange(of: self.currentPage) { _, newValue in
|
||||||
self.updatePermissionMonitoring(for: newValue)
|
self.updateMonitoring(for: newValue)
|
||||||
|
}
|
||||||
|
.onChange(of: self.state.connectionMode) { _, _ in
|
||||||
|
self.updateDiscoveryMonitoring(for: self.currentPage)
|
||||||
|
}
|
||||||
|
.onDisappear {
|
||||||
|
self.stopPermissionMonitoring()
|
||||||
|
self.stopDiscovery()
|
||||||
}
|
}
|
||||||
.onDisappear { self.stopPermissionMonitoring() }
|
|
||||||
.task {
|
.task {
|
||||||
await self.refreshPerms()
|
await self.refreshPerms()
|
||||||
self.refreshCLIStatus()
|
self.refreshCLIStatus()
|
||||||
@@ -145,9 +154,14 @@ struct OnboardingView: View {
|
|||||||
if self.state.connectionMode == .remote {
|
if self.state.connectionMode == .remote {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
LabeledContent("SSH target") {
|
LabeledContent("SSH target") {
|
||||||
TextField("user@host[:22]", text: self.$state.remoteTarget)
|
HStack(spacing: 8) {
|
||||||
.textFieldStyle(.roundedBorder)
|
TextField("user@host[:22]", text: self.$state.remoteTarget)
|
||||||
.frame(width: 300)
|
.textFieldStyle(.roundedBorder)
|
||||||
|
.frame(width: 300)
|
||||||
|
MasterDiscoveryMenu(discovery: self.masterDiscovery) { master in
|
||||||
|
self.applyDiscoveredMaster(master)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DisclosureGroup("Advanced") {
|
DisclosureGroup("Advanced") {
|
||||||
@@ -256,6 +270,17 @@ struct OnboardingView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func applyDiscoveredMaster(_ master: MasterDiscoveryModel.DiscoveredMaster) {
|
||||||
|
let host = master.tailnetDns ?? master.lanHost
|
||||||
|
guard let host else { return }
|
||||||
|
let user = NSUserName()
|
||||||
|
var target = "\(user)@\(host)"
|
||||||
|
if master.sshPort != 22 {
|
||||||
|
target += ":\(master.sshPort)"
|
||||||
|
}
|
||||||
|
self.state.remoteTarget = target
|
||||||
|
}
|
||||||
|
|
||||||
private func permissionsPage() -> some View {
|
private func permissionsPage() -> some View {
|
||||||
self.onboardingPage {
|
self.onboardingPage {
|
||||||
Text("Grant permissions")
|
Text("Grant permissions")
|
||||||
@@ -549,12 +574,35 @@ struct OnboardingView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateDiscoveryMonitoring(for pageIndex: Int) {
|
||||||
|
let isConnectionPage = pageIndex == self.connectionPageIndex
|
||||||
|
let shouldMonitor = isConnectionPage && self.state.connectionMode == .remote
|
||||||
|
if shouldMonitor, !self.monitoringDiscovery {
|
||||||
|
self.monitoringDiscovery = true
|
||||||
|
self.masterDiscovery.start()
|
||||||
|
} else if !shouldMonitor, self.monitoringDiscovery {
|
||||||
|
self.monitoringDiscovery = false
|
||||||
|
self.masterDiscovery.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateMonitoring(for pageIndex: Int) {
|
||||||
|
self.updatePermissionMonitoring(for: pageIndex)
|
||||||
|
self.updateDiscoveryMonitoring(for: pageIndex)
|
||||||
|
}
|
||||||
|
|
||||||
private func stopPermissionMonitoring() {
|
private func stopPermissionMonitoring() {
|
||||||
guard self.monitoringPermissions else { return }
|
guard self.monitoringPermissions else { return }
|
||||||
self.monitoringPermissions = false
|
self.monitoringPermissions = false
|
||||||
PermissionMonitor.shared.unregister()
|
PermissionMonitor.shared.unregister()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func stopDiscovery() {
|
||||||
|
guard self.monitoringDiscovery else { return }
|
||||||
|
self.monitoringDiscovery = false
|
||||||
|
self.masterDiscovery.stop()
|
||||||
|
}
|
||||||
|
|
||||||
private func installCLI() async {
|
private func installCLI() async {
|
||||||
guard !self.installingCLI else { return }
|
guard !self.installingCLI else { return }
|
||||||
self.installingCLI = true
|
self.installingCLI = true
|
||||||
|
|||||||
Reference in New Issue
Block a user