import SwiftUI struct HostEditor: View { let store: ProxyStore let isNew: Bool @State private var host: ProxyHost @Environment(\.dismiss) private var dismiss @State private var domains: String @State private var port: String @State private var saving = false @State private var error: String? init(store: ProxyStore, host: ProxyHost, isNew: Bool) { self.store = store self.isNew = isNew _host = State(initialValue: host) _domains = State(initialValue: host.domainNames.joined(separator: ", ")) _port = State(initialValue: String(host.forwardPort)) } private var valid: Bool { let names = domains.split(separator: ",").map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } return !names.isEmpty && names.allSatisfy { !$0.isEmpty && !$0.contains(" ") && !$0.contains("/") } && !host.forwardHost.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && (1...65535).contains(Int(port) ?? 0) } var body: some View { VStack(spacing: 0) { HStack { Text(isNew ? "New Proxy Host" : "Edit Proxy Host").font(.headline) Spacer() } .padding(20) Form { Section { TextField("Domain Names", text: $domains, prompt: Text("app.example.com")) Picker("Forward Scheme", selection: $host.forwardScheme) { Text("HTTP").tag("http") Text("HTTPS").tag("https") } TextField("Forward Host", text: $host.forwardHost, prompt: Text("192.168.1.20")) TextField("Forward Port", text: $port, prompt: Text("8080")) } header: { Text("Forwarding") } footer: { Text("Separate multiple domain names with commas.") } Section("Security") { Picker("SSL Certificate", selection: $host.certificateId) { Text("None").tag(0) ForEach(store.certificates) { certificate in Text(certificate.niceName).tag(certificate.id) } } Toggle("Force HTTPS", isOn: $host.sslForced).disabled(host.certificateId == 0) Toggle("Block Common Exploits", isOn: $host.blockExploits) } Section("Options") { Toggle("HTTP/2 Support", isOn: $host.http2Support) Toggle("WebSocket Support", isOn: $host.allowWebsocketUpgrade) } } .formStyle(.grouped) .disabled(saving) if let error { Label(error, systemImage: "exclamationmark.triangle") .foregroundStyle(.red).padding(.horizontal, 20).padding(.bottom, 12) } Divider() HStack { if saving { ProgressView().controlSize(.small) } if store.isDemo { Text("Demo workspace").font(.callout).foregroundStyle(.secondary) } Spacer() Button("Cancel") { dismiss() }.keyboardShortcut(.cancelAction).disabled(saving) Button(isNew ? "Add" : "Save") { Task { await save() } } .keyboardShortcut(.defaultAction) .disabled(!valid || saving) } .padding(20) } .frame(width: 510, height: 610) .interactiveDismissDisabled(saving) } private func save() async { saving = true error = nil host.domainNames = domains.split(separator: ",").map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } host.forwardHost = host.forwardHost.trimmingCharacters(in: .whitespacesAndNewlines) host.forwardPort = Int(port) ?? 80 if host.certificateId == 0 { host.sslForced = false } do { try await store.save(host, isNew: isNew) dismiss() } catch { self.error = error.localizedDescription } saving = false } } struct ConnectionSheet: View { let store: ProxyStore @Environment(\.dismiss) private var dismiss @State private var url = "" @State private var email = "" @State private var password = "" @State private var connecting = false @State private var error: String? var body: some View { VStack(alignment: .leading, spacing: 20) { Text("Connect to Server").font(.headline) Text("Enter the address and credentials for your Nginx Proxy Manager server.") .foregroundStyle(.secondary) Form { TextField("Server URL:", text: $url, prompt: Text("https://proxy.example.com")) TextField("Email:", text: $email, prompt: Text("admin@example.com")) SecureField("Password:", text: $password) } .textFieldStyle(.roundedBorder) .disabled(connecting || store.isBusy) Text("Your login is saved in Keychain after connecting and used to reconnect when you open the app.") .font(.callout).foregroundStyle(.secondary) Button("Forget Saved Login") { do { try store.forgetCredentials() password = "" error = nil } catch { self.error = error.localizedDescription } } .disabled(connecting || store.isBusy) Text("Forgetting the saved login leaves the current session connected.") .font(.caption).foregroundStyle(.secondary) if let error { Label(error, systemImage: "exclamationmark.triangle").foregroundStyle(.red) } HStack { if connecting { ProgressView().controlSize(.small) } Spacer() Button("Cancel") { dismiss() }.keyboardShortcut(.cancelAction).disabled(connecting) Button("Connect") { Task { await connect() } } .keyboardShortcut(.defaultAction) .disabled(connecting || store.isBusy || url.isEmpty || email.isEmpty || password.isEmpty) } } .padding(24) .frame(width: 460) .onAppear { url = store.baseURL } .interactiveDismissDisabled(connecting) } private func connect() async { connecting = true error = nil do { try await store.connect(url: url, email: email, password: password) password = "" dismiss() } catch { self.error = error.localizedDescription } connecting = false } }