ed7a7a5ce0
PR 4 of 4 for configurable dashboard widgets. - Replace hard-coded Jellyfin/Backups dashboard sections with a loop that renders enabled widget instances by sort_order. - Add WidgetInstance renderer and WidgetConfigDialog for adding, editing, enabling/disabling, deleting, and reordering widgets. - Add addon pages for grafana, prometheus, and ssh-tasks at /addons/:addonId. - Register /addons/:addonId route in App.tsx. - Update docs/REQUIREMENTS.md with the widget system design and API. Verification: - backend ruff clean; pytest 200 passed - frontend npm run lint: 0 errors - frontend npm run build: success - frontend npm run test -- src/widgets/registry.test.ts: 3 passed
199 lines
9.9 KiB
Markdown
199 lines
9.9 KiB
Markdown
# 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`:
|
|
|
|
- [x] 1.1 Create widget Pydantic models
|
|
- [x] 1.2 Create backend widget registry
|
|
- [x] 1.3 Implement widgets router (CRUD + metadata)
|
|
- [x] 1.4 Extend `SettingsStore` for `dashboard_widgets`
|
|
- [x] 1.5 Register widgets router in `main.py`
|
|
- [x] 1.6 Add backend tests for registry, CRUD, and seeding
|
|
- [x] 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:
|
|
|
|
```bash
|
|
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`:
|
|
|
|
- [x] 2.1 Add observability URL settings (`grafana_url`, `prometheus_url`)
|
|
- [x] 2.2 Create source adapters (`jellyfin`, `backups`, `grafana`, `prometheus`, `ssh_task`, `static`)
|
|
- [x] 2.3 Add per-widget data endpoint (`GET /api/widgets/instances/{id}/data`)
|
|
- [x] 2.4 Extract shared backup/Jellyfin dashboard helpers into `domain/dashboard.py`
|
|
- [x] 2.5 Add adapter + data endpoint tests
|
|
|
|
## Files changed (Slice 2)
|
|
|
|
### New files
|
|
|
|
- `backend/src/media_library_viewer_api/widgets/sources.py` — `WidgetSource` 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)
|
|
|
|
```bash
|
|
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.
|
|
|
|
## Completed tasks (Slice 3)
|
|
|
|
All Slice 3 tasks are marked `- [x]` in `tasks.md`:
|
|
|
|
- [x] 3.1 Add TypeScript widget interfaces (`WidgetInstance`, `WidgetInstanceInput`, `WidgetTypeInfo`, `WidgetDataResponse`)
|
|
- [x] 3.2 Create widget API client (`frontend/src/api/widgets.ts`)
|
|
- [x] 3.3 Create widget TanStack Query hooks (`frontend/src/hooks/useWidgets.ts`)
|
|
- [x] 3.4 Create frontend widget registry (`frontend/src/widgets/registry.ts`)
|
|
- [x] 3.5 Implement six widget presentational components (`frontend/src/widgets/*.tsx`)
|
|
- [x] 3.6 Add frontend registry unit test (`frontend/src/widgets/registry.test.ts`)
|
|
|
|
## Files changed (Slice 3)
|
|
|
|
### New files
|
|
|
|
- `frontend/src/api/widgets.ts` — API functions for widget CRUD, registry metadata, and per-widget data.
|
|
- `frontend/src/hooks/useWidgets.ts` — TanStack Query hooks for instances, data, sources, types, and mutations.
|
|
- `frontend/src/widgets/registry.ts` — Closed frontend registry with metadata, refresh intervals, and config fields.
|
|
- `frontend/src/widgets/JellyfinWidget.tsx` — Renders Jellyfin session activity.
|
|
- `frontend/src/widgets/BackupsWidget.tsx` — Renders backup dashboard summary.
|
|
- `frontend/src/widgets/GrafanaLinkWidget.tsx` — Renders a deep-link to Grafana (no iframe).
|
|
- `frontend/src/widgets/PrometheusMetricWidget.tsx` — Renders PromQL instant query result.
|
|
- `frontend/src/widgets/SshTaskWidget.tsx` — Renders saved SSH task output.
|
|
- `frontend/src/widgets/StaticWidget.tsx` — Renders static text.
|
|
- `frontend/src/widgets/index.ts` — Barrel exports.
|
|
- `frontend/src/widgets/registry.test.ts` — Vitest unit tests for registry metadata.
|
|
|
|
### Modified files
|
|
|
|
- `frontend/src/types/index.ts` — Added widget TypeScript interfaces.
|
|
|
|
## Verification (Slice 3)
|
|
|
|
```bash
|
|
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
|
|
npm run test -- src/widgets/registry.test.ts # 3 passed
|
|
```
|
|
|
|
## Deviations from design (Slice 3)
|
|
|
|
- Registry unit test is colocated at `frontend/src/widgets/registry.test.ts` and runs with Vitest, matching the project's existing `npm run test` setup, instead of `frontend/tests/widgets.test.mjs`.
|
|
- `JellyfinWidget` uses `SessionActivityPanel` directly because `NowPlaying` does not expose an `emptyMessage` prop.
|
|
|
|
## Completed tasks (Slice 4)
|
|
|
|
All Slice 4 tasks are marked `- [x]` in `tasks.md`:
|
|
|
|
- [x] 4.1 Refactor `Dashboard.tsx` to render enabled widget instances in sort order
|
|
- [x] 4.2 Create `WidgetInstance` renderer component
|
|
- [x] 4.3 Create `WidgetConfigDialog` for add/edit/reorder/delete widgets
|
|
- [x] 4.4 Create addon pages (`AddonPage`, `GrafanaAddonPage`, `PrometheusAddonPage`, `SshTasksAddonPage`)
|
|
- [x] 4.5 Register `/addons/:addonId` route in `App.tsx`
|
|
- [x] 4.6 Update `docs/REQUIREMENTS.md` with widget system documentation
|
|
|
|
## Files changed (Slice 4)
|
|
|
|
### New files
|
|
|
|
- `frontend/src/components/WidgetInstance.tsx` — Renders a widget instance by looking up its definition and dispatching to the registered component.
|
|
- `frontend/src/components/WidgetConfigDialog.tsx` — Dashboard widget configuration UI: list, add, edit, delete, reorder, enable/disable.
|
|
- `frontend/src/pages/AddonPage.tsx` — Route mapper for `/addons/:addonId`.
|
|
- `frontend/src/addons/GrafanaAddonPage.tsx` — Grafana addon landing page (deep-link only).
|
|
- `frontend/src/addons/PrometheusAddonPage.tsx` — Prometheus addon landing page.
|
|
- `frontend/src/addons/SshTasksAddonPage.tsx` — SSH tasks addon landing page.
|
|
- `frontend/src/addons/index.ts` — Barrel exports.
|
|
|
|
### Modified files
|
|
|
|
- `frontend/src/pages/Dashboard.tsx` — Replaced hard-coded Jellyfin/Backups sections with widget instance loop; kept Shortcuts section; added "Edit dashboard" button.
|
|
- `frontend/src/App.tsx` — Registered `/addons/:addonId` route in both OIDC and non-OIDC route trees.
|
|
- `docs/REQUIREMENTS.md` — Added Configurable Dashboard Widgets section.
|
|
|
|
## Verification (Slice 4)
|
|
|
|
```bash
|
|
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
|
|
npm run test -- src/widgets/registry.test.ts # 3 passed
|
|
```
|
|
|
|
## Deviations from design (Slice 4)
|
|
|
|
- The "Edit dashboard" button lives in the Shortcuts section action area for now. A future UI pass can move it to a dedicated dashboard header.
|
|
- Machine/task selectors in the config dialog filter to enabled Jellyfin machines / enabled tasks, which is slightly stricter than the design's generic string field.
|
|
|
|
## Remaining work
|
|
|
|
- Phase 1 widget system is complete. Future work could include widget grid layout, drag-and-drop reorder, richer Prometheus visualizations, or migrating shortcuts into the widget system.
|
|
|
|
## 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.
|