Rename grafana-prometheus-polish -> observability-service-registry and rewrite proposal/design/tasks for the approved vision: all observability integration (alertmanager, grafana, prometheus) configured as service- registry instances in the UI, surfaced on a dedicated page, with widgets per service definition -- nothing in the env. Key scope decisions captured: - Add alertmanager as a 6th service type + active_alerts widget. - Rewire /alerts + /alertmanager-status to resolve from service records (first-enabled-instance default; no primary flag in v1). - Add /grafana-status + /prometheus-status health endpoints. - Observability page discovers services; kill VITE_GRAFANA_URL / VITE_PROMETHEUS_URL deep-links. - Webhook receiver stays log-only (drop the outbound forward). - Remove PROMETHEUS_FILE_SD_DIR + the file-writer; external Prometheus uses http_sd_configs against GET /api/monitoring/prometheus-targets. build_node_exporter_targets + that endpoint stay. - PROMETHEUS_ENABLED stays (Manage's own /metrics toggle). - End state: zero observability *service* env vars. Plan = 5 slices, each <=400 changed lines, green tests/lint/build, commit per slice.
6.5 KiB
Proposal — Observability service registry
Change: observability-service-registry
Phase: proposal
Date: 2026-06-23
Problem
Grafana and Prometheus are already modeled as service-registry service types
with dashboard widgets (GrafanaLinkWidget, PrometheusMetricWidget) whose
adapters resolve URLs from service records — not env vars. The Observability
page, however, still treats observability as an env-configured afterthought:
- Alertmanager is not a service type. It has no
integrations/alertmanager.pydefinition. Its connection URL comes from theALERTMANAGER_URLenvironment variable (config.py), read directly by the monitoring router. This is the only observability component still env-bound, and it cannot be configured, named, multi-instanced, or surfaced as a widget like Grafana/Prometheus. - The Observability page hard-codes
VITE_GRAFANA_URL(ObservabilityPage.tsx→GRAFANA_BASE_URL = import.meta.env.VITE_GRAFANA_URL). It ignores any Grafana service instances configured in the registry, so the deep-links break unless the env var is also set. - No Grafana/Prometheus health card. The page shows Alertmanager health and Prometheus targets, but never reports whether the configured Grafana or Prometheus instance is actually reachable.
- Two env-bound relay knobs with no service-registry equivalent:
ALERTMANAGER_WEBHOOK_URL(the router forwards received webhooks onward) and the leftoverVITE_PROMETHEUS_URLbuild arg.
The result is a split-brain model: Grafana/Prometheus are first-class registry services, but Alertmanager is an env var and the page reads a different env var for Grafana than the registry holds.
Proposal
Make all observability integration (Alertmanager, Grafana, Prometheus) a first-class, UI-configured part of the existing service registry, with a dedicated Observability page and dashboard widgets — and zero observability service env vars.
- Add
alertmanageras a 6th service integration type (integrations/alertmanager.py): config (base_url,timeout_seconds), optional secret (api_key/bearer token for auth'd instances), and anactive_alertswidget kind. Register it inintegrations/registry.py, following the exact pattern ofgrafana.py/prometheus.py. - Rewire the monitoring router to resolve Alertmanager from a service record
instead of
config.alertmanager_url./alertsand/alertmanager-statusaccept an optional?service_id=and default to the first enabledalertmanagerinstance. Graceful "not configured" responses are preserved when none exists. - Add health/status endpoints for Grafana (
/api/monitoring/grafana-status) and Prometheus (/api/monitoring/prometheus-status) that resolve a service record and probe its health endpoint, returning{ up, version, service_id, name, error? }. - Observability page discovers services from the registry. Grafana deep-links
come from the discovered instance's
base_url;VITE_GRAFANA_URLis removed. Health cards for Alertmanager, Grafana, and Prometheus are shown. - Add an
active_alertsAlertmanager dashboard widget (backend adapter + frontend registry + component), mirroring the Grafana/Prometheus widgets. - Delete observability service env vars entirely: backend
alertmanager_url,alertmanager_webhook_url; frontendVITE_GRAFANA_URL,VITE_PROMETHEUS_URL. Drop the inbound→outbound webhook relay (the receiver stays, log-only).
Non-goals
- Do not build an in-app metric explorer or charting library. Manage stays a thin dashboard: deep-links/iframe to Grafana only.
- Do not introduce a "primary/default instance" flag on service records in this change. Default selection is "first enabled instance of type" (see Open questions). A default flag is a cross-cutting registry feature deferred to a later change.
- Do not remove the Prometheus target data path:
build_node_exporter_targetsand the/prometheus-targetsHTTP endpoint stay. Only the file writer (write_prometheus_targets+prometheus_file_sd_dir) is removed; external Prometheus useshttp_sd_configsagainst the endpoint instead. - Do not persist Prometheus query history or alert acknowledgements in Manage.
- Do not migrate the existing
/machines+ Node Exporter target model; it is orthogonal to service instances.
Acceptance
alertmanagerappears inGET /api/services/typesand can be created/edited/deleted like the other service types.ALERTMANAGER_URL,ALERTMANAGER_WEBHOOK_URL,VITE_GRAFANA_URL, andVITE_PROMETHEUS_URLhave no remaining references in backend source, frontend source, or the root compose files./alertsand/alertmanager-statuswork against a configured alertmanager service instance and return the existing "not configured" shape when none is configured./api/monitoring/grafana-statusand/prometheus-statusreturn sensible JSON for configured / missing / unreachable services.- The Observability page renders Alertmanager, Grafana, and Prometheus health cards and builds Grafana deep-links from the discovered service instance.
- An
active_alertswidget can be added to the dashboard bound to an alertmanager service and renders firing alerts. - Backend
pytest+ruffgreen; frontendnpm run lint,npm run build(tsc -b + vite build),npm run testgreen. docs/REQUIREMENTS.mddecision log,CHANGELOG.md, andbackend/README.mdupdated.
Impact
- Backend: new integration module; monitoring router rewire + two new
endpoints; config.py loses three fields (
alertmanager_url,alertmanager_webhook_url,prometheus_file_sd_dir) and the file-writer call sites; widget adapter + registry. - Frontend: Observability page rewire (service discovery, new health cards,
Grafana URL source); new alertmanager widget component + registry entry; types;
removal of two
import.meta.envreads. - Config/deploy: root compose files and Dockerfile build args lose the
observability service env vars and the
prometheus-file-sdvolume mount;.env.exampleneeds a manual update (assistant-edit-blocked — tracked as a task follow-up). - Compatibility:
ALERTMANAGER_URLusers must re-create their Alertmanager as a service instance after upgrading; admins usingfile_sd_configsagainst the shared volume must switch tohttp_sd_configsagainst/api/monitoring/prometheus-targets. Both breaking, called out in CHANGELOG.