230 lines
8.1 KiB
Swift
230 lines
8.1 KiB
Swift
//
|
|
// MainView.swift
|
|
// iKeyMon
|
|
//
|
|
// Created by tracer on 30.03.25.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct MainView: View {
|
|
|
|
@State var showAddServerSheet: Bool = false
|
|
@State private var serverBeingEdited: Server?
|
|
@State private var serverToDelete: Server?
|
|
@State private var showDeleteConfirmation = false
|
|
@State private var isFetchingInfo: Bool = false
|
|
@State private var refreshTimer = Timer.publish(every: 60, on: .main, in: .common).autoconnect()
|
|
@State private var progress: Double = 0
|
|
@State private var lastRefresh = Date()
|
|
@State private var pingTimer: Timer?
|
|
private let serverOrderKey = "serverOrder"
|
|
|
|
@State private var servers: [Server] = {
|
|
if let data = UserDefaults.standard.data(forKey: "storedServers"),
|
|
let saved = try? JSONDecoder().decode([Server].self, from: data) {
|
|
|
|
if let idStrings = UserDefaults.standard.stringArray(forKey: "serverOrder") {
|
|
let idMap = idStrings.compactMap(UUID.init)
|
|
return saved.sorted { a, b in
|
|
guard
|
|
let i1 = idMap.firstIndex(of: a.id),
|
|
let i2 = idMap.firstIndex(of: b.id)
|
|
else { return false }
|
|
return i1 < i2
|
|
}
|
|
}
|
|
|
|
return saved
|
|
}
|
|
return []
|
|
}()
|
|
|
|
// @State private var selectedServer: Server?
|
|
@State private var selectedServerID: UUID?
|
|
|
|
var body: some View {
|
|
|
|
NavigationSplitView {
|
|
List(selection: $selectedServerID) {
|
|
ForEach(servers) { server in
|
|
HStack {
|
|
Image(systemName: "dot.circle.fill")
|
|
.foregroundColor(server.pingable ? .green : .red)
|
|
Text(server.hostname)
|
|
}
|
|
.tag(server)
|
|
.contextMenu {
|
|
Button("Edit") {
|
|
print("Editing:", server.hostname)
|
|
serverBeingEdited = server
|
|
}
|
|
Divider()
|
|
Button("Delete", role: .destructive) {
|
|
serverToDelete = server
|
|
showDeleteConfirmation = true
|
|
}
|
|
}
|
|
}
|
|
.onMove(perform: moveServer)
|
|
}
|
|
.toolbar {
|
|
ToolbarItem(placement: .primaryAction) {
|
|
Button(action: { showAddServerSheet = true }) {
|
|
Image(systemName: "plus")
|
|
}
|
|
.help("Add Host")
|
|
}
|
|
}
|
|
.navigationTitle("Servers")
|
|
.onChange(of: selectedServerID) {
|
|
if let selectedServerID {
|
|
UserDefaults.standard.set(selectedServerID.uuidString, forKey: "selectedServerID")
|
|
fetchServerInfo(for: selectedServerID)
|
|
}
|
|
}
|
|
} detail: {
|
|
if let selectedServerID,
|
|
let index = servers.firstIndex(where: { selectedServerID == $0.id }) {
|
|
ServerDetailView(server: $servers[index], isFetching: isFetchingInfo)
|
|
} else {
|
|
ContentUnavailableView("No Server Selected", systemImage: "server.rack")
|
|
}
|
|
}
|
|
.sheet(isPresented: $showAddServerSheet) {
|
|
ServerFormView(
|
|
mode: .add,
|
|
servers: $servers,
|
|
dismiss: { showAddServerSheet = false }
|
|
)
|
|
}
|
|
.sheet(item: $serverBeingEdited) { server in
|
|
ServerFormView(
|
|
mode: .edit(server),
|
|
servers: $servers,
|
|
dismiss: { serverBeingEdited = nil }
|
|
)
|
|
}
|
|
.alert("Are you sure you want to delete this server?", isPresented: $showDeleteConfirmation, presenting: serverToDelete) { server in
|
|
Button("Delete", role: .destructive) {
|
|
ServerFormView.delete(server: server, from: &servers)
|
|
}
|
|
Button("Cancel", role: .cancel) {}
|
|
}
|
|
.onReceive(refreshTimer) { _ in
|
|
for server in servers {
|
|
print("fetching server: \(server.hostname)")
|
|
fetchServerInfo(for: server.id)
|
|
}
|
|
}
|
|
.onAppear {
|
|
if let storedID = UserDefaults.standard.string(forKey: "selectedServerID"),
|
|
let uuid = UUID(uuidString: storedID),
|
|
servers.contains(where: { $0.id == uuid }) {
|
|
selectedServerID = uuid
|
|
} else if selectedServerID == nil, let first = servers.first {
|
|
selectedServerID = first.id
|
|
}
|
|
pingAllServers()
|
|
pingTimer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: true) { _ in
|
|
pingAllServers()
|
|
}
|
|
}
|
|
.frame(minWidth: 800, minHeight: 450)
|
|
|
|
}
|
|
|
|
private func fetchServerInfo(for id: UUID) {
|
|
guard let server = servers.first(where: { $0.id == id }),
|
|
let api = ServerAPI(server: server) else {
|
|
return
|
|
}
|
|
|
|
isFetchingInfo = true
|
|
|
|
Task {
|
|
defer { isFetchingInfo = false }
|
|
do {
|
|
let info = try await api.fetchServerInfo()
|
|
if let index = servers.firstIndex(where: { $0.id == id }) {
|
|
var updated = servers[index]
|
|
updated.info = try ServerInfo(from: info as! Decoder)
|
|
servers[index] = updated
|
|
}
|
|
} catch {
|
|
print("❌ Failed to fetch server data: \(error)")
|
|
}
|
|
}
|
|
}
|
|
|
|
private func moveServer(from source: IndexSet, to destination: Int) {
|
|
servers.move(fromOffsets: source, toOffset: destination)
|
|
saveServerOrder()
|
|
}
|
|
|
|
private func saveServerOrder() {
|
|
let ids = servers.map { $0.id.uuidString }
|
|
UserDefaults.standard.set(ids, forKey: serverOrderKey)
|
|
}
|
|
|
|
private struct PingResponse: Codable {
|
|
let response: String
|
|
}
|
|
|
|
// func pingServer(_ server: Server) async -> Bool {
|
|
// let hostname = server.hostname
|
|
// guard let url = URL(string: "https://\(hostname)/api/v2/ping") else {
|
|
// return false
|
|
// }
|
|
//
|
|
// var request = URLRequest(url: url)
|
|
// request.httpMethod = "GET"
|
|
// request.timeoutInterval = 5
|
|
// request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
//
|
|
// let apiKey = KeychainHelper.loadApiKey(for: hostname)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
// request.setValue(apiKey, forHTTPHeaderField: "X-API-KEY")
|
|
//
|
|
// do {
|
|
// let (data, response) = try await URLSession.shared.data(for: request)
|
|
// if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 {
|
|
// do {
|
|
// let decoded = try JSONDecoder().decode(PingResponse.self, from: data)
|
|
// if decoded.response == "pong" {
|
|
// return true
|
|
// } else {
|
|
// print("❌ Unexpected response: \(decoded.response)")
|
|
// return false
|
|
// }
|
|
// } catch {
|
|
// print("❌ Failed to decode JSON: \(error)")
|
|
// return false
|
|
// }
|
|
// } else {
|
|
// return false
|
|
// }
|
|
// } catch {
|
|
// print("[Ping] \(server.hostname): \(error.localizedDescription)")
|
|
// return false
|
|
// }
|
|
// }
|
|
|
|
func pingAllServers() {
|
|
for (index, server) in servers.enumerated() {
|
|
Task {
|
|
let apiKey = KeychainHelper.loadApiKey(for: server.hostname)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
|
|
let api = ServerAPI(hostname: server.hostname, apiKey: apiKey)
|
|
let pingable = await api.ping()
|
|
|
|
servers[index].pingable = pingable
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#Preview {
|
|
MainView()
|
|
}
|