Fix new security settings

This commit is contained in:
Peter Steinberger 2025-06-24 02:26:11 +02:00
parent bcad09585d
commit 52bded09fa

View file

@ -82,7 +82,7 @@ struct DashboardSettingsView: View {
) )
} }
.formStyle(.grouped) .formStyle(.grouped)
.frame(minWidth: 600) .frame(minWidth: 500, idealWidth: 600)
.navigationTitle("Dashboard") .navigationTitle("Dashboard")
.onAppear { .onAppear {
onAppearSetup() onAppearSetup()
@ -258,10 +258,10 @@ private struct SecuritySection: View {
var displayName: String { var displayName: String {
switch self { switch self {
case .none: "No Authentication" case .none: "None"
case .osAuth: "macOS Authentication" case .osAuth: "macOS"
case .sshKeys: "SSH Keys Only" case .sshKeys: "SSH Keys"
case .both: "macOS + SSH Keys" case .both: "Both"
} }
} }
@ -280,52 +280,58 @@ private struct SecuritySection: View {
VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 16) {
// Authentication mode picker // Authentication mode picker
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
Text("Authentication Method") HStack {
.font(.callout) Text("Authentication Method")
.fontWeight(.medium) .font(.callout)
Spacer()
Picker("", selection: $authMode) { Picker("", selection: $authMode) {
ForEach(AuthenticationMode.allCases, id: \.self) { mode in ForEach(AuthenticationMode.allCases, id: \.self) { mode in
Text(mode.displayName) Text(mode.displayName)
.tag(mode) .tag(mode)
}
} }
} .labelsHidden()
.pickerStyle(.segmented) .pickerStyle(.menu)
.onChange(of: authMode) { _, newValue in .frame(minWidth: 120, idealWidth: 150)
// Save the authentication mode .fixedSize()
UserDefaults.standard.set(newValue.rawValue, forKey: "authenticationMode") .onChange(of: authMode) { _, newValue in
// Save the authentication mode
UserDefaults.standard.set(newValue.rawValue, forKey: "authenticationMode")
Task { Task {
logger.info("Authentication mode changed to: \(newValue.rawValue)") logger.info("Authentication mode changed to: \(newValue.rawValue)")
await serverManager.restart() await serverManager.restart()
}
} }
} }
Text(authMode.description)
.font(.caption)
.foregroundStyle(.secondary)
} }
// Additional info based on selected mode // Additional info based on selected mode
if authMode == .osAuth || authMode == .both { if authMode == .osAuth || authMode == .both {
HStack { HStack(alignment: .center, spacing: 6) {
Image(systemName: "info.circle") Image(systemName: "info.circle")
.foregroundColor(.blue) .foregroundColor(.blue)
.font(.system(size: 12))
.frame(width: 16, height: 16)
Text("Uses your macOS username: \(NSUserName())") Text("Uses your macOS username: \(NSUserName())")
.font(.caption) .font(.caption)
.foregroundStyle(.secondary)
Spacer()
} }
} }
if authMode == .sshKeys || authMode == .both { if authMode == .sshKeys || authMode == .both {
VStack(alignment: .leading, spacing: 8) { HStack(alignment: .center, spacing: 6) {
HStack { Image(systemName: "key.fill")
Image(systemName: "key.fill") .foregroundColor(.blue)
.foregroundColor(.blue) .font(.system(size: 12))
Text("SSH keys from ~/.ssh/authorized_keys") .frame(width: 16, height: 16)
.font(.caption) Text("SSH keys from ~/.ssh/authorized_keys")
} .font(.caption)
.foregroundStyle(.secondary)
Button("Open SSH Keys Folder") { Spacer()
Button("Open folder") {
let sshPath = NSHomeDirectory() + "/.ssh" let sshPath = NSHomeDirectory() + "/.ssh"
if FileManager.default.fileExists(atPath: sshPath) { if FileManager.default.fileExists(atPath: sshPath) {
NSWorkspace.shared.open(URL(fileURLWithPath: sshPath)) NSWorkspace.shared.open(URL(fileURLWithPath: sshPath))
@ -340,6 +346,7 @@ private struct SecuritySection: View {
} }
} }
.buttonStyle(.link) .buttonStyle(.link)
.font(.caption)
} }
} }
} }
@ -349,8 +356,8 @@ private struct SecuritySection: View {
} footer: { } footer: {
Text("Localhost connections are always accessible without authentication.") Text("Localhost connections are always accessible without authentication.")
.font(.caption) .font(.caption)
.frame(maxWidth: .infinity)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.frame(maxWidth: .infinity)
} }
} }
} }
@ -368,19 +375,21 @@ private struct ServerConfigurationSection: View {
var body: some View { var body: some View {
Section { Section {
AccessModeView( VStack(alignment: .leading, spacing: 12) {
accessMode: accessMode, AccessModeView(
accessModeString: $accessModeString, accessMode: accessMode,
serverPort: serverPort, accessModeString: $accessModeString,
localIPAddress: localIPAddress, serverPort: serverPort,
restartServerWithNewBindAddress: restartServerWithNewBindAddress localIPAddress: localIPAddress,
) restartServerWithNewBindAddress: restartServerWithNewBindAddress
)
PortConfigurationView( PortConfigurationView(
serverPort: $serverPort, serverPort: $serverPort,
restartServerWithNewPort: restartServerWithNewPort, restartServerWithNewPort: restartServerWithNewPort,
serverManager: serverManager serverManager: serverManager
) )
}
} header: { } header: {
Text("Server Configuration") Text("Server Configuration")
.font(.headline) .font(.headline)
@ -415,17 +424,14 @@ private struct AccessModeView: View {
} }
if accessMode == .network { if accessMode == .network {
HStack { if let ip = localIPAddress {
Image(systemName: "info.circle") Text("Dashboard available at http://\(ip):\(serverPort)")
.foregroundColor(.blue) .font(.caption)
if let ip = localIPAddress { .foregroundStyle(.secondary)
Text("Dashboard available at http://\(ip):\(serverPort)") } else {
.font(.caption) Text("Fetching local IP address...")
} else { .font(.caption)
Text("Fetching local IP address...") .foregroundStyle(.secondary)
.font(.caption)
.foregroundColor(.secondary)
}
} }
} }
} }
@ -443,37 +449,68 @@ private struct PortConfigurationView: View {
@State private var portError: String? @State private var portError: String?
var body: some View { var body: some View {
HStack { VStack(alignment: .leading, spacing: 8) {
Text("Port") HStack {
.font(.callout) Text("Port")
Spacer() .font(.callout)
TextField("Port", text: $pendingPort) Spacer()
.textFieldStyle(.roundedBorder) HStack(spacing: 4) {
.frame(width: 80) TextField("", text: $pendingPort)
.focused($isPortFieldFocused) .textFieldStyle(.roundedBorder)
.onSubmit { .frame(width: 80)
validateAndUpdatePort() .multilineTextAlignment(.center)
} .focused($isPortFieldFocused)
.onAppear { .onSubmit {
pendingPort = serverPort validateAndUpdatePort()
} }
.onChange(of: pendingPort) { _, newValue in .onAppear {
// Clear error when user types pendingPort = serverPort
portError = nil }
// Limit to 5 digits .onChange(of: pendingPort) { _, newValue in
if newValue.count > 5 { // Clear error when user types
pendingPort = String(newValue.prefix(5)) portError = nil
// Limit to 5 digits
if newValue.count > 5 {
pendingPort = String(newValue.prefix(5))
}
}
VStack(spacing: 0) {
Button(action: {
if let port = Int(pendingPort), port < 65535 {
pendingPort = String(port + 1)
validateAndUpdatePort()
}
}) {
Image(systemName: "chevron.up")
.font(.system(size: 10))
.frame(width: 16, height: 11)
}
.buttonStyle(.borderless)
Button(action: {
if let port = Int(pendingPort), port > 1024 {
pendingPort = String(port - 1)
validateAndUpdatePort()
}
}) {
Image(systemName: "chevron.down")
.font(.system(size: 10))
.frame(width: 16, height: 11)
}
.buttonStyle(.borderless)
} }
} }
} }
if let error = portError { if let error = portError {
HStack { HStack {
Image(systemName: "exclamationmark.triangle") Image(systemName: "exclamationmark.triangle")
.foregroundColor(.red) .foregroundColor(.red)
Text(error) Text(error)
.font(.caption) .font(.caption)
.foregroundColor(.red) .foregroundColor(.red)
}
} }
} }
} }
@ -566,8 +603,10 @@ private struct NgrokIntegrationSection: View {
// Link to ngrok dashboard // Link to ngrok dashboard
HStack { HStack {
Image(systemName: "link") Image(systemName: "link")
Link("Create free ngrok account", destination: URL(string: "https://dashboard.ngrok.com/signup")!) if let url = URL(string: "https://dashboard.ngrok.com/signup") {
.font(.caption) Link("Create free ngrok account", destination: url)
.font(.caption)
}
} }
} }
} header: { } header: {
@ -576,7 +615,6 @@ private struct NgrokIntegrationSection: View {
} footer: { } footer: {
Text("ngrok creates secure tunnels to your dashboard from anywhere.") Text("ngrok creates secure tunnels to your dashboard from anywhere.")
.font(.caption) .font(.caption)
.frame(maxWidth: .infinity)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
} }
} }
@ -683,10 +721,10 @@ private struct PublicURLView: View {
showCopiedFeedback = false showCopiedFeedback = false
} }
} }
}) { }, label: {
Image(systemName: showCopiedFeedback ? "checkmark" : "doc.on.doc") Image(systemName: showCopiedFeedback ? "checkmark" : "doc.on.doc")
.foregroundColor(showCopiedFeedback ? .green : .accentColor) .foregroundColor(showCopiedFeedback ? .green : .accentColor)
} })
.buttonStyle(.borderless) .buttonStyle(.borderless)
.help("Copy URL") .help("Copy URL")
} }
@ -712,9 +750,7 @@ private struct ErrorView: View {
// MARK: - Previews // MARK: - Previews
struct DashboardSettingsView_Previews: PreviewProvider { #Preview("Dashboard Settings") {
static var previews: some View { DashboardSettingsView()
DashboardSettingsView() .frame(width: 500, height: 800)
.frame(width: 600, height: 800)
}
} }