155 lines
8.0 KiB
Swift
155 lines
8.0 KiB
Swift
import SwiftUI
|
|
|
|
struct IOSSettingsView: View {
|
|
@EnvironmentObject private var appState: AppState
|
|
@EnvironmentObject private var workspaceStore: WorkspaceStore
|
|
@State private var editor: ServerEditorItem?
|
|
@State private var serverToDelete: SonarServer?
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
Form {
|
|
Section("Workspace") {
|
|
LabeledContent("Name", value: workspaceStore.selectedWorkspace.name)
|
|
LabeledContent("Type", value: workspaceStore.selectedWorkspace.kind == .team ? "Team" : "Personal")
|
|
LabeledContent("Role", value: workspaceStore.selectedWorkspace.role.rawValue.capitalized)
|
|
}
|
|
|
|
Section {
|
|
if appState.servers.isEmpty {
|
|
ContentUnavailableView("No SonarQube Servers", systemImage: "server.rack", description: Text("Add a server to load your SonarQube projects."))
|
|
} else {
|
|
ForEach(appState.servers) { server in
|
|
Button {
|
|
appState.selectServer(server.id)
|
|
} label: {
|
|
HStack(spacing: 12) {
|
|
Image(systemName: appState.selectedServerID == server.id ? "checkmark.circle.fill" : "circle")
|
|
.foregroundStyle(appState.selectedServerID == server.id ? Color.accentColor : Color.secondary)
|
|
VStack(alignment: .leading, spacing: 3) {
|
|
Text(server.name).foregroundStyle(.primary)
|
|
Text(server.baseURL).font(.caption).foregroundStyle(.secondary).lineLimit(1)
|
|
}
|
|
Spacer()
|
|
Button { editor = ServerEditorItem(server: server) } label: { Image(systemName: "pencil") }
|
|
.buttonStyle(.borderless)
|
|
}
|
|
}
|
|
.swipeActions(edge: .trailing) {
|
|
Button(role: .destructive) { serverToDelete = server } label: { Label("Delete", systemImage: "trash") }
|
|
Button { editor = ServerEditorItem(server: server) } label: { Label("Edit", systemImage: "pencil") }.tint(.blue)
|
|
}
|
|
}
|
|
}
|
|
|
|
Button { editor = ServerEditorItem(server: nil) } label: {
|
|
Label("Add SonarQube Server", systemImage: "plus.circle.fill")
|
|
}
|
|
} header: { Text("SonarQube Servers") } footer: {
|
|
Text("Access tokens are stored only in this device's Keychain and are not written to iCloud.")
|
|
}
|
|
|
|
if let server = appState.selectedServer {
|
|
Section("Active Server") {
|
|
LabeledContent("Server", value: server.name)
|
|
LabeledContent("Address", value: server.baseURL)
|
|
if let version = appState.serverVersion { LabeledContent("Version", value: version) }
|
|
Button("Refresh Projects") { Task { await appState.refresh() } }
|
|
}
|
|
}
|
|
}
|
|
.navigationTitle("Settings")
|
|
.sheet(item: $editor) { item in
|
|
IOSServerEditorView(server: item.server)
|
|
.environmentObject(appState)
|
|
}
|
|
.confirmationDialog("Delete this server?", isPresented: Binding(get: { serverToDelete != nil }, set: { if !$0 { serverToDelete = nil } }), titleVisibility: .visible) {
|
|
if let server = serverToDelete {
|
|
Button("Delete \(server.name)", role: .destructive) { appState.removeServer(server); serverToDelete = nil }
|
|
}
|
|
Button("Cancel", role: .cancel) { serverToDelete = nil }
|
|
} message: { Text("The server configuration and its token will be removed from this device.") }
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct ServerEditorItem: Identifiable {
|
|
let id = UUID()
|
|
let server: SonarServer?
|
|
}
|
|
|
|
private struct IOSServerEditorView: View {
|
|
@EnvironmentObject private var appState: AppState
|
|
@Environment(\.dismiss) private var dismiss
|
|
let server: SonarServer?
|
|
|
|
@State private var name: String
|
|
@State private var url: String
|
|
@State private var token = ""
|
|
@State private var isTesting = false
|
|
@State private var isSaving = false
|
|
@State private var testResult: String?
|
|
@State private var errorMessage: String?
|
|
|
|
init(server: SonarServer?) {
|
|
self.server = server
|
|
_name = State(initialValue: server?.name ?? "")
|
|
_url = State(initialValue: server?.baseURL ?? "https://")
|
|
}
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
Form {
|
|
Section("Server") {
|
|
TextField("Name", text: $name).textContentType(.organizationName)
|
|
TextField("https://sonarqube.example.com", text: $url)
|
|
.textInputAutocapitalization(.never).autocorrectionDisabled()
|
|
.keyboardType(.URL).textContentType(.URL)
|
|
}
|
|
Section("Authentication") {
|
|
SecureField(server == nil ? "SonarQube API token" : "New token (leave blank to keep current)", text: $token)
|
|
.textInputAutocapitalization(.never).autocorrectionDisabled()
|
|
Text(server == nil ? "Create a SonarQube user token and paste it here." : "For security the existing token cannot be displayed. Leave this blank unless you want to replace it.")
|
|
.font(.footnote).foregroundStyle(.secondary)
|
|
}
|
|
Section {
|
|
Button { Task { await testConnection() } } label: {
|
|
HStack { if isTesting { ProgressView() }; Label("Test Connection", systemImage: "network") }
|
|
}.disabled(isTesting || url.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || (server == nil && token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty))
|
|
if let testResult { Label(testResult, systemImage: "checkmark.circle.fill").foregroundStyle(.green) }
|
|
if let errorMessage { Text(errorMessage).foregroundStyle(.red).font(.footnote) }
|
|
}
|
|
}
|
|
.navigationTitle(server == nil ? "Add Server" : "Edit Server")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() } }
|
|
ToolbarItem(placement: .confirmationAction) { Button("Save") { save() }.disabled(isSaving || url.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || (server == nil && token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)) }
|
|
}
|
|
}
|
|
}
|
|
|
|
@MainActor private func testConnection() async {
|
|
isTesting = true; errorMessage = nil; testResult = nil
|
|
defer { isTesting = false }
|
|
do {
|
|
let result: String
|
|
if let server, token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
|
result = try await appState.testServer(id: server.id)
|
|
} else {
|
|
result = try await appState.testServerConfiguration(name: name, url: url, token: token)
|
|
}
|
|
testResult = "Connected — \(result)"
|
|
} catch { errorMessage = error.localizedDescription }
|
|
}
|
|
|
|
private func save() {
|
|
isSaving = true; errorMessage = nil
|
|
do {
|
|
if let server { try appState.updateServer(id: server.id, name: name, url: url, token: token.isEmpty ? nil : token) }
|
|
else { try appState.addServer(name: name, url: url, token: token) }
|
|
dismiss()
|
|
} catch { errorMessage = error.localizedDescription; isSaving = false }
|
|
}
|
|
}
|