feat: add summary dashboard history charts

This commit is contained in:
2026-04-21 18:03:51 +02:00
parent 0bb4be861c
commit 44f4206f34
12 changed files with 790 additions and 81 deletions

View File

@@ -12,7 +12,7 @@ struct GeneralView: View {
var canRestart: Bool = false
var isRestarting: Bool = false
var onRestart: (() -> Void)? = nil
var body: some View {
GeometryReader { geometry in
ScrollView {
@@ -22,13 +22,13 @@ struct GeneralView: View {
} value: {
InfoCell(value: [server.hostname])
}
TableRowView {
Text("IP addresses")
} value: {
InfoCell(value: server.info?.ipAddresses ?? [], monospaced: true)
}
TableRowView {
Text("Server time")
} value: {
@@ -49,53 +49,76 @@ struct GeneralView: View {
TableRowView {
Text("Operating system")
} value: {
InfoCell(value: operatingSystemRows, monospaced: true)
}
TableRowView {
Text("CPU")
} value: {
InfoCell(
value: {
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
}(),
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("Sytem PHP version")
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)
}
@@ -103,22 +126,7 @@ struct GeneralView: View {
TableRowView(showDivider: false) {
Text("Additional PHP interpreters")
} value: {
InfoCell(
value: {
let interpreters = server.info?.additionalPHPInterpreters ?? []
if interpreters.isEmpty {
return ["None"]
}
let versions = interpreters
.map { $0.fullVersion }
.filter { !$0.isEmpty }
if versions.isEmpty {
return ["None"]
}
return [versions.joined(separator: "")]
}(),
monospaced: true
)
InfoCell(value: additionalPHPRows, monospaced: true)
}
if canRestart, let onRestart {
@@ -155,6 +163,60 @@ struct GeneralView: View {
.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 {