pulseengine_mcp_server/
alerting_endpoint.rs

1//! Alerting management endpoints
2
3use axum::{
4    Router,
5    extract::{Path, State},
6    http::StatusCode,
7    response::{IntoResponse, Json},
8    routing::{get, post},
9};
10use pulseengine_mcp_logging::{AlertManager, AlertSeverity, AlertState};
11use serde::{Deserialize, Serialize};
12use std::sync::Arc;
13use uuid::Uuid;
14
15/// Alert manager state
16pub struct AlertingState {
17    pub alert_manager: Arc<AlertManager>,
18}
19
20/// Alert summary for API responses
21#[derive(Debug, Serialize, Deserialize)]
22pub struct AlertSummary {
23    pub total_active: usize,
24    pub by_severity: std::collections::HashMap<AlertSeverity, usize>,
25    pub by_state: std::collections::HashMap<AlertState, usize>,
26}
27
28/// Alert acknowledgment request
29#[derive(Debug, Deserialize)]
30pub struct AcknowledgeRequest {
31    pub acknowledged_by: String,
32    pub comment: Option<String>,
33}
34
35/// Alert resolution request
36#[derive(Debug, Deserialize)]
37pub struct ResolveRequest {
38    pub resolved_by: String,
39    pub comment: Option<String>,
40}
41
42/// Get alert summary
43pub async fn get_alert_summary(State(state): State<Arc<AlertingState>>) -> impl IntoResponse {
44    let active_alerts = state.alert_manager.get_active_alerts().await;
45
46    let mut by_severity = std::collections::HashMap::new();
47    let mut by_state = std::collections::HashMap::new();
48
49    for alert in &active_alerts {
50        *by_severity.entry(alert.severity.clone()).or_insert(0) += 1;
51        *by_state.entry(alert.state.clone()).or_insert(0) += 1;
52    }
53
54    let summary = AlertSummary {
55        total_active: active_alerts.len(),
56        by_severity,
57        by_state,
58    };
59
60    (StatusCode::OK, Json(summary))
61}
62
63/// Get active alerts
64pub async fn get_active_alerts(State(state): State<Arc<AlertingState>>) -> impl IntoResponse {
65    let alerts = state.alert_manager.get_active_alerts().await;
66    (StatusCode::OK, Json(alerts))
67}
68
69/// Get alert history
70pub async fn get_alert_history(State(state): State<Arc<AlertingState>>) -> impl IntoResponse {
71    let history = state.alert_manager.get_alert_history().await;
72    (StatusCode::OK, Json(history))
73}
74
75/// Get specific alert by ID
76pub async fn get_alert(
77    Path(alert_id): Path<Uuid>,
78    State(state): State<Arc<AlertingState>>,
79) -> impl IntoResponse {
80    let active_alerts = state.alert_manager.get_active_alerts().await;
81
82    if let Some(alert) = active_alerts.iter().find(|a| a.id == alert_id) {
83        (StatusCode::OK, Json(alert.clone())).into_response()
84    } else {
85        // Check history
86        let history = state.alert_manager.get_alert_history().await;
87        if let Some(alert) = history.iter().find(|a| a.id == alert_id) {
88            (StatusCode::OK, Json(alert.clone())).into_response()
89        } else {
90            (
91                StatusCode::NOT_FOUND,
92                Json(serde_json::json!({
93                    "error": "Alert not found",
94                    "alert_id": alert_id
95                })),
96            )
97                .into_response()
98        }
99    }
100}
101
102/// Acknowledge an alert
103pub async fn acknowledge_alert(
104    Path(alert_id): Path<Uuid>,
105    State(state): State<Arc<AlertingState>>,
106    Json(request): Json<AcknowledgeRequest>,
107) -> impl IntoResponse {
108    match state
109        .alert_manager
110        .acknowledge_alert(alert_id, request.acknowledged_by)
111        .await
112    {
113        Ok(()) => (
114            StatusCode::OK,
115            Json(serde_json::json!({
116                "success": true,
117                "message": "Alert acknowledged"
118            })),
119        )
120            .into_response(),
121        Err(e) => (
122            StatusCode::BAD_REQUEST,
123            Json(serde_json::json!({
124                "error": e.to_string(),
125                "alert_id": alert_id
126            })),
127        )
128            .into_response(),
129    }
130}
131
132/// Resolve an alert
133pub async fn resolve_alert(
134    Path(alert_id): Path<Uuid>,
135    State(state): State<Arc<AlertingState>>,
136    Json(_request): Json<ResolveRequest>,
137) -> impl IntoResponse {
138    match state.alert_manager.resolve_alert(alert_id).await {
139        Ok(()) => (
140            StatusCode::OK,
141            Json(serde_json::json!({
142                "success": true,
143                "message": "Alert resolved"
144            })),
145        )
146            .into_response(),
147        Err(e) => (
148            StatusCode::BAD_REQUEST,
149            Json(serde_json::json!({
150                "error": e.to_string(),
151                "alert_id": alert_id
152            })),
153        )
154            .into_response(),
155    }
156}
157
158/// Create alerting router
159pub fn create_alerting_router(alert_manager: Arc<AlertManager>) -> Router {
160    let state = Arc::new(AlertingState { alert_manager });
161
162    Router::new()
163        .route("/alerts/summary", get(get_alert_summary))
164        .route("/alerts/active", get(get_active_alerts))
165        .route("/alerts/history", get(get_alert_history))
166        .route("/alerts/:id", get(get_alert))
167        .route("/alerts/:id/acknowledge", post(acknowledge_alert))
168        .route("/alerts/:id/resolve", post(resolve_alert))
169        .with_state(state)
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175    use axum::http::StatusCode;
176    use axum_test::TestServer;
177    use pulseengine_mcp_logging::{Alert, AlertConfig};
178
179    #[tokio::test]
180    async fn test_alert_summary_endpoint() {
181        let config = AlertConfig::default();
182        let manager = Arc::new(AlertManager::new(config));
183        let router = create_alerting_router(manager);
184
185        let server = TestServer::new(router).unwrap();
186        let response = server.get("/alerts/summary").await;
187
188        assert_eq!(response.status_code(), StatusCode::OK);
189
190        let summary: AlertSummary = response.json();
191        assert_eq!(summary.total_active, 0);
192    }
193
194    #[tokio::test]
195    async fn test_active_alerts_endpoint() {
196        let config = AlertConfig::default();
197        let manager = Arc::new(AlertManager::new(config));
198        let router = create_alerting_router(manager);
199
200        let server = TestServer::new(router).unwrap();
201        let response = server.get("/alerts/active").await;
202
203        assert_eq!(response.status_code(), StatusCode::OK);
204
205        let alerts: Vec<Alert> = response.json();
206        assert!(alerts.is_empty());
207    }
208}