// // GeneralTab.swift // iKeyMon // // Created by tracer on 30.03.25. // import SwiftUI struct GeneralView: View { @Binding var server: Server var canRestart: Bool = false var isRestarting: Bool = false var onRestart: (() -> Void)? = nil var body: some View { GeometryReader { geometry in ScrollView { VStack(alignment: .leading, spacing: 6) { TableRowView { Text("Hostname") } value: { InfoCell(value: [server.hostname]) } TableRowView { Text("IP addresses") } value: { InfoCell(value: server.info?.ipAddresses ?? [], monospaced: true) } TableRowView { Text("Server time") } value: { InfoCell(value: [server.info?.formattedServerTime ?? ""], monospaced: true) } TableRowView { Text("Uptime") } value: { InfoCell(value: [server.info?.uptime ?? ""]) } TableRowView { Text("KeyHelp version") } value: { InfoCell(value: [server.info?.formattedVersion ?? ""], monospaced: true) } TableRowView { Text("Operating system") } value: { InfoCell(value: operatingSystemRows, monospaced: true) } TableRowView { Text("CPU") } value: { InfoCell( value: [ "\(server.info?.cpuCores ?? 0) cores", String(format: "Load %.2f%% (%.2f / %.2f / %.2f)", server.info?.load.percent ?? 0, server.info?.load.minute1 ?? 0, server.info?.load.minute5 ?? 0, server.info?.load.minute15 ?? 0) ], monospaced: true ) } TableRowView { Text("Memory") } value: { InfoCell( value: [ "Used \(server.info?.memory.used ?? 0) / Total \(server.info?.memory.total ?? 0)", String(format: "%.2f %%", server.info?.memory.percent ?? 0) ].map { line in line .replacingOccurrences(of: "\(server.info?.memory.used ?? 0)", with: (server.info?.memory.used ?? 0).toNiceBinaryUnit()) .replacingOccurrences(of: "\(server.info?.memory.total ?? 0)", with: (server.info?.memory.total ?? 0).toNiceBinaryUnit()) }, monospaced: true ) } TableRowView { Text("Swap") } value: { InfoCell( value: [ "Used \(server.info?.swap.used ?? 0) / Total \(server.info?.swap.total ?? 0)", String(format: "%.2f %%", server.info?.swap.percent ?? 0) ].map { line in line .replacingOccurrences(of: "\(server.info?.swap.used ?? 0)", with: (server.info?.swap.used ?? 0).toNiceBinaryUnit()) .replacingOccurrences(of: "\(server.info?.swap.total ?? 0)", with: (server.info?.swap.total ?? 0).toNiceBinaryUnit()) }, monospaced: true ) } TableRowView { Text("Disk space") } value: { InfoCell( value: [ "Used \(server.info?.diskSpace.used ?? 0) / Total \(server.info?.diskSpace.total ?? 0)", String(format: "%.2f %%", server.info?.diskSpace.percent ?? 0) ].map { line in line .replacingOccurrences(of: "\(server.info?.diskSpace.used ?? 0)", with: (server.info?.diskSpace.used ?? 0).toNiceBinaryUnit()) .replacingOccurrences(of: "\(server.info?.diskSpace.total ?? 0)", with: (server.info?.diskSpace.total ?? 0).toNiceBinaryUnit()) }, monospaced: true ) } TableRowView { Text("System PHP version") } value: { InfoCell(value: [server.info?.phpVersion ?? ""], monospaced: true) } TableRowView(showDivider: false) { Text("Additional PHP interpreters") } value: { InfoCell(value: additionalPHPRows, monospaced: true) } if canRestart, let onRestart { TableRowView(showDivider: false) { Text("Actions") } value: { VStack(alignment: .leading, spacing: 8) { Button(role: .destructive) { onRestart() } label: { if isRestarting { HStack(spacing: 8) { ProgressView() .controlSize(.small) Text("Rebooting…") } } else { Label("Reboot Server", systemImage: "arrow.clockwise.circle") } } .disabled(isRestarting) Text("Sends a reboot command to the selected host.") .font(.caption) .foregroundColor(.secondary) } } } } .padding() .frame(minHeight: geometry.size.height, alignment: .top) } .padding() .scrollDisabled(true) } } private var operatingSystemRows: [String] { guard let os = server.info?.operatingSystem else { return [] } var rows: [String] = [] let distro = [os.distribution, os.version] .filter { !$0.isEmpty } .joined(separator: " ") .trimmingCharacters(in: .whitespacesAndNewlines) var description = os.label.trimmingCharacters(in: .whitespacesAndNewlines) if description.isEmpty { description = distro } else if !distro.isEmpty && description.range(of: distro, options: [.caseInsensitive]) == nil { description += " • \(distro)" } if !os.architecture.isEmpty && description.range(of: os.architecture, options: [.caseInsensitive]) == nil { description += " (\(os.architecture))" } if !description.isEmpty { rows.append(description) } if let updates = os.updates { var updateDescription = "Updates: \(updates.updateCount)" if updates.securityUpdateCount > 0 { updateDescription += " • \(updates.securityUpdateCount) security" } rows.append(updateDescription) if updates.rebootRequired { rows.append("Reboot required") } } if os.endOfLife { rows.append("End-of-life release") } return rows } private var additionalPHPRows: [String] { let interpreters = server.info?.additionalPHPInterpreters ?? [] let versions = interpreters .map { $0.fullVersion } .filter { !$0.isEmpty } if versions.isEmpty { return ["None"] } return [versions.joined(separator: " • ")] } } #Preview { struct PreviewWrapper: View { @State var previewServer = Server(hostname: "example.com", info: .placeholder) var body: some View { GeneralView(server: $previewServer, canRestart: true) } } return PreviewWrapper() }