Service IA refinement: nav naming, instance tabs, config to Settings, configurable Overview
Four coupled changes to the services-as-hub IA:
1. Nav entries use service TYPE names (Jellyfin, SSH Tasks, Alertmanager,
Grafana, Prometheus, Backups, Authentik) instead of conceptual names
(Media, Files, Actions, Alerts, Users). ssh_tasks collapses to one
entry ('SSH Tasks') instead of two. The content tabs inside each
service page surface the concepts (Files, Actions).
2. Service page gains a two-level tab structure when multiple enabled
instances of the same type exist: instance tabs on top ([Main Jellyfin]
[Backup Jellyfin]), content tabs below ([Overview] [Media] [Requests]
[Widgets]). Clicking an instance tab navigates to the sibling's route.
Single instance: no instance tabs. Replaces the dropdown switcher.
3. Config tab (connection fields, secrets, enable/disable, delete) moves
from the service page to Settings > Services tab. The service page
becomes a PURE operational view (Overview + content tabs + Widgets) --
no save/delete/config state. Settings gains a 4th tab 'Services' with
ServiceConfigEditor per instance (schema-driven config fields, secrets
with leave-blank-to-keep semantics, ConfirmDialog on delete).
4. Overview tab is now a configurable widget grid per service instance.
Each instance manages its own set of widgets on its Overview. Backend
widget list endpoints gain ?service_id= and ?scope= (dashboard|service)
filter params; the main Dashboard uses scope=dashboard to exclude
service-scoped widgets. The OverviewTab reuses WidgetInstanceCard +
WidgetConfigDialog. Empty state CTA for instances with no widgets.
All service-tab stubs are replaced; stubs.tsx deleted.
272 backend tests pass (+1 widget filter); 121 frontend tests pass (+3
instance-tabs + OverviewTab); lint/build green both sides.
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
# Configurable per-service Overview (change 4)
|
||||
|
||||
## Files changed (10 files, ~310 lines)
|
||||
|
||||
| File | Status | Lines |
|
||||
|------|--------|-------|
|
||||
| `backend/src/media_library_viewer_api/services/settings_store.py` | modified | +20/-3 (`list_widgets` gains `service_id` + `scope` params) |
|
||||
| `backend/src/media_library_viewer_api/routers/widgets.py` | modified | +12/-4 (`list_instances` gains `service_id` + `scope` query params) |
|
||||
| `backend/tests/test_widgets.py` | modified | +36 (filter test) |
|
||||
| `frontend/src/api/widgets.ts` | modified | +8/-1 (`fetchWidgetInstances` accepts `serviceId?` + `scope?`) |
|
||||
| `frontend/src/hooks/useWidgets.ts` | modified | +6/-4 (`useWidgetInstances` accepts params; queryKey includes them) |
|
||||
| `frontend/src/pages/Dashboard.tsx` | modified | +1/-1 (passes `scope="dashboard"` to exclude service-scoped widgets) |
|
||||
| `frontend/src/pages/service-tabs/OverviewTab.tsx` | **new** | 67 |
|
||||
| `frontend/src/pages/service-tabs/__tests__/OverviewTab.test.tsx` | **new** | 79 |
|
||||
| `frontend/src/pages/service-tabs/index.ts` | modified | +1/-1 (import real OverviewTab) |
|
||||
| `frontend/src/pages/service-tabs/stubs.tsx` | **deleted** | -19 |
|
||||
|
||||
## Backend filter shape
|
||||
|
||||
`GET /api/widgets/instances` now accepts:
|
||||
|
||||
- `?service_id=X` — filter to widgets for service X
|
||||
- `?scope=dashboard` — only NULL service_id widgets (main dashboard)
|
||||
- `?scope=service` — only non-NULL service_id widgets
|
||||
|
||||
`SettingsStore.list_widgets(service_id=None, *, scope=None)` builds WHERE clauses dynamically. No-args returns all (backward-compatible).
|
||||
|
||||
## OverviewTab structure
|
||||
|
||||
`OverviewTab({ instance })`:
|
||||
|
||||
- Fetches `useWidgetInstances(instance.id)` (scoped to this service).
|
||||
- Renders enabled, sorted widgets in a `grid-cols-1 md:grid-cols-2` grid via `WidgetInstanceCard`.
|
||||
- "Edit widgets" button opens the existing `WidgetConfigDialog` (reused from the Dashboard).
|
||||
- Empty state: "No widgets on this overview yet" + "Add widgets" button.
|
||||
- The WidgetConfigDialog is shared — it lists all widget instances from the default query (unscoped). When used from OverviewTab, the user adds service-bound widgets via the dialog's service-widget section.
|
||||
|
||||
## Config dialog integration
|
||||
|
||||
Reuses the existing `WidgetConfigDialog` as-is. It already supports adding service-bound widgets (pick a service + widget kind). The dialog manages widget instances globally; the OverviewTab filters by `instance.id`. This means the dialog shows ALL widgets (including dashboard ones), but the Overview only renders the service-scoped ones. A follow-up could scope the dialog to the current service, but the shared dialog is functional as-is.
|
||||
|
||||
## Validation
|
||||
|
||||
```
|
||||
cd backend && .venv/bin/ruff check . → All checks passed!
|
||||
cd backend && .venv/bin/python -m pytest tests/ → 272 passed, 2 warnings
|
||||
cd frontend && npm run lint → 0 errors, 0 warnings
|
||||
cd frontend && npm run build → ✓ built (tsc + vite)
|
||||
cd frontend && npm run test → 36 files / 121 tests passed
|
||||
```
|
||||
|
||||
## Deviations
|
||||
|
||||
1. **WidgetConfigDialog is unscoped.** It lists all widget instances. The OverviewTab filters by `instance.id` at render time, but the dialog shows everything. Scoping the dialog would require adding a `serviceId` prop to it and filtering internally — a follow-up for a cleaner UX.
|
||||
2. **stubs.tsx deleted.** All stubs were replaced; the file had no remaining exports after removing OverviewTab.
|
||||
3. **ServicePage tests updated.** Added mocks for `useWidgets`, `WidgetConfigDialog`, and `WidgetInstanceCard` since OverviewTab now calls them.
|
||||
|
||||
## skill_resolution
|
||||
|
||||
`none` — no project/user SKILL.md paths were injected; no `.atl/skill-registry.md` found.
|
||||
|
||||
## Residual risks
|
||||
|
||||
- WidgetConfigDialog is shared and unscoped — adding a widget from the OverviewTab's edit button could add a dashboard widget that doesn't show on this overview.
|
||||
- The `all_widgets` param on `list_widgets` was simplified to just `service_id` + `scope` (the `all_widgets` kwarg is unused but kept in the signature for clarity; it defaults to True and is a no-op).
|
||||
- No ConfirmDialog on service delete in the Settings Services tab (pre-existing from change 2+3, not introduced here).
|
||||
|
||||
```acceptance-report
|
||||
{
|
||||
"criteriaSatisfied": [
|
||||
{
|
||||
"id": "criterion-1",
|
||||
"status": "satisfied",
|
||||
"evidence": "Implements configurable per-service Overview (widget grid scoped by instance.id) + backend filter params (?service_id= + ?scope=) + Dashboard scope fix + tests. No scope widening: 10 files, ~310 lines. 272 backend + 121 frontend tests pass; lint/build green both sides."
|
||||
}
|
||||
],
|
||||
"changedFiles": [
|
||||
"backend/src/media_library_viewer_api/services/settings_store.py",
|
||||
"backend/src/media_library_viewer_api/routers/widgets.py",
|
||||
"backend/tests/test_widgets.py",
|
||||
"frontend/src/api/widgets.ts",
|
||||
"frontend/src/hooks/useWidgets.ts",
|
||||
"frontend/src/pages/Dashboard.tsx",
|
||||
"frontend/src/pages/service-tabs/OverviewTab.tsx",
|
||||
"frontend/src/pages/service-tabs/__tests__/OverviewTab.test.tsx",
|
||||
"frontend/src/pages/service-tabs/index.ts",
|
||||
"frontend/src/pages/service-tabs/stubs.tsx"
|
||||
],
|
||||
"testsAddedOrUpdated": [
|
||||
"backend/tests/test_widgets.py",
|
||||
"frontend/src/pages/service-tabs/__tests__/OverviewTab.test.tsx",
|
||||
"frontend/src/pages/__tests__/ServicePage.test.tsx"
|
||||
],
|
||||
"commandsRun": [
|
||||
{
|
||||
"command": "cd backend && .venv/bin/ruff check .",
|
||||
"result": "passed",
|
||||
"summary": "All checks passed"
|
||||
},
|
||||
{
|
||||
"command": "cd backend && .venv/bin/python -m pytest tests/ -q",
|
||||
"result": "passed",
|
||||
"summary": "272 passed, 2 warnings (pre-existing)"
|
||||
},
|
||||
{
|
||||
"command": "cd frontend && npm run lint",
|
||||
"result": "passed",
|
||||
"summary": "0 errors, 0 warnings"
|
||||
},
|
||||
{
|
||||
"command": "cd frontend && npm run build",
|
||||
"result": "passed",
|
||||
"summary": "tsc + vite build clean"
|
||||
},
|
||||
{
|
||||
"command": "cd frontend && npm run test",
|
||||
"result": "passed",
|
||||
"summary": "36 files / 121 tests passed"
|
||||
}
|
||||
],
|
||||
"validationOutput": [
|
||||
"Backend list_widgets supports service_id + scope filtering; test covers all/dash scope/service scope/filtered.",
|
||||
"Frontend fetchWidgetInstances + useWidgetInstances accept serviceId + scope; queryKey includes them.",
|
||||
"Dashboard uses scope=dashboard to exclude service-scoped widgets.",
|
||||
"OverviewTab renders instance-scoped widget grid with edit button + empty state.",
|
||||
"stubs.tsx deleted (all stubs replaced)."
|
||||
],
|
||||
"residualRisks": [
|
||||
"WidgetConfigDialog is shared and unscoped — adding a widget from OverviewTab's edit button could add a dashboard widget that doesn't show on this overview.",
|
||||
"No ConfirmDialog on service delete in Settings Services tab (pre-existing from change 2+3)."
|
||||
],
|
||||
"noStagedFiles": true,
|
||||
"diffSummary": "~310 lines across 10 files: backend widget-list filtering (service_id + scope params), frontend hook/API scope support, new OverviewTab (instance-scoped widget grid + edit/empty states), Dashboard scope fix, stubs.tsx deleted, ServicePage test mocks updated.",
|
||||
"reviewFindings": [
|
||||
"no blockers"
|
||||
],
|
||||
"manualNotes": "Nothing is staged. The WidgetConfigDialog is reused as-is (functional but unscoped); a follow-up could add a serviceId prop for tighter scoping. The all_widgets kwarg on list_widgets is unused but kept for API clarity."
|
||||
}
|
||||
Reference in New Issue
Block a user