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

@@ -8,6 +8,7 @@
import SwiftUI
import Combine
import UserNotifications
import SwiftData
import AppKit
import UniformTypeIdentifiers
@@ -37,9 +38,11 @@ struct MainView: View {
@State private var draggedGroupID: UUID?
@State private var groupDropIndicator: GroupDropIndicator?
@State private var lastRefreshInterval: Int?
@State private var lastMetricPrune: Date?
@State private var previousServiceStates: [String: String] = [:]
private let serverOrderKey = MainView.serverOrderKeyStatic
private let storedGroupsKey = MainView.storedGroupsKeyStatic
@Environment(\.modelContext) private var modelContext
@State private var servers: [Server] = MainView.loadStoredServers()
@State private var groups: [ServerGroup] = MainView.loadStoredGroups()
@@ -351,6 +354,7 @@ struct MainView: View {
var updated = servers[index]
updated.info = info
servers[index] = updated
recordMetricSample(for: id, info: info)
checkServiceStatusChanges(for: server.hostname, newInfo: info)
}
}
@@ -386,6 +390,7 @@ struct MainView: View {
var updated = servers[index]
updated.info = info
servers[index] = updated
recordMetricSample(for: id, info: info)
}
}
} catch {
@@ -451,6 +456,53 @@ struct MainView: View {
}
}
private func recordMetricSample(for serverID: UUID, info: ServerInfo) {
let sample = MetricSample(
serverID: serverID,
cpuPercent: info.load.percent,
memoryPercent: info.memory.percent,
swapPercent: info.swap.percent,
diskPercent: info.diskSpace.percent
)
modelContext.insert(sample)
do {
try modelContext.save()
} catch {
print("❌ [MainView] Failed to save metric sample: \(error)")
}
pruneOldMetricSamplesIfNeeded()
}
private func pruneOldMetricSamplesIfNeeded() {
let now = Date()
if let lastMetricPrune, now.timeIntervalSince(lastMetricPrune) < 3600 {
return
}
let cutoff = now.addingTimeInterval(-30 * 24 * 60 * 60)
let descriptor = FetchDescriptor<MetricSample>(
predicate: #Predicate { sample in
sample.timestamp < cutoff
}
)
do {
let expiredSamples = try modelContext.fetch(descriptor)
for sample in expiredSamples {
modelContext.delete(sample)
}
if !expiredSamples.isEmpty {
try modelContext.save()
}
lastMetricPrune = now
} catch {
print("❌ [MainView] Failed to prune metric samples: \(error)")
}
}
private func deleteGroup(_ group: ServerGroup) {
groups.removeAll { $0.id == group.id }
for index in servers.indices {