Files
manage/openspec/changes/configurable-dashboard-widgets/apply-progress.md
Developer 1cd8e926de feat(widgets): add backend source adapters and per-widget data endpoint
PR 2 of 4 for configurable dashboard widgets.

- Add grafana_url and prometheus_url settings (config.py + compose/env).
- Create WidgetSource protocol and adapters for jellyfin, backups, grafana,
  prometheus, ssh_task, and static sources.
- Add GET /api/widgets/instances/{id}/data endpoint.
- Extract shared dashboard helpers into domain/dashboard.py so widgets and
  the dashboard router reuse the same logic.
- Add adapter and data-endpoint tests.
- Update apply-progress.md.

Verification: ruff clean; backend pytest 200 passed; frontend lint/build green.
2026-06-21 10:09:45 +00:00

5.1 KiB

Apply Progress: Configurable Dashboard Widgets

Change: configurable-dashboard-widgets
Apply run: PR 1 / Slice 1 — Backend CRUD and default seeding
Date: 2026-06-19

Completed tasks (Slice 1)

All Slice 1 tasks are marked - [x] in tasks.md:

  • 1.1 Create widget Pydantic models
  • 1.2 Create backend widget registry
  • 1.3 Implement widgets router (CRUD + metadata)
  • 1.4 Extend SettingsStore for dashboard_widgets
  • 1.5 Register widgets router in main.py
  • 1.6 Add backend tests for registry, CRUD, and seeding
  • 1.7 Verify backend slice

Files changed

New files

  • backend/src/media_library_viewer_api/models/widgets.py — Pydantic models: WidgetInstance, WidgetInstanceInput, WidgetTypeInfo, WidgetDataResponse, plus credential-key/secret-value validators.
  • backend/src/media_library_viewer_api/widgets/__init__.py — Package marker.
  • backend/src/media_library_viewer_api/widgets/registry.py — Closed WIDGET_REGISTRY for six Phase 1 widget types, source-type listing, type metadata, and lightweight config-schema validation.
  • backend/src/media_library_viewer_api/routers/widgets.py — REST endpoints for /api/widgets/sources, /types, /instances, and instance CRUD.
  • backend/tests/test_widgets.py — 12 tests covering registry, CRUD, validation, and seeding.

Modified files

  • backend/src/media_library_viewer_api/services/settings_store.py — Added dashboard_widgets table, index, CRUD helpers, default seeding, and refactored ensure_defaults() to seed widgets independently of machine seeding.
  • backend/src/media_library_viewer_api/main.py — Registered widgets_router.

Verification

Commands run:

cd backend
.venv/bin/python -m ruff check .              # All checks passed
PYTHONPATH=src .venv/bin/python -m pytest     # 185 passed, 2 warnings
cd ../frontend
npm run lint                                  # 2 pre-existing warnings, 0 errors
npm run build                                 # Built successfully

Focused widget test output: 12 passed.

Deviations from design

  • None significant for Slice 1. The implementation follows the design's backend CRUD layout.
  • Used HTTP_422_UNPROCESSABLE_CONTENT instead of the deprecated HTTP_422_UNPROCESSABLE_ENTITY.

Completed tasks (Slice 2)

All Slice 2 tasks are marked - [x] in tasks.md:

  • 2.1 Add observability URL settings (grafana_url, prometheus_url)
  • 2.2 Create source adapters (jellyfin, backups, grafana, prometheus, ssh_task, static)
  • 2.3 Add per-widget data endpoint (GET /api/widgets/instances/{id}/data)
  • 2.4 Extract shared backup/Jellyfin dashboard helpers into domain/dashboard.py
  • 2.5 Add adapter + data endpoint tests

Files changed (Slice 2)

New files

  • backend/src/media_library_viewer_api/widgets/sources.pyWidgetSource protocol and six source adapters.
  • backend/src/media_library_viewer_api/domain/dashboard.py — Shared dashboard helpers (_map_sessions_to_activity_rows, build_backup_dashboard_summary).

Modified files

  • backend/src/media_library_viewer_api/config.py — Added grafana_url and prometheus_url settings.
  • backend/src/media_library_viewer_api/routers/widgets.py — Added GET /api/widgets/instances/{id}/data.
  • backend/src/media_library_viewer_api/routers/dashboard.py — Delegated to shared domain/dashboard.py helpers.
  • backend/tests/test_widgets.py — Added adapter and data endpoint tests.
  • docker-compose.yml, docker-compose.dev.yml, .env.example — Wired GRAFANA_URL and PROMETHEUS_URL for the new adapters.

Verification (Slice 2)

cd backend
.venv/bin/python -m ruff check .              # All checks passed
PYTHONPATH=src .venv/bin/python -m pytest     # 200 passed, 2 warnings
cd ../frontend
npm run lint                                  # 2 pre-existing warnings, 0 errors
npm run build                                 # Built successfully

Focused widget test output: 27 passed.

Deviations from design (Slice 2)

  • Adapters currently call get_settings_store() internally for backups/ssh_task sources. The router-level endpoint uses FastAPI DI, but adapter unit tests patch get_settings_store to inject a test store. A future refactor can pass store and settings explicitly into adapter.fetch() for cleaner testability.

Remaining work

  • Slice 3: Frontend types/API/hooks/registry/components
  • Slice 4: Dashboard loop + configuration UI + addon pages

PR boundary

This slice is PR 1 of 4 in the approved stacked-to-main chain. It is backend-only and leaves the frontend build/lint green.

Actual changed-line count: ~780 added lines across production code and tests (new files: ~597 lines; modified files: ~181 insertions). This is above the nominal ~400-line review budget, but Slice 1 is the smallest coherent backend unit: removing the CRUD router, store helpers, or tests would leave the slice non-functional or unverifiable. If the reviewer prefers a smaller blast radius, the store helpers (~90 lines) could be split into a preceding PR, though that PR would not be independently user-visible.