# Tasks — Observability service registry **Change:** `observability-service-registry` **Phase:** tasks **Date:** 2026-06-23 ## Review workload forecast | Field | Value | |-------|-------| | Estimated changed lines | ~790 | | Chained PRs recommended | Yes (5 slices) | | Chain strategy | stacked-to-main | | Slice order | 1 (alertmanager type) → 2 (router rewire + status) → 3 (frontend) → 4 (drop file-SD writer) → 5 (env removal + docs) | Each slice is committed separately (user pref). Every slice must leave `cd backend && .venv/bin/ruff check . && .venv/bin/python -m pytest` **and** `cd frontend && npm run lint && npm run build && npm run test` green. --- ## Slice 1: Backend — add `alertmanager` service type + widget **Goal:** Alertmanager becomes a first-class registry service with an `active_alerts` widget, mirroring Grafana/Prometheus. - [ ] **1.1 Add integration module** - Files: `backend/src/media_library_viewer_api/integrations/alertmanager.py` (new) - Lines: ~45 - Details: `AlertmanagerConfig` (`base_url`, `timeout_seconds=5`), `AlertmanagerAlertsWidgetConfig` (`severity_filter` optional), `DEFINITION` with `service_type="alertmanager"`, secret `api_key`, widget kind `active_alerts`. Follow `prometheus.py` exactly. - [ ] **1.2 Register the type** - Files: `backend/src/media_library_viewer_api/integrations/registry.py` - Lines: ~2 - Details: import `DEFINITION as ALERTMANAGER`; add to `SERVICE_DEFINITIONS`. - [ ] **1.3 Add widget source adapter** - Files: `backend/src/media_library_viewer_api/widgets/sources.py` - Lines: ~40 - Details: `AlertmanagerWidgetSource` — reuse `_summary_from_alerts` (move it to a shared import or keep in monitoring router and import). `fetch` calls `{base_url}/api/v1/alerts` with optional bearer token, optional `severity_filter`, returns the summary shape. Register in `SERVICE_ADAPTERS`. - [ ] **1.4 Update backend tests** - Files: `backend/tests/test_services.py`, `backend/tests/test_widgets.py` - Lines: ~50 - Details: assert `alertmanager` in service types (test count becomes 6); test the adapter with mocked HTTP (alerts summary) and missing service. - [ ] **1.5 Verify backend** - Run: `cd backend && .venv/bin/ruff check . && .venv/bin/python -m pytest` - [ ] **1.6 Commit** - Message: `feat(observability): add alertmanager service type and widget` **Slice 1 total:** ~140 changed lines. --- ## Slice 2: Backend — rewire monitoring router + health endpoints **Goal:** Alertmanager is resolved from a service record; Grafana/Prometheus health endpoints added; webhook relay dropped. - [ ] **2.1 Add service resolution helper** - Files: `backend/src/media_library_viewer_api/routers/monitoring.py` - Lines: ~20 - Details: `_resolve_service_record(store, service_type, service_id)` returning the requested or first-enabled `ServiceRecord` (reuses `build_service_record` from `widgets/sources.py`). Delete `_alertmanager_client`/`_webhook_client` env readers. - [ ] **2.2 Rewire `/alerts` and `/alertmanager-status`** - Files: `backend/src/media_library_viewer_api/routers/monitoring.py` - Lines: ~40 - Details: `Depends(get_settings_store)`; optional `service_id` query param; resolve via helper; attach bearer token; on `None` return not-configured body; add `service_id` + `name` to success responses; add `name`/`peers` to down-branches (fix type drift). - [ ] **2.3 Make webhook receiver log-only** - Files: `backend/src/media_library_viewer_api/routers/monitoring.py` - Lines: ~-15 (net delete) - Details: remove the outbound forward `POST` + `_webhook_client`; keep the receiver logging received alerts and returning `{"status": "received"}`. - [ ] **2.4 Add grafana/prometheus status endpoints** - Files: `backend/src/media_library_viewer_api/routers/monitoring.py` - Lines: ~60 - Details: `GET /api/monitoring/grafana-status?service_id=` (probe `/api/health`), `GET /api/monitoring/prometheus-status?service_id=` (probe `/-/healthy` + `/api/v1/status/buildinfo`). Return `{ up, version, service_id, name, error? }`; none-configured → `error: "no_service_configured"`. - [ ] **2.5 Update backend tests** - Files: `backend/tests/test_api.py` - Lines: ~70 - Details: rewire existing `TestAlertmanager` to seed an alertmanager service instance instead of mocking `_alertmanager_client`; add not-configured (no instance) cases; add `TestGrafanaStatus` / `TestPrometheusStatus` (configured, missing, unreachable); assert webhook receiver is log-only. - [ ] **2.6 Verify backend** - Run: `cd backend && .venv/bin/ruff check . && .venv/bin/python -m pytest` - [ ] **2.7 Commit** - Message: `feat(observability): resolve services from registry, add health endpoints` **Slice 2 total:** ~210 changed lines. --- ## Slice 3: Frontend — service discovery, health cards, alertmanager widget **Goal:** Observability page discovers services; `VITE_*` URL reads removed; alertmanager widget added. (If this slice exceeds ~400 lines, split 3a page / 3b widget — see note.) - [ ] **3.1 Add status hooks + client fns** - Files: `frontend/src/api/client.ts`, `frontend/src/hooks/useObservability.ts` - Lines: ~30 - Details: `fetchGrafanaStatus(serviceId?)`, `fetchPrometheusStatus(serviceId?)`, `useGrafanaStatus`, `usePrometheusStatus`. - [ ] **3.2 Rewire Observability page** - Files: `frontend/src/components/ObservabilityPage.tsx` - Lines: ~90 - Details: `useServiceInstances("grafana")` → derive `GRAFANA_BASE_URL` from first enabled instance; remove `import.meta.env.VITE_GRAFANA_URL`; empty-state linking to `/services` when none; add Grafana + Prometheus `HealthCard`s. - [ ] **3.3 Add alertmanager widget** - Files: `frontend/src/widgets/AlertmanagerAlertsWidget.tsx` (new), `frontend/src/integrations/registry.ts`, `frontend/src/widgets/index.ts` - Lines: ~90 - Details: presentational component reusing the alert summary shape; register `alertmanager` binding with `active_alerts` kind. - [ ] **3.4 Update types** - Files: `frontend/src/types/index.ts` - Lines: ~20 - Details: `GrafanaStatus`, `PrometheusStatus`; add `service_id`/`name` to status types; `AlertmanagerAlertsWidgetConfig`. - [ ] **3.5 Update frontend tests** - Files: `frontend/src/integrations/registry.test.ts`, widget/page tests as needed - Lines: ~40 - Details: assert alertmanager binding resolves; mock status hooks. - [ ] **3.6 Verify frontend** - Run: `cd frontend && npm run lint && npm run build && npm run test` - [ ] **3.7 Commit** - Message: `feat(observability): service discovery, health cards, alertmanager widget` **Slice 3 total:** ~270 changed lines. --- ## Slice 4: Backend — remove file-SD writer **Goal:** Drop the shared-file Prometheus bridge (`PROMETHEUS_FILE_SD_DIR`); external Prometheus uses `http_sd_configs` against the existing endpoint. - [ ] **4.1 Remove the file-writer call sites** - Files: `backend/src/media_library_viewer_api/main.py` (startup call), `backend/src/media_library_viewer_api/routers/settings.py` (post machine create/update/delete calls) - Lines: ~-15 - Details: delete `write_prometheus_targets(...)` invocations; remove the import. **Keep** `services/targets.py::build_node_exporter_targets` and the `GET /api/monitoring/prometheus-targets` endpoint (external Prometheus consumes via `http_sd_configs`). - [ ] **4.2 Remove the file-writer + config field** - Files: `backend/src/media_library_viewer_api/services/targets.py`, `backend/src/media_library_viewer_api/config.py` - Lines: ~-30 - Details: delete `write_prometheus_targets` (the writer) from `targets.py`, leaving `build_node_exporter_targets`; delete `prometheus_file_sd_dir` from `config.py`. Prune now-unused imports (`Path`, etc.). - [ ] **4.3 Remove compose mount + env var** - Files: `docker-compose.yml`, `docker-compose.dev.yml` - Lines: ~-4 - Details: drop `PROMETHEUS_FILE_SD_DIR` backend env var and any `prometheus-file-sd` volume mount/bind reference (no Prometheus container remains to read it). - [ ] **4.4 Update backend tests** - Files: `backend/tests/test_targets.py`, `backend/tests/test_api.py` - Lines: ~-20 / ~+5 - Details: drop tests covering `write_prometheus_targets`; keep/extend tests for `build_node_exporter_targets` and the `/prometheus-targets` endpoint. Drop any `write_targets.assert_called_once` assertion in `TestSettingsMachines`. - [ ] **4.5 Verify backend** - Run: `cd backend && .venv/bin/ruff check . && .venv/bin/python -m pytest` - [ ] **4.6 Commit** - Message: `refactor(observability): drop file-SD writer for http_sd_configs` **Slice 4 total:** ~70 changed lines (net negative). --- ## Slice 5: Env removal + docs **Goal:** Delete the remaining observability service env vars from config/compose and document the change. - [ ] **5.1 Remove backend env fields** - Files: `backend/src/media_library_viewer_api/config.py` - Lines: ~-2 - Details: delete `alertmanager_url` and `alertmanager_webhook_url` fields. - [ ] **5.2 Remove compose / Dockerfile build args** - Files: `docker-compose.yml`, `docker-compose.dev.yml`, `frontend/Dockerfile` - Lines: ~-8 - Details: drop `ALERTMANAGER_URL`, `ALERTMANAGER_WEBHOOK_URL` from backend env; drop `VITE_GRAFANA_URL`, `VITE_PROMETHEUS_URL` from frontend build args (both compose files) and `ARG`/`ENV` (Dockerfile). - [ ] **5.3 Update docs** - Files: `docs/REQUIREMENTS.md`, `CHANGELOG.md`, `backend/README.md`, `docs/monitoring-logging-design.md` - Lines: ~60 - Details: REQUIREMENTS decision-log entry (alertmanager as a service type; observability env vars removed incl. `PROMETHEUS_FILE_SD_DIR`; http_sd_configs replaces the file bridge; thin-dashboard model unchanged); CHANGELOG added/changed/**BREAKING** (`ALERTMANAGER_URL` users re-create the instance; `file_sd_configs` users switch to `http_sd_configs`); backend README monitoring section. - [ ] **5.4 Manual follow-up (assistant-blocked) — `.env.example`** - `.env.example` is blocked by the safety policy. Note for the user: remove `ALERTMANAGER_URL`, `ALERTMANAGER_WEBHOOK_URL`, `VITE_GRAFANA_URL`, `VITE_PROMETHEUS_URL`, and `PROMETHEUS_FILE_SD_DIR`; keep `PROMETHEUS_ENABLED`. - No code lines. - [ ] **5.5 Verify** - Run: `cd backend && .venv/bin/ruff check . && .venv/bin/python -m pytest` - Run: `cd frontend && npm run lint && npm run build && npm run test` - Grep-gate: confirm no remaining references to the removed env vars in `backend/src`, `frontend/src`, root compose files, Dockerfile. - [ ] **5.6 Commit** - Message: `chore(observability): remove remaining observability env vars, docs` **Slice 5 total:** ~110 changed lines. --- ## Acceptance - `alertmanager` is a service type; observability services are UI-configured only. - The observability service env vars (`ALERTMANAGER_URL`, `ALERTMANAGER_WEBHOOK_URL`, `VITE_GRAFANA_URL`, `VITE_PROMETHEUS_URL`, `PROMETHEUS_FILE_SD_DIR`) have no remaining references. `PROMETHEUS_ENABLED` (Manage's own `/metrics` toggle) is the only observability env var that remains. - `/alerts`, `/alertmanager-status`, `/grafana-status`, `/prometheus-status` work against service instances with graceful missing/unreachable states. - Observability page shows Alertmanager/Grafana/Prometheus health and derives Grafana links from the registry (no `VITE_*` URL). - `active_alerts` alertmanager widget renders on the dashboard. - External Prometheus consumes node-exporter targets via `http_sd_configs` against `/api/monitoring/prometheus-targets`; no shared volume remains. - All backend and frontend test suites green; docs + CHANGELOG updated.