"""Alertmanager service definition.""" from __future__ import annotations from typing import TYPE_CHECKING, Any import requests from media_library_viewer_api.integrations.base import ( SecretField, ServiceBaseUrl, ServiceConfigBase, ServiceDefinition, TestResult, WidgetConfigBase, translate_connection_error, widget_kind, ) if TYPE_CHECKING: from media_library_viewer_api.services.settings_store import SettingsStore class AlertmanagerConfig(ServiceConfigBase): """Non-secret Alertmanager connection config.""" base_url: ServiceBaseUrl timeout_seconds: int = 15 class AlertmanagerAlertsWidgetConfig(WidgetConfigBase): """Active-alerts summary for an Alertmanager instance.""" severity_filter: str | None = None def summarize_alerts( alerts: list[dict[str, Any]], *, severity_filter: str | None = None, ) -> dict[str, Any]: """Build a UI-friendly summary from an Alertmanager ``/api/v1/alerts`` list. Reshapes the raw alert objects into a stable summary (``total``, ``by_severity``, top-50 ``alerts``). When ``severity_filter`` is given, only alerts whose ``labels.severity`` matches are counted. """ by_severity: dict[str, int] = {} open_alerts: list[dict[str, Any]] = [] for alert in alerts: labels = alert.get("labels") or {} annotations = alert.get("annotations") or {} severity = labels.get("severity", "unknown") if severity_filter and severity != severity_filter: continue by_severity[severity] = by_severity.get(severity, 0) + 1 open_alerts.append( { "name": labels.get("alertname", "unknown"), "severity": severity, "category": labels.get("category", ""), "job_name": labels.get("job_name", labels.get("job", "")), "summary": annotations.get("summary", ""), "description": annotations.get("description", ""), "active_since": alert.get("startsAt"), "state": alert.get("status", "firing"), "labels": labels, } ) open_alerts.sort(key=lambda a: (a["severity"] not in {"critical", "warning"}, a["severity"], a["name"])) return { "total": len(open_alerts), "by_severity": by_severity, "alerts": open_alerts[:50], } def test_connection( config: dict[str, Any], secrets: dict[str, str], store: SettingsStore, ) -> TestResult: """GET /api/v2/status with optional bearer auth.""" try: base_url = str(config.get("base_url") or "").rstrip("/") timeout = int(config.get("timeout_seconds") or 15) headers: dict[str, str] = {} api_key = str(secrets.get("api_key") or "") if api_key: headers["Authorization"] = f"Bearer {api_key}" resp = requests.get(f"{base_url}/api/v2/status", headers=headers, timeout=timeout) resp.raise_for_status() payload = resp.json() version = str(payload.get("versionInfo", {}).get("version", "") or "connected") return TestResult(ok=True, detail="Connected to Alertmanager.", evidence=version) except Exception as exc: return translate_connection_error(exc, context="Alertmanager") DEFINITION = ServiceDefinition( service_type="alertmanager", name="Alertmanager", description="Alertmanager alerts and status.", config_model=AlertmanagerConfig, secret_fields=[ SecretField(key="api_key", label="API key", helper="Optional bearer token"), ], widget_kinds=[ widget_kind( kind="active_alerts", name="Active alerts", description="Firing alerts summary from Alertmanager.", model_cls=AlertmanagerAlertsWidgetConfig, default_config={}, refresh_interval_ms=30_000, ), ], test_callable=test_connection, )