mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-05-22 07:34:47 +02:00
- Full Alerts page with active alerts, history table, subscriptions - Active alert cards with severity styling and acknowledge button - Alert history table with type/severity filtering and pagination - Subscription viewer showing mesh subscriptions - ToastProvider for app-wide toast notifications - Toast notifications triggered on WebSocket alert_fired messages - Auto-dismiss toasts after 8 seconds, click to navigate - Page titles on all pages (Dashboard/Mesh/Environment/Config/Alerts) - Improved alert_routes.py with proper pending alert handling - Added AlertHistoryItem, Subscription types to api.ts - Added fetchAlertHistory, fetchSubscriptions functions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
99 lines
2.8 KiB
Python
99 lines
2.8 KiB
Python
"""Alert API routes."""
|
|
|
|
from fastapi import APIRouter, Request, Query
|
|
from typing import Optional
|
|
|
|
router = APIRouter(tags=["alerts"])
|
|
|
|
|
|
@router.get("/alerts/active")
|
|
async def get_active_alerts(request: Request):
|
|
"""Get currently active alerts."""
|
|
alert_engine = getattr(request.app.state, "alert_engine", None)
|
|
|
|
if not alert_engine:
|
|
return []
|
|
|
|
alerts = []
|
|
|
|
# Try get_pending_alerts first (our method)
|
|
if hasattr(alert_engine, "get_pending_alerts"):
|
|
try:
|
|
raw_alerts = alert_engine.get_pending_alerts()
|
|
for alert in raw_alerts:
|
|
alerts.append({
|
|
"type": alert.get("type", "unknown"),
|
|
"severity": _map_severity(alert),
|
|
"message": alert.get("message", ""),
|
|
"timestamp": alert.get("timestamp"),
|
|
"scope_type": alert.get("scope_type"),
|
|
"scope_value": alert.get("scope_value"),
|
|
})
|
|
except Exception:
|
|
pass
|
|
|
|
return alerts
|
|
|
|
|
|
@router.get("/alerts/history")
|
|
async def get_alert_history(
|
|
request: Request,
|
|
limit: int = Query(50, ge=1, le=200),
|
|
offset: int = Query(0, ge=0),
|
|
type: Optional[str] = Query(None),
|
|
severity: Optional[str] = Query(None),
|
|
):
|
|
"""Get historical alerts with pagination and filtering.
|
|
|
|
Note: Alert history persistence is not yet implemented.
|
|
Returns empty array for now.
|
|
"""
|
|
# Future: Query SQLite for historical alerts
|
|
# For now, return empty with proper structure
|
|
return {
|
|
"items": [],
|
|
"total": 0,
|
|
}
|
|
|
|
|
|
@router.get("/subscriptions")
|
|
async def get_subscriptions(request: Request):
|
|
"""Get all alert subscriptions."""
|
|
subscription_manager = getattr(request.app.state, "subscription_manager", None)
|
|
|
|
if not subscription_manager:
|
|
return []
|
|
|
|
try:
|
|
subs = subscription_manager.get_all_subs()
|
|
return [
|
|
{
|
|
"id": sub["id"],
|
|
"user_id": sub["user_id"],
|
|
"sub_type": sub["sub_type"],
|
|
"schedule_time": sub.get("schedule_time"),
|
|
"schedule_day": sub.get("schedule_day"),
|
|
"scope_type": sub.get("scope_type", "mesh"),
|
|
"scope_value": sub.get("scope_value"),
|
|
"enabled": sub.get("enabled", 1) == 1,
|
|
}
|
|
for sub in subs
|
|
]
|
|
except Exception:
|
|
return []
|
|
|
|
|
|
def _map_severity(alert: dict) -> str:
|
|
"""Map alert properties to severity level."""
|
|
if alert.get("is_critical"):
|
|
return "critical"
|
|
alert_type = alert.get("type", "")
|
|
if "emergency" in alert_type:
|
|
return "emergency"
|
|
if "critical" in alert_type:
|
|
return "critical"
|
|
if "warning" in alert_type:
|
|
return "warning"
|
|
if "watch" in alert_type:
|
|
return "watch"
|
|
return "info"
|