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
9.9 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
SettingsStorefordashboard_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— ClosedWIDGET_REGISTRYfor 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— Addeddashboard_widgetstable, index, CRUD helpers, default seeding, and refactoredensure_defaults()to seed widgets independently of machine seeding.backend/src/media_library_viewer_api/main.py— Registeredwidgets_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_CONTENTinstead of the deprecatedHTTP_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.py—WidgetSourceprotocol 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— Addedgrafana_urlandprometheus_urlsettings.backend/src/media_library_viewer_api/routers/widgets.py— AddedGET /api/widgets/instances/{id}/data.backend/src/media_library_viewer_api/routers/dashboard.py— Delegated to shareddomain/dashboard.pyhelpers.backend/tests/test_widgets.py— Added adapter and data endpoint tests.docker-compose.yml,docker-compose.dev.yml,.env.example— WiredGRAFANA_URLandPROMETHEUS_URLfor 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 forbackups/ssh_tasksources. The router-level endpoint uses FastAPI DI, but adapter unit tests patchget_settings_storeto inject a test store. A future refactor can passstoreandsettingsexplicitly intoadapter.fetch()for cleaner testability.
Completed tasks (Slice 3)
All Slice 3 tasks are marked - [x] in tasks.md:
- 3.1 Add TypeScript widget interfaces (
WidgetInstance,WidgetInstanceInput,WidgetTypeInfo,WidgetDataResponse) - 3.2 Create widget API client (
frontend/src/api/widgets.ts) - 3.3 Create widget TanStack Query hooks (
frontend/src/hooks/useWidgets.ts) - 3.4 Create frontend widget registry (
frontend/src/widgets/registry.ts) - 3.5 Implement six widget presentational components (
frontend/src/widgets/*.tsx) - 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)
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.tsand runs with Vitest, matching the project's existingnpm run testsetup, instead offrontend/tests/widgets.test.mjs. JellyfinWidgetusesSessionActivityPaneldirectly becauseNowPlayingdoes not expose anemptyMessageprop.
Completed tasks (Slice 4)
All Slice 4 tasks are marked - [x] in tasks.md:
- 4.1 Refactor
Dashboard.tsxto render enabled widget instances in sort order - 4.2 Create
WidgetInstancerenderer component - 4.3 Create
WidgetConfigDialogfor add/edit/reorder/delete widgets - 4.4 Create addon pages (
AddonPage,GrafanaAddonPage,PrometheusAddonPage,SshTasksAddonPage) - 4.5 Register
/addons/:addonIdroute inApp.tsx - 4.6 Update
docs/REQUIREMENTS.mdwith 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/:addonIdroute in both OIDC and non-OIDC route trees.docs/REQUIREMENTS.md— Added Configurable Dashboard Widgets section.
Verification (Slice 4)
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.