diff --git a/openspec/changes/grafana-prometheus-polish/design.md b/openspec/changes/grafana-prometheus-polish/design.md new file mode 100644 index 0000000..2583617 --- /dev/null +++ b/openspec/changes/grafana-prometheus-polish/design.md @@ -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. diff --git a/openspec/changes/grafana-prometheus-polish/proposal.md b/openspec/changes/grafana-prometheus-polish/proposal.md new file mode 100644 index 0000000..f9a5535 --- /dev/null +++ b/openspec/changes/grafana-prometheus-polish/proposal.md @@ -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. diff --git a/openspec/changes/grafana-prometheus-polish/tasks.md b/openspec/changes/grafana-prometheus-polish/tasks.md new file mode 100644 index 0000000..5f9b700 --- /dev/null +++ b/openspec/changes/grafana-prometheus-polish/tasks.md @@ -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 | ~400–600 | +| 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. diff --git a/openspec/changes/jellyfin-service-registry/design.md b/openspec/changes/jellyfin-service-registry/design.md new file mode 100644 index 0000000..33b29a4 --- /dev/null +++ b/openspec/changes/jellyfin-service-registry/design.md @@ -0,0 +1,50 @@ +# Design — Jellyfin service-registry migration (completion) + +**Change:** `jellyfin-service-registry` +**Phase:** design +**Date:** 2026-06-23 + +## Current state + +- Jellyfin/Jellyseerr service definitions exist in `integrations/jellyfin.py` and `integrations/jellyseerr.py`. +- `dependencies.py` resolves Jellyfin clients from service records via `jellyfin_service_id`. +- Frontend pages already select Jellyfin services via `useServiceInstances("jellyfin")`. +- `settings_store.py` still defaults machine `services` to `["monitoring", "files", "jellyfin"]` and persists `media_root` / `path_prefix`. +- `MonitoringMachine` / `MonitoringMachineInput` types still include `media_root` and `path_prefix`. +- `Settings.tsx` still renders `media_root` and `path_prefix` inputs and lists `jellyfin` as a service option. +- `path_utils.py` still has path-mapping helpers, but the callers in `files.py` no longer depend on machine-level `media_root`/`path_prefix`. + +## Target state + +### Backend + +- `DEFAULT_SERVICES` becomes `["monitoring", "files"]`. +- `settings_store.py` drops `media_root` and `path_prefix` from `_normalize_machine_payload`, `_row_to_machine`, the machine table schema, and default seeding. +- Add `DROP TABLE IF EXISTS ...` or `ALTER TABLE DROP COLUMN` cleanup as needed for existing databases (SQLite has limited `DROP COLUMN` support; prefer table rebuild or leave harmless columns). +- `path_utils.py` is reviewed; if no callers remain, delete the module. If callers remain, ensure they do not reference machine fields. +- `routers/files.py` confirmed to not read machine `media_root`/`path_prefix`. +- Tests updated to remove `media_root`/`path_prefix` from fixtures. + +### Frontend + +- Remove `media_root` and `path_prefix` from `MonitoringMachine` and `MonitoringMachineInput` in `types/index.ts`. +- Remove `jellyfin` from the machine services multi-select in `Settings.tsx`. +- Remove `media_root` and `path_prefix` inputs from the machine editor. +- Update `emptyMachine()` to not include these fields. +- Update tests/fixtures. + +### Docs + +- `docs/REQUIREMENTS.md` — update the machine config description to remove Jellyfin/path-prefix fields. +- `CHANGELOG.md` — breaking note about removed machine fields. + +## Slices + +1. **Backend cleanup** — `settings_store.py`, `config.py` (if any env vars), `path_utils.py`, tests. +2. **Frontend cleanup** — types, `Settings.tsx`, tests. +3. **Docs + changelog**. + +## Risks + +- Existing DB rows may have `media_root`/`path_prefix` values; dropping columns is safe but irreversible. Backups recommended. +- `path_utils.py` may still be referenced by archive code; verify only live code is affected. diff --git a/openspec/changes/jellyfin-service-registry/proposal.md b/openspec/changes/jellyfin-service-registry/proposal.md new file mode 100644 index 0000000..47717ad --- /dev/null +++ b/openspec/changes/jellyfin-service-registry/proposal.md @@ -0,0 +1,41 @@ +# Proposal — Jellyfin service-registry migration (completion) + +**Change:** `jellyfin-service-registry` +**Phase:** proposal +**Date:** 2026-06-23 + +## Problem + +The service-registry foundation already moved Jellyfin/Jellyseerr resolution into `dependencies.py` (`get_jellyfin_client`, `get_jellyseerr_client`, `get_user_id`) using `jellyfin_service_id`. The frontend `Applications`, `Media`, and `Users` pages already consume `useServiceInstances("jellyfin")`. + +However, the **machine-level config still carries legacy Jellyfin DNA** that is now dead or misleading: + +- `DEFAULT_SERVICES = ["monitoring", "files", "jellyfin"]` in `settings_store.py` auto-tags every local machine as a Jellyfin source. +- The Settings machine editor still exposes `services` multi-select that can include `jellyfin`, even though Jellyfin is selected via the Services page, not via a machine. +- `MonitoringMachine` types still carry `media_root` and `path_prefix` fields whose original purpose was Jellyfin→SSH path mapping, but the path-mapping logic in `path_utils.py` now operates independently and those fields are no longer read by the media/file flows. +- Documentation still describes machine-level Jellyfin fields in places. + +This creates UI confusion (two places to think about Jellyfin) and stale surface area. + +## Goal + +Cleanly complete the Jellyfin migration onto the service registry: + +1. Remove Jellyfin from the machine `services` taxonomy entirely. +2. Remove dead `media_root` / `path_prefix` machine fields from backend, frontend types, Settings form, and tests. +3. Update path-resolution helpers to rely only on the Jellyfin service config (or explicit path-prefix settings on the service record if needed), not on machine fields. +4. Update docs and changelog. + +## Non-goals + +- Do NOT change how Jellyfin/Jellyseerr clients are resolved in `dependencies.py` — that is already correct. +- Do NOT remove the `jellyfin` / `jellyseerr` service definitions. +- Do NOT add new service types or widgets in this change. + +## Acceptance + +- `grep -R "media_root\|path_prefix" backend/src frontend/src` returns only `path_utils.py` and its tests (if those helpers remain) or nothing (if the helpers are removed). +- `jellyfin` no longer appears in machine `services` options in the Settings UI. +- `DEFAULT_SERVICES` no longer includes `jellyfin`. +- Backend and frontend test suites remain green. +- Docs/CHANGELOG explain the final migration. diff --git a/openspec/changes/jellyfin-service-registry/tasks.md b/openspec/changes/jellyfin-service-registry/tasks.md new file mode 100644 index 0000000..f3baad2 --- /dev/null +++ b/openspec/changes/jellyfin-service-registry/tasks.md @@ -0,0 +1,86 @@ +# Tasks — Jellyfin service-registry migration (completion) + +**Change:** `jellyfin-service-registry` +**Phase:** tasks +**Date:** 2026-06-23 + +## Review workload forecast + +| Field | Value | +|-------|-------| +| Estimated changed lines | ~250–350 | +| Chained PRs recommended | No (single PR) | +| Chain strategy | n/a | + +## Slice 1: Backend cleanup + +**Goal:** Remove Jellyfin and dead path-mapping fields from machine config. + +- [ ] **1.1 Drop `jellyfin` from default machine services** + - Files: `backend/src/media_library_viewer_api/services/settings_store.py` + - Lines: ~5 + - Details: Change `DEFAULT_SERVICES = ["monitoring", "files", "jellyfin"]` to `["monitoring", "files"]`. Update all fallback/default service lists. + +- [ ] **1.2 Remove `media_root` and `path_prefix` machine fields** + - Files: `backend/src/media_library_viewer_api/services/settings_store.py` + - Lines: ~40 + - Details: Remove from machine table schema (if present), `_normalize_machine_payload`, `_row_to_machine`, default seeding, and any serialization helpers. + +- [ ] **1.3 Review/delete `path_utils.py`** + - Files: `backend/src/media_library_viewer_api/path_utils.py`, `backend/tests/test_path_utils.py` + - Lines: ~-120 if deleted, else ~10 to confirm callers + - Details: Check if any live router still imports the helpers. If not, delete module and tests. + +- [ ] **1.4 Update backend tests** + - Files: `backend/tests/test_api.py`, `backend/tests/test_widgets.py`, others as needed + - Lines: ~20 + - Details: Remove `media_root`/`path_prefix` from machine fixtures. + +- [ ] **1.5 Verify backend** + - Run: `cd backend && .venv/bin/ruff check . && PYTHONPATH=src .venv/bin/python -m pytest` + +## Slice 2: Frontend cleanup + +**Goal:** Remove dead machine fields and the Jellyfin service option from Settings. + +- [ ] **2.1 Update types** + - Files: `frontend/src/types/index.ts` + - Lines: ~10 + - Details: Remove `media_root` and `path_prefix` from `MonitoringMachine` and `MonitoringMachineInput`. + +- [ ] **2.2 Update Settings form** + - Files: `frontend/src/pages/Settings.tsx` + - Lines: ~60 + - Details: Remove `media_root` and `path_prefix` inputs, remove `jellyfin` from the services multi-select options, update `emptyMachine()` and edit reset logic. + +- [ ] **2.3 Update frontend tests** + - Files: `frontend/src/pages/__tests__/Settings.test.tsx` if it exists, or other fixtures + - Lines: ~15 + - Details: Remove fields from mock machines. + +- [ ] **2.4 Verify frontend** + - Run: `cd frontend && npm run lint && npm run build && npm run test` + +## Slice 3: Docs + changelog + +**Goal:** Document the completed migration and removed fields. + +- [ ] **3.1 Update REQUIREMENTS.md** + - Files: `docs/REQUIREMENTS.md` + - Lines: ~10 + - Details: Remove references to machine-level `media_root`/`path_prefix` and Jellyfin service tagging. + +- [ ] **3.2 Update CHANGELOG.md** + - Files: `CHANGELOG.md` + - Lines: ~10 + - Details: Add a breaking/changed note: machine config no longer includes `media_root`, `path_prefix`, or `jellyfin` in `services`; Jellyfin is configured exclusively via Services. + +- [ ] **3.3 Commit** + - Commit message: `refactor(settings): complete Jellyfin migration onto service registry` + +## Acceptance + +- `grep -R "media_root\|path_prefix" backend/src frontend/src` returns only `path_utils.py` and tests if retained, or nothing if deleted. +- `jellyfin` is not offered as a machine service in the Settings UI. +- Backend `pytest` and frontend `npm run lint/build/test` all pass. +- Docs and changelog updated.