feat: add alerting system with configurable rules and UI (MON-02, MON-03)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-11 12:28:44 +00:00
parent 592548066e
commit 1b8a8cfd32
7 changed files with 989 additions and 5 deletions

View File

@@ -436,6 +436,10 @@ impl RpcHandler {
"monitoring.current" => self.handle_monitoring_current().await,
"monitoring.history" => self.handle_monitoring_history(params).await,
"monitoring.containers" => self.handle_monitoring_containers().await,
"monitoring.alerts" => self.handle_monitoring_alerts(params).await,
"monitoring.alert-rules" => self.handle_monitoring_alert_rules().await,
"monitoring.configure-alert" => self.handle_monitoring_configure_alert(params).await,
"monitoring.acknowledge-alert" => self.handle_monitoring_acknowledge_alert(params).await,
// System updates
"update.check" => self.handle_update_check().await,

View File

@@ -1,4 +1,5 @@
use super::RpcHandler;
use crate::monitoring::AlertRuleKind;
use anyhow::Result;
use tracing::debug;
@@ -59,4 +60,76 @@ impl RpcHandler {
None => Ok(serde_json::json!({ "containers": [] })),
}
}
/// monitoring.alerts — get fired alert history
pub(super) async fn handle_monitoring_alerts(
&self,
params: Option<serde_json::Value>,
) -> Result<serde_json::Value> {
debug!("Getting alert history");
let count = params
.as_ref()
.and_then(|p| p.get("count"))
.and_then(|v| v.as_u64())
.unwrap_or(50) as usize;
let alerts = self.metrics_store.get_fired_alerts(count).await;
Ok(serde_json::json!({
"count": alerts.len(),
"alerts": alerts,
}))
}
/// monitoring.alert-rules — get current alert rules
pub(super) async fn handle_monitoring_alert_rules(&self) -> Result<serde_json::Value> {
debug!("Getting alert rules");
let rules = self.metrics_store.get_alert_rules().await;
Ok(serde_json::json!({ "rules": rules }))
}
/// monitoring.configure-alert — update an alert rule
pub(super) async fn handle_monitoring_configure_alert(
&self,
params: Option<serde_json::Value>,
) -> Result<serde_json::Value> {
let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
let kind_str = params
.get("kind")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing 'kind' parameter"))?;
let kind: AlertRuleKind = serde_json::from_value(serde_json::json!(kind_str))
.map_err(|_| anyhow::anyhow!("Invalid alert kind: {}", kind_str))?;
let enabled = params.get("enabled").and_then(|v| v.as_bool());
let threshold = params.get("threshold").and_then(|v| v.as_f64());
self.metrics_store
.update_alert_rule(&kind, enabled, threshold)
.await;
debug!("Updated alert rule: {:?}", kind);
Ok(serde_json::json!({ "updated": true, "kind": kind_str }))
}
/// monitoring.acknowledge-alert — acknowledge a fired alert
pub(super) async fn handle_monitoring_acknowledge_alert(
&self,
params: Option<serde_json::Value>,
) -> Result<serde_json::Value> {
let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
let alert_id = params
.get("id")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing 'id' parameter"))?;
let found = self.metrics_store.acknowledge_alert(alert_id).await;
Ok(serde_json::json!({ "acknowledged": found, "id": alert_id }))
}
}