feat(services): cleanup, services admin UI, docs

PR 4a of the runtime service registry change.

- Remove addon pages (/addons/:addonId, AddonPage, addons/*) superseded by
  service pages.
- Remove grafana_url/prometheus_url from backend config, compose, .env.example,
  and README (URLs now live on service records; VITE_ frontend deep-link vars
  retained).
- Add Services page (/services) with create/list/delete + sidebar nav, so
  services are configurable in the tool itself and service pages are reachable.
- Update docs/REQUIREMENTS.md service-registry section; add CHANGELOG.md with
  the breaking-upgrade note (MANAGE_ENCRYPTION_KEY required; grafana/prometheus
  env vars removed; default widget seeding removed).

Verification: backend ruff clean, pytest 222 passed; frontend lint 0 errors,
build success, 70 tests passed.
This commit is contained in:
Developer
2026-06-23 10:57:30 +00:00
parent 5ec35b4849
commit c9c72be0b6
15 changed files with 506 additions and 251 deletions
@@ -1,87 +1,66 @@
# Apply Progress: Runtime Service Registry
**Change:** `service-registry`
**Apply run:** PR 1 + PR 2 + PR 3 (Slices 13)
**Apply run:** PRs #7#10 (Slices 14a)
**Date:** 2026-06-19
## Slice 1 — Backend service foundation (MERGED, PR #7)
## Slices 13 (MERGED)
Fernet secrets, closed `integrations/` registry (Pydantic config + widget-config
for grafana/prometheus/jellyfin/nextcloud/ssh_tasks), `services` +
`service_task_runs` tables with cascade delete, `/api/services*` CRUD,
`MANAGE_ENCRYPTION_KEY` required at startup.
- Slice 1 (#7): backend service foundation — encryption, integrations registry,
services + service_task_runs tables, `/api/services*` CRUD.
- Slice 2 (#8): backend widget rebind — service_id + widget_kind, ServiceRecord
adapters, built-ins, SSH run logging, retired old widget registry.
- Slice 3 (#9): frontend services runtime — types/API/hooks, frontend registry,
ServicePage, route swap, reconciled widget components + config dialog.
## Slice 2Backend widget rebind (MERGED, PR #8)
Widgets carry `service_id` + `widget_kind`; adapters take
`fetch(service: ServiceRecord | None, widget_kind, config)`; backups + static
stay as service-less built-ins; SSH adapter logs to `service_task_runs`; old
`widgets/registry.py` retired; default seeding removed.
## Slice 3 — Frontend services runtime (this PR)
## Slice 4aCleanup + services admin UI + docs (this PR)
### Completed tasks
- [x] 3.1 Service + new widget TypeScript types (`ServiceInstance`,
`ServiceInstanceInput`, `ServiceTypeInfo`, `ServiceWidgetKindInfo`,
`SecretFieldInfo`, `BuiltinWidgetKindInfo`; widget gains `service_id` +
`widget_kind`).
- [x] 3.2 Services API + hooks (`api/services.ts`, `hooks/useServices.ts`).
Reconciled `api/widgets.ts` + `hooks/useWidgets.ts` to the new shape
(removed sources/types; added builtin kinds).
- [x] 3.3 Closed frontend service registry (`integrations/registry.ts`)
mirroring the backend; `resolveWidget(widget, services)` maps a widget to
its component + refresh interval.
- [x] 3.4 Service page at `/services/:serviceType/:serviceId` with config view,
empty-on-edit secret inputs + "set" badges, enable toggle, delete, and the
service's widget-kind list.
- [x] 3.5 Route swap: added `/services/:serviceType/:serviceId`; addon route
retained for now (removed in Slice 4 cleanup).
- [x] 3.6 Reconciled widget components to take `refreshIntervalMs` +
`description` props; rewrote `WidgetConfigDialog` around the
service → widget-kind picker (pulled 4.1 forward to keep the build whole).
- [x] 3.7 Registry + Dashboard tests updated; new
`integrations/registry.test.ts`.
- [x] Removed addon pages (`/addons/:addonId`, `AddonPage.tsx`, `addons/*`) —
superseded by service pages.
- [x] Removed `grafana_url` / `prometheus_url` from `config.py`, both compose
files, `.env.example`, and README. (Frontend `VITE_GRAFANA_URL` /
`VITE_PROMETHEUS_URL` deep-link vars retained.)
- [x] Added a **Services page** (`/services`) with create/list/delete and a nav
entry, so service pages are reachable and services are configurable in the
tool itself.
- [x] Registered `/services` route in both route trees + sidebar nav.
- [x] Updated `docs/REQUIREMENTS.md` (service registry section) and added
`CHANGELOG.md` with the breaking-upgrade note.
### Decision resolved mid-slice
Secret edit UX = **empty-on-edit + "set" badge** (blank = keep existing; typing
= replace). Applied on the ServicePage secrets card.
"Full machine migration" was scoped into **4a (cleanup) + 4b (Jellyfin/Jellyseerr
migration)** because removing machine-level Jellyfin/Jellyseerr fields is deeply
coupled to the Media/Users/Files pages (load-bearing) and there is no
`jellyseerr` service definition yet. 4a ships the safe cleanup + the services
admin UI; 4b does the machine-app-field migration as its own reviewable change.
### Files changed (Slice 3)
### Files changed (Slice 4a)
- New: `api/services.ts`, `hooks/useServices.ts`, `integrations/registry.ts`,
`integrations/registry.test.ts`, `pages/ServicePage.tsx`.
- Modified: `types/index.ts`, `api/widgets.ts`, `hooks/useWidgets.ts`,
`components/WidgetInstance.tsx`, `components/WidgetConfigDialog.tsx`,
`pages/Dashboard.tsx`, `pages/__tests__/Dashboard.test.tsx`, `App.tsx`,
all six `widgets/*.tsx` components, `widgets/index.ts`.
- Deleted: `widgets/registry.ts`, `widgets/registry.test.ts`.
- Backend: `config.py` (removed grafana_url/prometheus_url).
- Compose/env/docs: `docker-compose.yml`, `docker-compose.dev.yml`,
`.env.example`, `README.md`, `docs/REQUIREMENTS.md`, `CHANGELOG.md` (new).
- Frontend: new `pages/ServicesPage.tsx`; `App.tsx` (routes + nav); removed
`pages/AddonPage.tsx`, `addons/*`.
### Verification (Slice 3)
### Verification (Slice 4a)
```bash
cd frontend
cd backend
.venv/bin/ruff check . # clean
PYTHONPATH=src .venv/bin/python -m pytest # 222 passed
cd ../frontend
npm run lint # 0 errors
npm run build # success
npm run test # 70 passed
cd ../backend
.venv/bin/ruff check . # clean
PYTHONPATH=src .venv/bin/python -m pytest # 222 passed
```
### Deviations / notes
- `WidgetConfigDialog` was rewritten in this slice (pulled forward from task
4.1) because the old dialog imported the deleted widget registry and would
not compile. The SSH task-output widget keeps a dedicated task picker; other
widget configs use a generic schema-driven field editor.
- Addon pages (`/addons/:addonId`) are kept compiling but superseded by service
pages; Slice 4 removes them and the now-unused machine Jellyfin/Jellyseerr
fields + `grafana_url`/`prometheus_url` env vars, and writes the changelog.
## Remaining work
- Slice 4: remove addon pages + machine app fields, remove
`grafana_url`/`prometheus_url` from config + compose, docs + changelog
(breaking upgrade note).
- Slice 4b: add `jellyseerr` service definition; rewire `dependencies.py`
Jellyfin/Jellyseerr resolution to the service registry; migrate the
Media/Users/Files/Dashboard selector from machine_id to service instance;
remove machine-level Jellyfin/Jellyseerr fields from `settings_store.py`,
`routers/settings.py`, and the Settings UI.