Ensure tailscale can be disabled when not running

This commit is contained in:
Peter Steinberger 2025-08-01 00:45:22 +02:00
parent 8814f2623f
commit 459cf52ef6
2 changed files with 160 additions and 82 deletions

View file

@ -311,7 +311,59 @@ private struct TailscaleIntegrationSection: View {
.buttonStyle(.link) .buttonStyle(.link)
.controlSize(.small) .controlSize(.small)
} }
} else if tailscaleService.isRunning { } else if !tailscaleService.isRunning {
// Show Tailscale preferences even when not running
VStack(alignment: .leading, spacing: 12) {
// Tailscale Serve toggle - always available when installed
HStack {
Toggle("Enable Tailscale Serve Integration", isOn: $tailscaleServeEnabled)
.onChange(of: tailscaleServeEnabled) { _, newValue in
logger.info("Tailscale Serve integration \(newValue ? "enabled" : "disabled")")
// Restart server to apply the new setting
Task {
await serverManager.restart()
}
}
Spacer()
// Show status when enabled but not running
if tailscaleServeEnabled {
HStack(spacing: 4) {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundColor(.orange)
Text("Tailscale not running")
.font(.caption)
.foregroundColor(.orange)
}
.frame(height: 16)
}
}
// Show action button to start Tailscale
if tailscaleService.isInstalled && !tailscaleService.isRunning {
Button(action: {
tailscaleService.openTailscaleApp()
}, label: {
HStack(spacing: 4) {
Image(systemName: "play.circle")
Text("Start Tailscale")
}
})
.buttonStyle(.borderedProminent)
.controlSize(.small)
}
// Show help text about what will happen when enabled
if tailscaleServeEnabled {
Text("Tailscale Serve will activate automatically when Tailscale is running.")
.font(.caption)
.foregroundColor(.secondary)
}
}
} else {
// Tailscale is running - show full interface
VStack(alignment: .leading, spacing: 12) {
// Tailscale Serve toggle // Tailscale Serve toggle
HStack { HStack {
Toggle("Enable Tailscale Serve Integration", isOn: $tailscaleServeEnabled) Toggle("Enable Tailscale Serve Integration", isOn: $tailscaleServeEnabled)
@ -410,6 +462,7 @@ private struct TailscaleIntegrationSection: View {
} }
} }
} }
}
} header: { } header: {
Text("Tailscale Integration") Text("Tailscale Integration")
.font(.headline) .font(.headline)

View file

@ -88,6 +88,11 @@ struct AccessModeView: View {
@AppStorage(AppConstants.UserDefaultsKeys.tailscaleServeEnabled) @AppStorage(AppConstants.UserDefaultsKeys.tailscaleServeEnabled)
private var tailscaleServeEnabled = false private var tailscaleServeEnabled = false
@Environment(TailscaleService.self)
private var tailscaleService
@Environment(TailscaleServeStatusService.self)
private var tailscaleServeStatus
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
HStack { HStack {
@ -95,14 +100,14 @@ struct AccessModeView: View {
.font(.callout) .font(.callout)
Spacer() Spacer()
if tailscaleServeEnabled { if shouldLockToLocalhost {
// When Tailscale Serve is enabled, force localhost mode // Only lock when Tailscale Serve is actually working
Text("Localhost") Text("Localhost")
.foregroundColor(.secondary) .foregroundColor(.secondary)
Image(systemName: "lock.shield.fill") Image(systemName: "lock.shield.fill")
.foregroundColor(.blue) .foregroundColor(.blue)
.help("Tailscale Serve requires localhost binding for security") .help("Tailscale Serve active - locked to localhost for security")
} else { } else {
Picker("", selection: $accessModeString) { Picker("", selection: $accessModeString) {
ForEach(DashboardAccessMode.allCases, id: \.rawValue) { mode in ForEach(DashboardAccessMode.allCases, id: \.rawValue) { mode in
@ -117,7 +122,20 @@ struct AccessModeView: View {
} }
} }
if tailscaleServeEnabled && accessMode == .network { // Show warning when Tailscale Serve is enabled but not working
if tailscaleServeEnabled && !shouldLockToLocalhost {
HStack(spacing: 4) {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundColor(.orange)
.font(.caption)
Text("Tailscale Serve enabled but not active - using selected access mode")
.font(.caption)
.foregroundColor(.secondary)
}
}
// Show info when Tailscale Serve is active and locked
if shouldLockToLocalhost && accessMode == .network {
HStack(spacing: 4) { HStack(spacing: 4) {
Image(systemName: "info.circle.fill") Image(systemName: "info.circle.fill")
.foregroundColor(.blue) .foregroundColor(.blue)
@ -129,6 +147,13 @@ struct AccessModeView: View {
} }
} }
} }
/// Only lock to localhost when Tailscale Serve is enabled AND actually working
private var shouldLockToLocalhost: Bool {
tailscaleServeEnabled &&
tailscaleService.isRunning &&
tailscaleServeStatus.isRunning
}
} }
// MARK: - Port Configuration View // MARK: - Port Configuration View