- Archive the completed observability-service-registry SDD change into openspec/changes/archive/ (delivered across 5 slices; only jellyfin-service-registry remains active). - Stop ignoring .pi-map.md / .pi-map.index.md so the navigation maps are versioned alongside the code, and add the regenerated map pairs repo-wide.
12 KiB
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_filteroptional),DEFINITIONwithservice_type="alertmanager", secretapi_key, widget kindactive_alerts. Followprometheus.pyexactly.
- Files:
-
1.2 Register the type
- Files:
backend/src/media_library_viewer_api/integrations/registry.py - Lines: ~2
- Details: import
DEFINITION as ALERTMANAGER; add toSERVICE_DEFINITIONS.
- Files:
-
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).fetchcalls{base_url}/api/v1/alertswith optional bearer token, optionalseverity_filter, returns the summary shape. Register inSERVICE_ADAPTERS.
- Files:
-
1.4 Update backend tests
- Files:
backend/tests/test_services.py,backend/tests/test_widgets.py - Lines: ~50
- Details: assert
alertmanagerin service types (test count becomes 6); test the adapter with mocked HTTP (alerts summary) and missing service.
- Files:
-
1.5 Verify backend
- Run:
cd backend && .venv/bin/ruff check . && .venv/bin/python -m pytest
- Run:
-
1.6 Commit
- Message:
feat(observability): add alertmanager service type and widget
- Message:
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-enabledServiceRecord(reusesbuild_service_recordfromwidgets/sources.py). Delete_alertmanager_client/_webhook_clientenv readers.
- Files:
-
2.2 Rewire
/alertsand/alertmanager-status- Files:
backend/src/media_library_viewer_api/routers/monitoring.py - Lines: ~40
- Details:
Depends(get_settings_store); optionalservice_idquery param; resolve via helper; attach bearer token; onNonereturn not-configured body; addservice_id+nameto success responses; addname/peersto down-branches (fix type drift).
- Files:
-
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"}.
- Files:
-
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".
- Files:
-
2.5 Update backend tests
- Files:
backend/tests/test_api.py - Lines: ~70
- Details: rewire existing
TestAlertmanagerto seed an alertmanager service instance instead of mocking_alertmanager_client; add not-configured (no instance) cases; addTestGrafanaStatus/TestPrometheusStatus(configured, missing, unreachable); assert webhook receiver is log-only.
- Files:
-
2.6 Verify backend
- Run:
cd backend && .venv/bin/ruff check . && .venv/bin/python -m pytest
- Run:
-
2.7 Commit
- Message:
feat(observability): resolve services from registry, add health endpoints
- Message:
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.
- Files:
-
3.2 Rewire Observability page
- Files:
frontend/src/components/ObservabilityPage.tsx - Lines: ~90
- Details:
useServiceInstances("grafana")→ deriveGRAFANA_BASE_URLfrom first enabled instance; removeimport.meta.env.VITE_GRAFANA_URL; empty-state linking to/serviceswhen none; add Grafana + PrometheusHealthCards.
- Files:
-
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
alertmanagerbinding withactive_alertskind.
- Files:
-
3.4 Update types
- Files:
frontend/src/types/index.ts - Lines: ~20
- Details:
GrafanaStatus,PrometheusStatus; addservice_id/nameto status types;AlertmanagerAlertsWidgetConfig.
- Files:
-
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.
- Files:
-
3.6 Verify frontend
- Run:
cd frontend && npm run lint && npm run build && npm run test
- Run:
-
3.7 Commit
- Message:
feat(observability): service discovery, health cards, alertmanager widget
- Message:
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. Keepservices/targets.py::build_node_exporter_targetsand theGET /api/monitoring/prometheus-targetsendpoint (external Prometheus consumes viahttp_sd_configs).
- Files:
-
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) fromtargets.py, leavingbuild_node_exporter_targets; deleteprometheus_file_sd_dirfromconfig.py. Prune now-unused imports (Path, etc.).
- Files:
-
4.3 Remove compose mount + env var
- Files:
docker-compose.yml,docker-compose.dev.yml - Lines: ~-4
- Details: drop
PROMETHEUS_FILE_SD_DIRbackend env var and anyprometheus-file-sdvolume mount/bind reference (no Prometheus container remains to read it).
- Files:
-
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 forbuild_node_exporter_targetsand the/prometheus-targetsendpoint. Drop anywrite_targets.assert_called_onceassertion inTestSettingsMachines.
- Files:
-
4.5 Verify backend
- Run:
cd backend && .venv/bin/ruff check . && .venv/bin/python -m pytest
- Run:
-
4.6 Commit
- Message:
refactor(observability): drop file-SD writer for http_sd_configs
- Message:
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_urlandalertmanager_webhook_urlfields.
- Files:
-
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_URLfrom backend env; dropVITE_GRAFANA_URL,VITE_PROMETHEUS_URLfrom frontend build args (both compose files) andARG/ENV(Dockerfile).
- Files:
-
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_URLusers re-create the instance;file_sd_configsusers switch tohttp_sd_configs); backend README monitoring section.
- Files:
-
5.4 Manual follow-up (assistant-blocked) —
.env.example.env.exampleis blocked by the safety policy. Note for the user: removeALERTMANAGER_URL,ALERTMANAGER_WEBHOOK_URL,VITE_GRAFANA_URL,VITE_PROMETHEUS_URL, andPROMETHEUS_FILE_SD_DIR; keepPROMETHEUS_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.
- Run:
-
5.6 Commit
- Message:
chore(observability): remove remaining observability env vars, docs
- Message:
Slice 5 total: ~110 changed lines.
Acceptance
alertmanageris 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/metricstoggle) is the only observability env var that remains. /alerts,/alertmanager-status,/grafana-status,/prometheus-statuswork 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_alertsalertmanager widget renders on the dashboard.- External Prometheus consumes node-exporter targets via
http_sd_configsagainst/api/monitoring/prometheus-targets; no shared volume remains. - All backend and frontend test suites green; docs + CHANGELOG updated.