feat: add alert grace period controls

This commit is contained in:
2026-04-24 19:16:20 +02:00
parent b4163d8c8b
commit d3af580f07
6 changed files with 189 additions and 14 deletions
+57 -8
View File
@@ -31,6 +31,7 @@ struct MainView: View {
@AppStorage("refreshInterval") private var refreshInterval: Int = 60
@AppStorage("enableStatusNotifications") private var enableStatusNotifications: Bool = true
@AppStorage("enableAlertNotifications") private var enableAlertNotifications: Bool = true
@AppStorage("alertGracePeriod") private var alertGracePeriod: Int = 30
@State private var refreshTimer: Timer.TimerPublisher?
@State private var refreshSubscription: AnyCancellable?
@State private var pingTimer: Timer?
@@ -40,6 +41,8 @@ struct MainView: View {
@State private var lastRefreshInterval: Int?
@State private var lastMetricPrune: Date?
@State private var previousServiceStates: [String: String] = [:]
@State private var serviceFailureStartedAt: [String: Date] = [:]
@State private var serviceOfflineAlerted: [String: Bool] = [:]
private let serverOrderKey = MainView.serverOrderKeyStatic
private let storedGroupsKey = MainView.storedGroupsKeyStatic
@Environment(\.modelContext) private var modelContext
@@ -242,9 +245,10 @@ struct MainView: View {
private func sidebarRow(for server: Server) -> some View {
HStack {
Image(systemName: "dot.circle.fill")
.foregroundColor(server.pingable ? .green : .red)
.foregroundColor(sidebarStatusColor(for: server))
Text(server.hostname)
}
.help(sidebarStatusTooltip(for: server))
.tag(server.id)
.contextMenu {
Button("Edit") {
@@ -259,6 +263,23 @@ struct MainView: View {
}
}
private func sidebarStatusColor(for server: Server) -> Color {
if !server.pingable {
return .red
}
if server.info?.rebootRequired == true {
return .yellow
}
return .green
}
private func sidebarStatusTooltip(for server: Server) -> String {
if !server.pingable {
return "This host is offline."
}
return server.info?.statusTooltip ?? "This host is online."
}
private func groupHeader(for group: ServerGroup) -> some View {
let activePlacement = groupDropIndicator?.groupID == group.id ? groupDropIndicator?.placement : nil
@@ -616,17 +637,45 @@ struct MainView: View {
for port in ports {
let key = "\(hostname)-\(port.id)"
let previousStatus = previousServiceStates[key]
let currentStatus = port.status
previousServiceStates[key] = currentStatus
let currentStatus = port.status.lowercased()
let now = Date()
if let previousStatus, previousStatus != currentStatus {
if currentStatus == "offline" && enableStatusNotifications {
sendServiceNotification(service: port.service, hostname: hostname, status: "offline")
} else if currentStatus == "online" && previousStatus == "offline" && enableStatusNotifications {
sendServiceNotification(service: port.service, hostname: hostname, status: "online")
if currentStatus == "offline" {
serviceFailureStartedAt[key] = now
serviceOfflineAlerted[key] = false
} else if currentStatus == "online" {
let hadOfflineAlert = serviceOfflineAlerted[key] == true
serviceFailureStartedAt.removeValue(forKey: key)
serviceOfflineAlerted.removeValue(forKey: key)
if hadOfflineAlert && enableStatusNotifications {
sendServiceNotification(service: port.service, hostname: hostname, status: "online")
}
}
} else if previousStatus == nil {
if currentStatus == "offline" {
serviceFailureStartedAt[key] = now
serviceOfflineAlerted[key] = false
} else if currentStatus == "online" {
serviceFailureStartedAt.removeValue(forKey: key)
}
}
if currentStatus == "offline" {
let startedAt = serviceFailureStartedAt[key] ?? now
if serviceFailureStartedAt[key] == nil {
serviceFailureStartedAt[key] = startedAt
}
if serviceOfflineAlerted[key] != true,
enableAlertNotifications,
now.timeIntervalSince(startedAt) >= TimeInterval(alertGracePeriod) {
sendServiceNotification(service: port.service, hostname: hostname, status: "offline")
serviceOfflineAlerted[key] = true
}
}
previousServiceStates[key] = currentStatus
}
}