044d386ac7
Every HTTP client passed an integer timeout to requests, applying the same value to BOTH connect and read phases. A slow Jellyfin /Items page or qBit /sync/maindata blew through the 10s read budget → ReadTimeoutError. Split into a (connect=5s, read=60s default) tuple via shared http_timeout() helper. The media index build worker uses a 180s read floor. Existing services with low timeout_seconds benefit from bumping to 60+.
120 lines
3.9 KiB
Python
120 lines
3.9 KiB
Python
"""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,
|
|
)
|