docs(openspec): add SDD artifacts for next changes

- jellyfin-service-registry: proposal, design, and tasks for completing
  the Jellyfin migration off machine-level config.
- grafana-prometheus-polish: proposal, design, and tasks for improving
  the Grafana/Prometheus observability integration.

Both are planning-only artifacts; implementation not started.
This commit is contained in:
Developer
2026-06-23 20:40:35 +00:00
parent ca8927834e
commit 4d520ab0e3
6 changed files with 391 additions and 0 deletions
@@ -0,0 +1,64 @@
# Design — Grafana/Prometheus integration polish
**Change:** `grafana-prometheus-polish`
**Phase:** design
**Date:** 2026-06-23
## Current state
- Backend integrations: `integrations/grafana.py` (config `base_url`, secret `api_key`, widget `link`) and `integrations/prometheus.py` (config `base_url`, secret `api_key`, widget `metric`).
- Backend widget sources: `widgets/sources.py` has `GrafanaWidgetSource` and `PrometheusWidgetSource`.
- Frontend `SERVICE_REGISTRY` (`frontend/src/integrations/registry.ts`) mirrors those widget kinds.
- Observability page (`frontend/src/components/ObservabilityPage.tsx`) uses hard-coded `VITE_GRAFANA_URL` for Grafana links and shows only Alertmanager + Prometheus targets.
- Monitoring router (`backend/src/media_library_viewer_api/routers/monitoring.py`) exposes `/alerts`, `/alertmanager-status`, `/alertmanager-webhook`, `/prometheus-targets`, `/machines`.
## Target state
### Backend
1. **New health endpoints**
- `GET /api/monitoring/grafana-status` — probes `GET {base_url}/api/health` (no auth needed for health) and returns `{up, version, error?}`.
- `GET /api/monitoring/prometheus-status` — probes `GET {base_url}/-/healthy` and returns `{up, version, error?}`.
- If multiple instances of a type exist, probe the first enabled instance and include its `service_id` + `name` in the response.
- If no instance is configured, return `up: false, error: "no_service_configured"`.
2. **New widget kinds**
- Grafana: add `explore_link` kind with config `query` (optional), `datasource` (optional). Renders a deep-link to `/explore?orgId=1&left={...}` or similar.
- Prometheus: add `range_chart` kind with config `promql`, `step` (e.g. `1m`), `range_seconds` (e.g. `3600`). Renders `{ series: [{ timestamp, value }] }` for the frontend to chart. The adapter performs a range query (`/api/v1/query_range`) and reshapes the result.
3. **Source adapters**
- Extend `GrafanaWidgetSource.fetch` to handle `explore_link`.
- Extend `PrometheusWidgetSource.fetch` to handle `range_chart`.
### Frontend
1. **Observability page**
- Replace hard-coded `GRAFANA_BASE_URL` with discovery from `useServiceInstances("grafana")`.
- Add `useGrafanaStatus()` and `usePrometheusStatus()` hooks using the new endpoints.
- Add health cards for Grafana and Prometheus next to Alertmanager.
- Grafana links (alerts panel, targets panel) use the discovered instance URL.
2. **Widget registry**
- Add `explore_link` to `SERVICE_REGISTRY.grafana.widgets`.
- Add `range_chart` to `SERVICE_REGISTRY.prometheus.widgets`.
- Add presentational components `GrafanaExploreLinkWidget` and `PrometheusRangeChartWidget` (or extend existing components).
3. **Types**
- Add status response types and widget config types.
### Docs
- Update `docs/REQUIREMENTS.md` observability section to describe service-instance-driven Grafana/Prometheus status and new widget kinds.
- `CHANGELOG.md` added/changed note.
## Slices
1. **Backend health endpoints + widget kind extensions.**
2. **Frontend Observability page + widget registry + new components.**
3. **Docs + changelog.**
## Risks
- Grafana Explore URL encoding is version-dependent. Target Grafana 10+ and document the assumption.
- Prometheus range queries can be expensive; cap `range_seconds` in the adapter (e.g. max 24h) and use a modest `step`.
- Multiple Grafana/Prometheus instances: keep it simple — use the first enabled instance for status; widgets already bind to a specific service instance.
@@ -0,0 +1,44 @@
# Proposal — Grafana/Prometheus integration polish
**Change:** `grafana-prometheus-polish`
**Phase:** proposal
**Date:** 2026-06-23
## Problem
Grafana and Prometheus are already modeled as service-registry service types and provide dashboard widgets (`GrafanaLinkWidget`, `PrometheusMetricWidget`). The Observability page already shows Alertmanager alerts, Prometheus targets, and a hard-coded Grafana base URL (`VITE_GRAFANA_URL`).
However, the integration is still piecemeal:
1. **Hard-coded Grafana URL** in `frontend/src/components/ObservabilityPage.tsx` (`GRAFANA_BASE_URL = import.meta.env.VITE_GRAFANA_URL || "http://localhost:3000"`). This ignores any Grafana service instances configured in the registry.
2. **No Grafana health/status card** on the Observability page. Users cannot see whether their configured Grafana instance is reachable.
3. **No Prometheus health/status card**. The targets endpoint shows scrape config, but not whether Prometheus itself is up.
4. **Prometheus widget only supports instant queries**. There is no range-query chart widget or table widget.
5. **Grafana widget only supports dashboard deep-links**. There is no "open Explore with a query" shortcut.
6. **Observability page mixes service-bound and hard-coded sources**. It should derive Grafana/Prometheus URLs from configured service instances instead of env vars.
## Goal
Polish the Grafana/Prometheus integration so the Observability page and dashboard widgets feel first-class:
1. Observability page discovers configured Grafana and Prometheus service instances and uses their `base_url` instead of `VITE_GRAFANA_URL`.
2. Add health/status cards for Grafana and Prometheus (reachable/unreachable + version if available).
3. Add backend health-check endpoints for Grafana (`/api/monitoring/grafana-status`) and Prometheus (`/api/monitoring/prometheus-status`) that proxy a lightweight health endpoint.
4. Extend widget kinds:
- Prometheus: add `range_chart` widget kind for simple sparkline charts.
- Grafana: add `explore_link` widget kind to open Grafana Explore with a pre-filled query.
5. Keep the existing `link` and `metric` widgets backward-compatible.
## Non-goals
- Do NOT build a full in-app metric explorer or charting library.
- Do NOT store Prometheus query history or persist widget state beyond existing dashboard widgets.
- Do NOT replace the Alertmanager proxy or Prometheus targets endpoint.
## Acceptance
- `VITE_GRAFANA_URL` is no longer required; the Observability page falls back to configured Grafana instances.
- `/api/monitoring/grafana-status` and `/api/monitoring/prometheus-status` return `{up, version, error?}`.
- New widget kinds are registered in backend integrations and frontend `SERVICE_REGISTRY`.
- Frontend lint/build/test and backend pytest remain green.
- Docs/CHANGELOG updated.
@@ -0,0 +1,106 @@
# Tasks — Grafana/Prometheus integration polish
**Change:** `grafana-prometheus-polish`
**Phase:** tasks
**Date:** 2026-06-23
## Review workload forecast
| Field | Value |
|-------|-------|
| Estimated changed lines | ~400600 |
| Chained PRs recommended | Yes (2 PRs) |
| Chain strategy | stacked-to-main |
## Slice 1: Backend — health endpoints + widget extensions
**Goal:** Surface Grafana/Prometheus health and extend widget kinds.
- [ ] **1.1 Add Grafana status endpoint**
- Files: `backend/src/media_library_viewer_api/routers/monitoring.py`
- Lines: ~35
- Details: `GET /api/monitoring/grafana-status` discovers the first enabled `grafana` service, probes `/api/health`, returns `{ up, version, service_id, name, error? }`.
- [ ] **1.2 Add Prometheus status endpoint**
- Files: `backend/src/media_library_viewer_api/routers/monitoring.py`
- Lines: ~35
- Details: `GET /api/monitoring/prometheus-status` discovers the first enabled `prometheus` service, probes `/-/healthy` and `/api/v1/status/buildinfo` for version, returns `{ up, version, service_id, name, error? }`.
- [ ] **1.3 Add `explore_link` widget kind to Grafana integration**
- Files: `backend/src/media_library_viewer_api/integrations/grafana.py`, `backend/src/media_library_viewer_api/widgets/sources.py`
- Lines: ~30
- Details: New `GrafanaExploreWidgetConfig` with `query` and `datasource`; `GrafanaWidgetSource.fetch` builds an `/explore` deep-link.
- [ ] **1.4 Add `range_chart` widget kind to Prometheus integration**
- Files: `backend/src/media_library_viewer_api/integrations/prometheus.py`, `backend/src/media_library_viewer_api/widgets/sources.py`
- Lines: ~60
- Details: New `PrometheusRangeChartWidgetConfig` with `promql`, `range_seconds`, `step`; adapter calls `/api/v1/query_range`, caps `range_seconds` at 86400, reshapes to `{ series: [{ timestamp, value }] }`.
- [ ] **1.5 Update backend tests**
- Files: `backend/tests/test_api.py`, `backend/tests/test_widgets.py`
- Lines: ~40
- Details: Test status endpoints with mocked HTTP; test new widget kinds.
- [ ] **1.6 Verify backend**
- Run: `cd backend && .venv/bin/ruff check . && PYTHONPATH=src .venv/bin/python -m pytest`
**Slice 1 total:** ~200 changed lines.
## Slice 2: Frontend — Observability page + widget registry
**Goal:** Remove hard-coded Grafana URL and surface new widgets.
- [ ] **2.1 Add status hooks**
- Files: `frontend/src/hooks/useObservability.ts`, `frontend/src/api/client.ts`
- Lines: ~30
- Details: `fetchGrafanaStatus`, `fetchPrometheusStatus`, `useGrafanaStatus()`, `usePrometheusStatus()`.
- [ ] **2.2 Update Observability page**
- Files: `frontend/src/components/ObservabilityPage.tsx`
- Lines: ~80
- Details: Discover Grafana/Prometheus services; replace `GRAFANA_BASE_URL`; add health cards; use discovered URLs for links.
- [ ] **2.3 Extend widget registry**
- Files: `frontend/src/integrations/registry.ts`, `frontend/src/widgets/*`
- Lines: ~80
- Details: Add `GrafanaExploreLinkWidget` and `PrometheusRangeChartWidget` (or extend existing); register new kinds; update `registry.test.ts`.
- [ ] **2.4 Update types**
- Files: `frontend/src/types/index.ts`
- Lines: ~20
- Details: Add `GrafanaStatus`, `PrometheusStatus`, and widget config types.
- [ ] **2.5 Update frontend tests**
- Files: `frontend/src/components/__tests__/ObservabilityPage.test.tsx` if it exists, or relevant widget tests
- Lines: ~30
- Details: Mock new hooks/services; assert health cards render.
- [ ] **2.6 Verify frontend**
- Run: `cd frontend && npm run lint && npm run build && npm run test`
**Slice 2 total:** ~240 changed lines.
## Slice 3: Docs + changelog
**Goal:** Document the polished integration.
- [ ] **3.1 Update REQUIREMENTS.md**
- Files: `docs/REQUIREMENTS.md`
- Lines: ~20
- Details: Observability section describes service-instance-driven Grafana/Prometheus status cards and new widget kinds.
- [ ] **3.2 Update CHANGELOG.md**
- Files: `CHANGELOG.md`
- Lines: ~15
- Details: Added status endpoints, removed hard-coded `VITE_GRAFANA_URL` dependency, new widget kinds.
- [ ] **3.3 Commit**
- Commit message: `feat(observability): polish Grafana/Prometheus integration`
## Acceptance
- `/api/monitoring/grafana-status` and `/api/monitoring/prometheus-status` return sensible JSON for configured/missing/unreachable services.
- Observability page no longer references `VITE_GRAFANA_URL`.
- New widget kinds appear in the "Add widget" flow and render data.
- Backend and frontend test suites pass.
- Docs and changelog updated.