Files
gronodandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> a7f13542ae feat(alerts): implement threshold alert engine and Notification Center integration (fixes #24)
Introduce Sources/Intelligence with an @Observable AlertEngine that evaluates
five configurable rules (CPU temperature, fan stall, low memory, low storage,
low battery) against a Sendable AlertEvaluationContext built after each
telemetry decode pass. Breached conditions dispatch through a mockable
AlertNotificationDispatching protocol to UNUserNotificationCenter with
per-alert cooldowns, edge-triggered resolve detection, and a capped
in-memory history log.

Add a macOS Settings scene (AlertsSettingsView) for thresholds, cooldown,
permission status, and test notifications; a new Alerts and Notifications
sidebar tab with active-alert cards and the history log; header and menu bar
warning indicators; and an AppNavigationBus so notification actions
(Open MacMonitor, View Processes) steer the main window.

Also fix the memory snapshot decode to read memoryPressureStatus so
pressureLevel is populated correctly.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-08 13:53:48 +01:00

68 lines
2.5 KiB
Swift

import Foundation
// MARK: - Alert Configuration
/// User-tunable threshold and cooldown settings for `AlertEngine`.
/// Persisted as a single JSON blob in `UserDefaults` so the engine can read
/// configuration outside of a SwiftUI context.
public struct AlertConfiguration: Codable, Equatable, Sendable {
/// Global switch — when false, no rules are evaluated.
public var alertsEnabled: Bool = true
/// Minimum minutes between two notifications for the same alert identifier
/// while a condition remains breached.
public var cooldownMinutes: Double = 15
// MARK: CPU Temperature
public var cpuTempEnabled: Bool = true
/// Degrees Celsius; breach when the highest core/package reading reaches this.
public var cpuTempThresholdC: Double = 95
// MARK: Fan Stall
public var fanStallEnabled: Bool = true
/// Degrees Celsius; a fan reading 0 RPM only alerts while the CPU is at or
/// above this temperature (distinguishes genuine stalls from idle spin-down).
public var fanStallTempThresholdC: Double = 75
// MARK: Memory Pressure
public var memoryEnabled: Bool = true
/// Free physical RAM threshold in megabytes.
public var memoryFreeMBThreshold: Double = 500
/// Also alert whenever the kernel pressure tier reports "Critical".
public var memoryAlertOnCriticalPressure: Bool = true
// MARK: Storage
public var storageEnabled: Bool = true
/// Free-space threshold per volume in gigabytes.
public var storageFreeGBThreshold: Double = 10
/// Free-space threshold per volume as a percentage of capacity.
public var storageFreePercentThreshold: Double = 10
// MARK: Battery
public var batteryEnabled: Bool = true
/// Charge percentage; only evaluated while discharging on battery power.
public var batteryPercentThreshold: Double = 10
public init() {}
}
// MARK: - UserDefaults Persistence
public extension AlertConfiguration {
static let storageKey = "com.i3omb.macmonitor.alertConfiguration"
static func load(from defaults: UserDefaults = .standard) -> AlertConfiguration {
guard let data = defaults.data(forKey: storageKey),
let config = try? JSONDecoder().decode(AlertConfiguration.self, from: data) else {
return AlertConfiguration()
}
return config
}
func save(to defaults: UserDefaults = .standard) {
if let data = try? JSONEncoder().encode(self) {
defaults.set(data, forKey: AlertConfiguration.storageKey)
}
}
}