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:
@@ -1,87 +1,66 @@
|
||||
# Apply Progress: Runtime Service Registry
|
||||
|
||||
**Change:** `service-registry`
|
||||
**Apply run:** PR 1 + PR 2 + PR 3 (Slices 1–3)
|
||||
**Apply run:** PRs #7–#10 (Slices 1–4a)
|
||||
**Date:** 2026-06-19
|
||||
|
||||
## Slice 1 — Backend service foundation (MERGED, PR #7)
|
||||
## Slices 1–3 (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 2 — Backend 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 4a — Cleanup + 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.
|
||||
|
||||
Reference in New Issue
Block a user