mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
Shuffle settings around
This commit is contained in:
parent
31f1d625b1
commit
c16ccacf30
4 changed files with 151 additions and 171 deletions
|
|
@ -6,6 +6,8 @@ struct AdvancedSettingsView: View {
|
||||||
private var debugMode = false
|
private var debugMode = false
|
||||||
@AppStorage("cleanupOnStartup")
|
@AppStorage("cleanupOnStartup")
|
||||||
private var cleanupOnStartup = true
|
private var cleanupOnStartup = true
|
||||||
|
@AppStorage("showInDock")
|
||||||
|
private var showInDock = false
|
||||||
@State private var cliInstaller = CLIInstaller()
|
@State private var cliInstaller = CLIInstaller()
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
@ -70,13 +72,21 @@ struct AdvancedSettingsView: View {
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
} header: {
|
|
||||||
Text("Advanced")
|
|
||||||
.font(.headline)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug section
|
// Show in Dock
|
||||||
Section {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Toggle("Show in Dock", isOn: showInDockBinding)
|
||||||
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
|
Text("Show VibeTunnel icon in the Dock.")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
Text("The dock icon is always displayed when the Settings dialog is visible.")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug mode toggle
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Toggle("Debug mode", isOn: $debugMode)
|
Toggle("Debug mode", isOn: $debugMode)
|
||||||
Text("Enable additional logging and debugging features.")
|
Text("Enable additional logging and debugging features.")
|
||||||
|
|
@ -84,7 +94,7 @@ struct AdvancedSettingsView: View {
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
} header: {
|
} header: {
|
||||||
Text("Debug")
|
Text("Advanced")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -96,6 +106,17 @@ struct AdvancedSettingsView: View {
|
||||||
cliInstaller.checkInstallationStatus()
|
cliInstaller.checkInstallationStatus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var showInDockBinding: Binding<Bool> {
|
||||||
|
Binding(
|
||||||
|
get: { showInDock },
|
||||||
|
set: { newValue in
|
||||||
|
showInDock = newValue
|
||||||
|
// Don't change activation policy while settings window is open
|
||||||
|
// The change will be applied when the settings window closes
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Terminal Preference Section
|
// MARK: - Terminal Preference Section
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,6 @@ struct DashboardSettingsView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
Form {
|
Form {
|
||||||
PermissionsSection()
|
|
||||||
|
|
||||||
SecuritySection(
|
SecuritySection(
|
||||||
passwordEnabled: $passwordEnabled,
|
passwordEnabled: $passwordEnabled,
|
||||||
password: $password,
|
password: $password,
|
||||||
|
|
@ -825,102 +823,3 @@ private struct NgrokErrorView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Permissions Section
|
|
||||||
|
|
||||||
private struct PermissionsSection: View {
|
|
||||||
@StateObject private var appleScriptManager = AppleScriptPermissionManager.shared
|
|
||||||
@State private var accessibilityUpdateTrigger = 0
|
|
||||||
|
|
||||||
private var hasAccessibilityPermission: Bool {
|
|
||||||
// This will cause a re-read whenever accessibilityUpdateTrigger changes
|
|
||||||
_ = accessibilityUpdateTrigger
|
|
||||||
return AccessibilityPermissionManager.shared.hasPermission()
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Section {
|
|
||||||
VStack(alignment: .leading, spacing: 16) {
|
|
||||||
// Automation permission
|
|
||||||
HStack {
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
|
||||||
Text("Terminal Automation")
|
|
||||||
.font(.body)
|
|
||||||
Text("Required to launch and control terminal applications")
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
if appleScriptManager.hasPermission {
|
|
||||||
HStack {
|
|
||||||
Image(systemName: "checkmark.circle.fill")
|
|
||||||
.foregroundColor(.green)
|
|
||||||
Text("Granted")
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
}
|
|
||||||
.font(.caption)
|
|
||||||
.padding(.horizontal, 10)
|
|
||||||
.padding(.vertical, 2)
|
|
||||||
.frame(height: 22) // Match small button height
|
|
||||||
} else {
|
|
||||||
Button("Grant Permission") {
|
|
||||||
appleScriptManager.requestPermission()
|
|
||||||
}
|
|
||||||
.buttonStyle(.bordered)
|
|
||||||
.controlSize(.small)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
|
||||||
|
|
||||||
// Accessibility permission
|
|
||||||
HStack {
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
|
||||||
Text("Accessibility")
|
|
||||||
.font(.body)
|
|
||||||
Text("Required for terminals that need keystroke input (Ghostty, Warp, Hyper)")
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
if hasAccessibilityPermission {
|
|
||||||
HStack {
|
|
||||||
Image(systemName: "checkmark.circle.fill")
|
|
||||||
.foregroundColor(.green)
|
|
||||||
Text("Granted")
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
}
|
|
||||||
.font(.caption)
|
|
||||||
.padding(.horizontal, 10)
|
|
||||||
.padding(.vertical, 2)
|
|
||||||
.frame(height: 22) // Match small button height
|
|
||||||
} else {
|
|
||||||
Button("Grant Permission") {
|
|
||||||
AccessibilityPermissionManager.shared.requestPermission()
|
|
||||||
}
|
|
||||||
.buttonStyle(.bordered)
|
|
||||||
.controlSize(.small)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} header: {
|
|
||||||
Text("Permissions")
|
|
||||||
.font(.headline)
|
|
||||||
} footer: {
|
|
||||||
Text("Terminal automation is required for all terminals. Accessibility is only needed for terminals that simulate keyboard input.")
|
|
||||||
.font(.caption)
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
}
|
|
||||||
.task {
|
|
||||||
_ = await appleScriptManager.checkPermission()
|
|
||||||
}
|
|
||||||
.onReceive(Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()) { _ in
|
|
||||||
// Force a re-render to check accessibility permission
|
|
||||||
accessibilityUpdateTrigger += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -32,15 +32,12 @@ struct DebugSettingsView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
Form {
|
Form {
|
||||||
HTTPServerSection(
|
ServerSection(
|
||||||
isServerHealthy: isServerHealthy,
|
isServerHealthy: isServerHealthy,
|
||||||
isServerRunning: isServerRunning,
|
isServerRunning: isServerRunning,
|
||||||
serverPort: serverPort,
|
serverPort: serverPort,
|
||||||
lastError: lastError,
|
lastError: lastError,
|
||||||
toggleServer: toggleServer
|
toggleServer: toggleServer,
|
||||||
)
|
|
||||||
|
|
||||||
ServerConfigurationSection(
|
|
||||||
serverModeString: $serverModeString,
|
serverModeString: $serverModeString,
|
||||||
serverManager: serverManager
|
serverManager: serverManager
|
||||||
)
|
)
|
||||||
|
|
@ -269,18 +266,21 @@ struct DebugSettingsView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - HTTP Server Section
|
// MARK: - Server Section
|
||||||
|
|
||||||
private struct HTTPServerSection: View {
|
private struct ServerSection: View {
|
||||||
let isServerHealthy: Bool
|
let isServerHealthy: Bool
|
||||||
let isServerRunning: Bool
|
let isServerRunning: Bool
|
||||||
let serverPort: Int
|
let serverPort: Int
|
||||||
let lastError: String?
|
let lastError: String?
|
||||||
let toggleServer: (Bool) async -> Void
|
let toggleServer: (Bool) async -> Void
|
||||||
|
@Binding var serverModeString: String
|
||||||
|
let serverManager: ServerManager
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Section {
|
Section {
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
|
// Server Status
|
||||||
HStack {
|
HStack {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
HStack {
|
HStack {
|
||||||
|
|
@ -323,29 +323,10 @@ private struct HTTPServerSection: View {
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.red)
|
.foregroundStyle(.red)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.padding(.vertical, 4)
|
|
||||||
} header: {
|
|
||||||
Text("HTTP Server")
|
|
||||||
.font(.headline)
|
|
||||||
} footer: {
|
|
||||||
Text("The HTTP server provides REST API endpoints for terminal session management.")
|
|
||||||
.font(.caption)
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Server Configuration Section
|
Divider()
|
||||||
|
|
||||||
private struct ServerConfigurationSection: View {
|
// Server Mode Configuration
|
||||||
@Binding var serverModeString: String
|
|
||||||
let serverManager: ServerManager
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Section {
|
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("Server Mode")
|
Text("Server Mode")
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
@ -383,11 +364,12 @@ private struct ServerConfigurationSection: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.padding(.vertical, 4)
|
||||||
} header: {
|
} header: {
|
||||||
Text("Server Configuration")
|
Text("HTTP Server")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
} footer: {
|
} footer: {
|
||||||
Text("Choose between the built-in Swift Hummingbird server or the Rust tty-fwd binary.")
|
Text("The HTTP server provides REST API endpoints for terminal session management. Choose between the built-in Swift Hummingbird server or the Rust tty-fwd binary.")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
|
|
@ -559,6 +541,7 @@ private struct DeveloperToolsSection: View {
|
||||||
showServerConsole()
|
showServerConsole()
|
||||||
}
|
}
|
||||||
.buttonStyle(.bordered)
|
.buttonStyle(.bordered)
|
||||||
|
.frame(width: 120)
|
||||||
}
|
}
|
||||||
Text("View real-time server logs from both Hummingbird and Rust servers")
|
Text("View real-time server logs from both Hummingbird and Rust servers")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
|
|
@ -573,6 +556,7 @@ private struct DeveloperToolsSection: View {
|
||||||
openConsole()
|
openConsole()
|
||||||
}
|
}
|
||||||
.buttonStyle(.bordered)
|
.buttonStyle(.bordered)
|
||||||
|
.frame(width: 120)
|
||||||
}
|
}
|
||||||
Text("View all application logs in Console.app")
|
Text("View all application logs in Console.app")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
|
|
@ -587,6 +571,7 @@ private struct DeveloperToolsSection: View {
|
||||||
showApplicationSupport()
|
showApplicationSupport()
|
||||||
}
|
}
|
||||||
.buttonStyle(.bordered)
|
.buttonStyle(.bordered)
|
||||||
|
.frame(width: 120)
|
||||||
}
|
}
|
||||||
Text("Open the application support directory")
|
Text("Open the application support directory")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
|
|
@ -601,6 +586,7 @@ private struct DeveloperToolsSection: View {
|
||||||
AppDelegate.showWelcomeScreen()
|
AppDelegate.showWelcomeScreen()
|
||||||
}
|
}
|
||||||
.buttonStyle(.bordered)
|
.buttonStyle(.bordered)
|
||||||
|
.frame(width: 120)
|
||||||
}
|
}
|
||||||
Text("Display the welcome screen again")
|
Text("Display the welcome screen again")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
|
|
@ -616,6 +602,7 @@ private struct DeveloperToolsSection: View {
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(.borderedProminent)
|
||||||
.tint(.red)
|
.tint(.red)
|
||||||
|
.frame(width: 120)
|
||||||
}
|
}
|
||||||
Text("Remove all stored preferences and reset to defaults")
|
Text("Remove all stored preferences and reset to defaults")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Combine
|
||||||
|
|
||||||
/// General settings tab for basic app preferences
|
/// General settings tab for basic app preferences
|
||||||
struct GeneralSettingsView: View {
|
struct GeneralSettingsView: View {
|
||||||
|
|
@ -6,8 +7,6 @@ struct GeneralSettingsView: View {
|
||||||
private var autostart = false
|
private var autostart = false
|
||||||
@AppStorage("showNotifications")
|
@AppStorage("showNotifications")
|
||||||
private var showNotifications = true
|
private var showNotifications = true
|
||||||
@AppStorage("showInDock")
|
|
||||||
private var showInDock = false
|
|
||||||
@AppStorage("updateChannel")
|
@AppStorage("updateChannel")
|
||||||
private var updateChannelRaw = UpdateChannel.stable.rawValue
|
private var updateChannelRaw = UpdateChannel.stable.rawValue
|
||||||
|
|
||||||
|
|
@ -76,23 +75,8 @@ struct GeneralSettingsView: View {
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
// Permissions Section
|
||||||
// Show in Dock
|
PermissionsSection()
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
|
||||||
Toggle("Show in Dock", isOn: showInDockBinding)
|
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
|
||||||
Text("Show VibeTunnel icon in the Dock.")
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
Text("The dock icon is always displayed when the Settings dialog is visible.")
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} header: {
|
|
||||||
Text("Appearance")
|
|
||||||
.font(.headline)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.formStyle(.grouped)
|
.formStyle(.grouped)
|
||||||
.scrollContentBackground(.hidden)
|
.scrollContentBackground(.hidden)
|
||||||
|
|
@ -114,17 +98,6 @@ struct GeneralSettingsView: View {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var showInDockBinding: Binding<Bool> {
|
|
||||||
Binding(
|
|
||||||
get: { showInDock },
|
|
||||||
set: { newValue in
|
|
||||||
showInDock = newValue
|
|
||||||
// Don't change activation policy while settings window is open
|
|
||||||
// The change will be applied when the settings window closes
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var updateChannelBinding: Binding<UpdateChannel> {
|
private var updateChannelBinding: Binding<UpdateChannel> {
|
||||||
Binding(
|
Binding(
|
||||||
get: { updateChannel },
|
get: { updateChannel },
|
||||||
|
|
@ -151,3 +124,103 @@ struct GeneralSettingsView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Permissions Section
|
||||||
|
|
||||||
|
private struct PermissionsSection: View {
|
||||||
|
@StateObject private var appleScriptManager = AppleScriptPermissionManager.shared
|
||||||
|
@State private var accessibilityUpdateTrigger = 0
|
||||||
|
|
||||||
|
private var hasAccessibilityPermission: Bool {
|
||||||
|
// This will cause a re-read whenever accessibilityUpdateTrigger changes
|
||||||
|
_ = accessibilityUpdateTrigger
|
||||||
|
return AccessibilityPermissionManager.shared.hasPermission()
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Section {
|
||||||
|
VStack(alignment: .leading, spacing: 16) {
|
||||||
|
// Automation permission
|
||||||
|
HStack {
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Text("Terminal Automation")
|
||||||
|
.font(.body)
|
||||||
|
Text("Required to launch and control terminal applications")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
if appleScriptManager.hasPermission {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "checkmark.circle.fill")
|
||||||
|
.foregroundColor(.green)
|
||||||
|
Text("Granted")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
.font(.caption)
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
.padding(.vertical, 2)
|
||||||
|
.frame(height: 22) // Match small button height
|
||||||
|
} else {
|
||||||
|
Button("Grant Permission") {
|
||||||
|
appleScriptManager.requestPermission()
|
||||||
|
}
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
.controlSize(.small)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
|
||||||
|
// Accessibility permission
|
||||||
|
HStack {
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Text("Accessibility")
|
||||||
|
.font(.body)
|
||||||
|
Text("Required for terminals that need keystroke input (Ghostty, Warp, Hyper)")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
if hasAccessibilityPermission {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "checkmark.circle.fill")
|
||||||
|
.foregroundColor(.green)
|
||||||
|
Text("Granted")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
.font(.caption)
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
.padding(.vertical, 2)
|
||||||
|
.frame(height: 22) // Match small button height
|
||||||
|
} else {
|
||||||
|
Button("Grant Permission") {
|
||||||
|
AccessibilityPermissionManager.shared.requestPermission()
|
||||||
|
}
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
.controlSize(.small)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} header: {
|
||||||
|
Text("Permissions")
|
||||||
|
.font(.headline)
|
||||||
|
} footer: {
|
||||||
|
Text("Terminal automation is required for all terminals. Accessibility is only needed for terminals that simulate keyboard input.")
|
||||||
|
.font(.caption)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
}
|
||||||
|
.task {
|
||||||
|
_ = await appleScriptManager.checkPermission()
|
||||||
|
}
|
||||||
|
.onReceive(Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()) { _ in
|
||||||
|
// Force a re-render to check accessibility permission
|
||||||
|
accessibilityUpdateTrigger += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue