Files
manage/openspec/changes/services-as-hub-ia/tasks.md
T
Developer fe028b0e6f Plan services-as-hub IA rework (OpenSpec change)
Reorganize the app around services as the hub. Operational content
(Media, Files, Actions, Users, Backups) moves into type-specific tabs on
the service page. Top nav shrinks to Main Dashboard + named dashboards +
conditional per-type entries (appear when configured) + Services + Settings.

Decisions (D1-D17): conditional type entries; instance switcher for
multi-instance; Files/Actions into ssh_tasks tabs; Backups = new service
type; Users -> Authentik (in scope); Observability split per type (no
aggregate); main dashboard special at /; named dashboards = widgets +
pinned service links; each named dashboard = top entry; Authentik =
directory source (OIDC unchanged); Messaging -> Authentik users via SMTP;
Jellyseerr absorbed into Jellyfin config; standard tab skeleton
(Overview | content | Widgets | Config); Overview = health + metrics;
routing /services/:type/:id + /d/:slug; legacy routes 404; empty-state
CTAs.

Authentik directory client + endpoint included. 12 chained PRs forecast
(backend types -> frontend shell -> content tabs -> dashboards ->
cleanup -> verify).
2026-06-26 17:16:52 +00:00

271 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Tasks — Services as hub IA
**Change:** `services-as-hub-ia`
**Phase:** tasks
**Date:** 2026-06-26
## Review workload forecast
| Field | Value |
|-------|-------|
| Estimated changed lines | ~45006000 |
| Chained PRs recommended | Yes (12 slices) |
| Chain strategy | stacked-to-main |
| Slice order | 13 backend → 4 shell → 59 content tabs → 10 dashboards → 11 cleanup → 12 verify |
Each slice is committed separately. Every slice must leave
`cd backend && .venv/bin/ruff check . && .venv/bin/python -m pytest` **and**
`cd frontend && npm run lint && npm run build && npm run test` green. Every
touched page gains a Vitest case at the new route and asserts the old route 404s
(where applicable).
---
## Slice 1 — Backend: new service types + Jellyseerr absorption
**Goal:** Registry reflects the new world. No frontend change yet.
- [ ] **1.1 Add `backups` integration**
- Files: `backend/src/media_library_viewer_api/integrations/backups.py` (new),
`integrations/registry.py`
- Details: `BackupsConfig` (`ingestion_label: str = "default"`), no secrets,
widget kind `summary` (move `BackupsWidgetSource` adapter to bind the
service_id). Register in `SERVICE_DEFINITIONS`.
- [ ] **1.2 Add `authentik` integration**
- Files: `integrations/authentik.py` (new), `registry.py`
- Details: `AuthentikConfig` (`base_url: ServiceBaseUrl`, `timeout_seconds`),
secret `api_token` (required). No widget kinds yet.
- [ ] **1.3 Absorb `jellyseerr` into `JellyfinConfig`**
- Files: `integrations/jellyfin.py`, `integrations/jellyseerr.py` (delete),
`integrations/registry.py`, `integrations/__init__.py`
- Details: Add optional `jellyseerr_url`, `jellyseerr_api_key` to
`JellyfinConfig`. Delete the `jellyseerr` integration module and registry
entry. Update tests.
- [ ] **1.4 Jellyseerr migration**
- Files: `services/settings_store.py` (`ensure_defaults`)
- Details: On startup, migrate existing `jellyseerr` rows into paired
`jellyfin` instances per the design. Log a warning for unpaired drops.
- [ ] **1.5 Tests**
- Update `backend/tests/test_services.py`, `test_widgets.py` for the new types
and the migration. Assert registry contains 8 types (alertmanager, authentik,
backups, grafana, jellyfin, nextcloud, prometheus, ssh_tasks).
---
## Slice 2 — Backend: Authentik directory client + endpoint
- [ ] **2.1 AuthentikClient**
- Files: `clients/authentik.py` (new)
- Details: `users(search, page, page_size) -> {items, total}` against the
Authentik directory API. Reuse the requests-session pattern from
`clients/jellyseerr.py`. Tests: `tests/test_authentik_client.py`.
- [ ] **2.2 Directory endpoint**
- Files: `routers/authentik_users.py` (new), `main.py` (register router)
- Details: `GET /api/services/authentik/{service_id}/users` proxies to the
client, resolving the service record via the existing dependency. Tests
cover not-configured + unreachable + paginated-success.
---
## Slice 3 — Backend: route cleanup + backups attribution
- [ ] **3.1 Remove Users router**
- Files: `routers/users.py`, `routers/users_impl.py` (delete), `main.py`,
`dependencies.py`
- Details: Delete the Jellyfin-backed user directory + message-compose router
and its deps. Update `test_api.py` to drop the corresponding tests.
- [ ] **3.2 Backups service attribution**
- Files: `routers/backups.py`, `services/settings_store.py`
- Details: backup report endpoint accepts optional `?service_id=`; first-wins
association to an enabled `backups` instance when omitted. Dashboard summary
- poller continue to work.
- [ ] **3.3 Named dashboards backend**
- Files: `models/dashboards.py` (new), `routers/dashboards.py` (new),
`services/settings_store.py` (table + CRUD)
- Details: `named_dashboards` table (id, slug, label, sort_order, payload JSON
of widget+link placements). Endpoints: `GET/POST/PUT/DELETE /api/dashboards`.
Tests in `tests/test_dashboards.py`.
---
## Slice 4 — Frontend: top-nav generation + service-page skeleton
**Goal:** Data-driven nav + tab-skeleton ServicePage shell. Content tabs are
stubs that say "coming soon" so the rest of the app stays green.
- [ ] **4.1 Service-type → nav-entry map**
- Files: `frontend/src/integrations/navEntries.ts` (new)
- Details: Static `SERVICE_TYPE_NAV_ENTRIES` map (jellyfin→Media,
ssh_tasks→[Files, Actions], alertmanager→Alerts, etc.). Helper to filter by
configured types.
- [ ] **4.2 Data-driven nav in `App.tsx`**
- Files: `frontend/src/App.tsx`
- Details: Replace static `navItems` with the memoized list from design. Add
`useDashboards()` and combine with `useServiceInstances()`. Loading skeleton
nav until settled. Legacy routes removed; add 404 catch-all.
- [ ] **4.3 ServicePage tab skeleton + instance switcher**
- Files: `frontend/src/pages/ServicePage.tsx`, new `pages/service-tabs/`
directory, `pages/ServiceTypePage.tsx` (redirect resolver)
- Details: Refactor ServicePage to render `[Overview, ...content, Widgets,
Config]` from `serviceTabs(serviceType)`. Add `/services/:type` resolver
route. Content tabs are stub components ("coming soon"). Instance switcher
dropdown when siblings > 1.
- [ ] **4.4 Empty-state CTAs**
- Files: `frontend/src/pages/Dashboard.tsx`, `pages/ServicesPage.tsx`
- Details: Dashboard shows "Add a service" CTA when no services. Services
page strong empty state.
- [ ] **4.5 Tests**
- Nav-generation tests, service-page-skeleton tests, 404-on-legacy-routes
tests.
---
## Slice 5 — Frontend: Jellyfin content tabs (Media + Requests)
- [ ] **5.1 MediaTab**
- Files: `pages/service-tabs/MediaTab.tsx` (lift from `pages/Media.tsx`)
- Details: Accept `instance` prop, pass `instance.id` to media hooks. Preserve
the index build controls + mobile card layout. Delete the old `/media` route
and `Applications.tsx` wrapper.
- [ ] **5.2 RequestsTab (Jellyseerr enrichment)**
- Files: `pages/service-tabs/RequestsTab.tsx`
- Details: Source from the absorbed `jellyseerr_url`/`jellyseerr_api_key` on
the Jellyfin instance. Render request-management data.
- [ ] **5.3 Tests**
- New tests for MediaTab (instance-scoped), RequestsTab. Delete old Media page
tests.
---
## Slice 6 — Frontend: ssh_tasks content tabs (Files + Actions)
- [ ] **6.1 FilesTab**
- Files: `pages/service-tabs/FilesTab.tsx` (lift from `FileBrowser.impl.tsx`)
- Details: Accept `instance` prop. Delete old `/files` route + page wrapper.
- [ ] **6.2 ActionsTab**
- Files: `pages/service-tabs/ActionsTab.tsx` (lift from `Actions.tsx`)
- Details: Accept `instance` prop. Delete old `/actions` route + page.
- [ ] **6.3 Tests**
---
## Slice 7 — Frontend: backups Jobs tab
- [ ] **7.1 JobsTab**
- Files: `pages/service-tabs/JobsTab.tsx` (lift from `components/BackupsPage.tsx`)
- Details: Accept `instance` prop, scope queries by `instance.id`. Delete old
`/backups` route + page.
- [ ] **7.2 Tests**
---
## Slice 8 — Frontend: Authentik Users + Messaging tabs
- [ ] **8.1 UsersTab**
- Files: `pages/service-tabs/UsersTab.tsx`, `hooks/useAuthentikUsers.ts`,
`api/authentik.ts`
- Details: Directory table + search, sourced from the new endpoint. No
Jellyfin/Jellyseerr enrichment.
- [ ] **8.2 MessagingTab**
- Files: `pages/service-tabs/MessagingTab.tsx` (lift compose UI from
`UsersPage.impl.tsx`)
- Details: Recipient list sourced from Authentik users. Reuse the mail queue +
SMTP settings. Delete the old `/users` route + UsersPage.
- [ ] **8.3 Tests**
---
## Slice 9 — Frontend: Observability split (Alerts + Links + Metrics tabs)
- [ ] **9.1 AlertsTab**
- Files: `pages/service-tabs/AlertsTab.tsx` (lift from `ObservabilityPage.tsx`)
- Details: Alertmanager alerts view, instance-scoped. Delete old
`/observability` route + page.
- [ ] **9.2 LinksTab + MetricsTab**
- Files: `pages/service-tabs/LinksTab.tsx`, `pages/service-tabs/MetricsTab.tsx`
- Details: Grafana deep-links; Prometheus status + PromQL explorer. Each
instance-scoped.
- [ ] **9.3 Tests**
---
## Slice 10 — Frontend: named dashboards
- [ ] **10.1 NamedDashboardPage**
- Files: `pages/NamedDashboardPage.tsx`, `hooks/useDashboards.ts`,
`api/dashboards.ts`
- Details: Render widgets + pinned service links at `/d/:slug`. CRUD via the
new endpoints.
- [ ] **10.2 Pinned service links**
- Files: `components/PinnedServiceLink.tsx`, integration into the dashboard
config dialog
- Details: Shortcut variant targeting `/services/:type/:id` or a specific tab.
- [ ] **10.3 Dashboard management UI**
- Files: a new "Manage dashboards" entry on the Services or Settings page
- Details: Create/rename/reorder/delete named dashboards.
- [ ] **10.4 Tests**
---
## Slice 11 — Cleanup + docs
- [ ] **11.1 Delete dead code**
- Files: any remaining top-level page wrappers, unused hooks, stale types.
- Details: Confirm no references to removed routes/pages remain.
- [ ] **11.2 Update `docs/REQUIREMENTS.md`**
- Files: `docs/REQUIREMENTS.md`
- Details: Rewrite the Information Architecture section. Document the service-
type → nav-entry map, the service-page tab skeleton, named dashboards,
routing, and the Users→Authentik + Observability-split decisions.
- [ ] **11.3 Update `CHANGELOG.md`**
---
## Slice 12 — Verify
- [ ] **12.1 Cross-route manual pass**
- Details: Walk every service type's page + tabs; walk named dashboards; walk
the empty state; confirm legacy routes 404.
- [ ] **12.2 Verify report**
- Files: `openspec/changes/services-as-hub-ia/verify-report.md`
- Details: Per-AC evidence (AC1AC10), tool versions, manual notes, residual
risks.
---
## Notes
- Slices 13 are backend-only; slice 4 is the frontend shell turning on the new
IA with stubs; 59 replace stubs with real content; 10 adds named dashboards;
1112 close out.
- Slices 59 are independent and can be reordered or parallelized across
branches if useful, but each must merge green with its stub replaced.
- The frontend content lifts (59) are the bulk of the line count; treat each as
a self-contained review-sized PR.