Refactor project structure and API

This commit is contained in:
Micha
2025-11-15 19:49:28 +01:00
parent 23ffe1268a
commit 7593a781f2
33 changed files with 966 additions and 404 deletions

246
Views/ServerFormView.swift Normal file
View File

@@ -0,0 +1,246 @@
//
// EditServerView.swift
// iKeyMon
//
// Created by tracer on 30.03.25.
//
import SwiftUI
struct ServerFormView: View {
enum Mode {
case add
case edit(Server)
}
var mode: Mode
@Binding var servers: [Server]
@State private var hostname: String
@State private var apiKey: String
@State private var connectionOK: Bool = false
@Environment(\.dismiss) private var dismiss
init(
mode: Mode,
servers: Binding<[Server]>,
dismiss: @escaping () -> Void
) {
self.mode = mode
self._servers = servers
switch mode {
case .add:
self._hostname = State(initialValue: "")
self._apiKey = State(initialValue: "")
case .edit(let server):
self._hostname = State(initialValue: server.hostname)
self._apiKey = State(initialValue: KeychainHelper.loadApiKey(for: server.hostname) ?? "")
}
}
var body: some View {
VStack {
Text("Edit Server")
.font(.headline)
TextField("Hostname", text: $hostname)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.top)
SecureField("API Key", text: $apiKey)
.textFieldStyle(RoundedBorderTextFieldStyle())
HStack {
Button("Cancel") {
dismiss()
}
Spacer()
Button("Test connection") {
Task {
await testConnection()
}
}
Button("Save") {
saveServer()
updateServer()
saveServers()
dismiss()
}
.disabled(hostname.isEmpty || apiKey.isEmpty || !connectionOK)
}
.padding(.top)
}
.padding()
.frame(width: 300)
.onAppear {
print("on appear")
if case let .edit(server) = mode {
print("serve \(server)")
hostname = server.hostname
apiKey = KeychainHelper.loadApiKey(for: server.hostname) ?? ""
print("💡 Loaded server: \(hostname)")
}
}
}
private var modeTitle: String {
switch mode {
case .add:
return "Add Server"
case .edit(let server):
return "Edit \(server.hostname)"
}
}
private func testConnection() async {
let host = hostname.trimmingCharacters(in: .whitespacesAndNewlines)
let key = apiKey.trimmingCharacters(in: .whitespacesAndNewlines)
let pinger = ServerAPI(hostname: host, apiKey: key)
connectionOK = await pinger.ping()
//
// guard let url = URL(string: "https://\(host)/api/v2/ping") else {
// print(" Invalid URL")
// return
// }
//
// var request = URLRequest(url: url)
// request.httpMethod = "GET"
// request.setValue(key, forHTTPHeaderField: "X-API-KEY")
// request.setValue("application/json", forHTTPHeaderField: "Content-Type")
//
// print("🔍 Headers: \(request.allHTTPHeaderFields ?? [:])")
//
// Task {
// do {
// let (data, response) = try await URLSession.shared.data(for: request)
// if let httpResponse = response as? HTTPURLResponse {
// print("🔄 Status: \(httpResponse.statusCode)")
// }
//
// if let result = try? JSONDecoder().decode([String: String].self, from: data),
// result["response"] == "pong" {
// print(" Pong received")
// connectionOK = true
// } else {
// print(" Unexpected response")
// connectionOK = false
// }
// } catch {
// print(" Error: \(error)")
// connectionOK = false
// }
// }
}
// func testKeyHelpConnection() {
// let url = URL(string: "https://keyhelp.lab.24unix.net/api/v2/ping")!
// var request = URLRequest(url: url)
// request.httpMethod = "GET"
//
// // @State is no reliable source
// let key = apiKey.trimmingCharacters(in: .whitespacesAndNewlines)
// request.setValue(key, forHTTPHeaderField: "X-API-KEY")
//
// request.setValue("application/json", forHTTPHeaderField: "Content-Type")
//
// print("🔍 Headers: \(request.allHTTPHeaderFields ?? [:])")
//
// let task = URLSession.shared.dataTask(with: request) { data, response, error in
// if let error = error {
// print(" Error: \(error.localizedDescription)")
// return
// }
//
// if let httpResponse = response as? HTTPURLResponse {
// print("🔄 Status Code: \(httpResponse.statusCode)")
// }
//
// if let data = data,
// let json = try? JSONSerialization.jsonObject(with: data) {
// print(" Response: \(json)")
// } else {
// print(" No JSON response")
// }
// }
//
// task.resume()
// }
private func saveServer() {
print("in save server")
let trimmedHost = hostname.trimmingCharacters(in: .whitespacesAndNewlines)
let trimmedKey = apiKey.trimmingCharacters(in: .whitespacesAndNewlines)
switch mode {
case .add:
print("adding server")
let newServer = Server(hostname: trimmedHost)
servers.append(newServer)
KeychainHelper.save(apiKey: trimmedKey, for: trimmedHost)
saveServers()
case .edit(let oldServer):
if let index = servers.firstIndex(where: { $0.id == oldServer.id }) {
let oldHostname = servers[index].hostname
servers[index].hostname = trimmedHost
if oldHostname != trimmedHost {
KeychainHelper.deleteApiKey(for: oldHostname)
}
KeychainHelper.save(apiKey: trimmedKey, for: trimmedHost)
}
}
}
private func updateServer() {
print ("in edit server")
guard case let .edit(server) = mode else {
return
}
if let index = servers.firstIndex(where: { $0.id == server.id }) {
// Only replace hostname if changed
let oldHostname = servers[index].hostname
servers[index].hostname = hostname
// Update Keychain
if oldHostname != hostname {
KeychainHelper.deleteApiKey(for: oldHostname)
}
KeychainHelper.save(apiKey: apiKey, for: hostname)
saveServers()
}
}
private func saveServers() {
if let data = try? JSONEncoder().encode(servers) {
UserDefaults.standard.set(data, forKey: "storedServers")
}
}
static func delete(server: Server, from servers: inout [Server]) {
if let index = servers.firstIndex(where: { $0.id == server.id }) {
servers.remove(at: index)
if let data = try? JSONEncoder().encode(servers) {
UserDefaults.standard.set(data, forKey: "storedServers")
}
}
}
}
#Preview {
ServerFormView(
mode: .edit(Server(hostname: "example.com")),
servers: .constant([
Server(hostname: "example.com")
]),
dismiss: {}
)
}