Commit Graph

54 Commits

Author SHA1 Message Date
Developer c9201a004c spec(service-storage-harness): sync into canonical service-storage domain
New canonical openspec/specs/service-storage/spec.md (28 reqs SS-101..128).
Change-side delta + sync-report. web-ui + prometheus-charting canonicals
untouched.
2026-07-09 09:37:22 +00:00
Developer 40a7ac80d3 spec(service-storage-harness): verify + reconcile tracking
Write apply-progress.md, tick all 35 tasks, add verify-report.md (28/28
SS-101..128 PASS). Gates green: 322 pytest, ruff clean, FE build+lint 0
errors, PrometheusChartWidget extraction 4/4 non-regressive. No blocking
code findings. Process note: slice 2 was +644 lines over 400-line budget
(additive, no scope creep; retrospectively extract+widgets could split).
2026-07-09 09:30:52 +00:00
Developer c9404f0794 spec(service-storage-harness): add spec (28 requirements SS-101..128)
Covers harness lifecycle, QbittorrentSampleStore, QbittorrentClient,
3 widget kinds (totals=item count, active=DL/UL, speed=LineSeriesChart
reuse), MediaIndex migration (+service_id, scoped replace_items bug fix,
backward-compat query), cascade-delete wiring, test/build greenness.
2026-07-09 09:15:32 +00:00
Developer 9a251db23c spec(service-storage-harness): add tasks (4 slices, each <=400 lines)
S1 harness+store+client+integration; S2 widget adapter+3 FE widgets+
LineSeriesChart extract; S3 MediaIndex +service_id migration (fixes latent
global-clear bug); S4 cascade-delete wiring. Each slice leaves pytest/npm
build/npm lint green. Risk flags on LineSeriesChart extract + MediaIndex migration.
2026-07-09 08:02:31 +00:00
Developer cff082c7a1 spec(service-storage-harness): add design
10 design decisions incl: lifecycle-only harness, per-concern DB files,
QbittorrentSampleStore (120-sample cap), qBit cookie client, 3 widget kinds
(totals=item count, active=DL/UL state, speed=reuses Change A renderer via
shared LineSeriesChart extract), MediaIndex +service_id backfill, scoped
replace_items (fixes latent global-clear bug), best-effort cascade-delete.
4-slice plan. 3 source findings (worker already threads service_id).
2026-07-09 07:55:59 +00:00
Developer bc389ae3f7 spec(service-storage-harness): reconcile proposal post Change A + question round
Resolve Q1 (reuse Change A's PrometheusChartWidget renderer via InService
data path), Q2 (totals = item count, not transfer bytes), Q3 (active =
downloading/uploading), Q4 (N instances), Q5 (username/password cookie
auth). Remove TBD/stale thin-dashboard caveats.
2026-07-09 07:45:55 +00:00
Developer 7efc06a629 chore(prometheus-direct-charting): archive verified+synced change
Move to openspec/changes/archive/2026-07-08-prometheus-direct-charting/
(git mv, history preserved). 9 artifacts: proposal/spec/design/tasks/
apply-progress/verify-report/sync-report/archive-report + delta spec.
Canonical openspec/specs/prometheus-charting/ remains.
2026-07-08 23:01:35 +00:00
Developer 3e77075171 spec(prometheus-direct-charting): sync into canonical prometheus-charting domain
New canonical domain openspec/specs/prometheus-charting/spec.md with all
27 requirements (SC-101..127) as the durable post-change contract. Change-side
delta specs/prometheus-charting/spec.md + sync-report.md. web-ui canonical
untouched (different concern).
2026-07-08 22:57:29 +00:00
Developer 7440603cdb spec(prometheus-direct-charting): verify + close SC-125 + reconcile tracking
Add loading-state tests to the three Prometheus widget test files
(closes SC-125 PARTIAL). Write apply-progress.md, tick all 39 tasks,
add verify-report.md (26/27 PASS, 1 PARTIAL->PASS). All gates green:
293 pytest, ruff clean, npm build+lint 0 errors. No blocking findings.
2026-07-08 22:50:30 +00:00
Developer 67ca0fc3bc feat(prometheus-direct-charting): slice 3 — remove grafana + config rewrite + changelog
Remove the entire Grafana surface: integrations/grafana.py, GrafanaWidgetSource
(+ _fetch_chart, now redundant since prometheus chart exists), GrafanaLinkWidget,
LinksTab, get_grafana_status endpoint, useGrafanaStatus hook, GrafanaStatus type,
fetchGrafanaStatus client fn, registry/nav/tab entries (FE+BE). Rewrite
config.yaml thin-dashboard rule to match reality (recharts is sanctioned for
Prometheus-backed series). CHANGELOG migration note added.

Backend: 293 pytest pass, ruff clean. Frontend: build+lint green (0 errors).
SC-115/116 grep-clean (only prometheus_range.py migration comments + Dashboard.test
shortcut fixture remain — both spec-allowed).
2026-07-08 22:35:06 +00:00
Developer 65bae95e3c feat(prometheus-direct-charting): slice 2 — gauge + mean widgets
Add gauge widget (recharts RadialBarChart with configurable threshold
bands, scalar-only per SC-111) and mean widget (client-side average over
range-query window, scalar-only per SC-114). Extract shared _instant_query
helper from the metric path; _fetch_gauge and _fetch_mean dispatch in
PrometheusWidgetSource.fetch(). Both new widget kinds declared in
integrations/prometheus.py and frontend registry.

Backend: 305 pytest pass, ruff clean. Frontend: 136 vitest pass, build+lint green.
2026-07-08 22:10:11 +00:00
Developer 5dad98231f feat(prometheus-direct-charting): slice 1 — prom range query + chart rebrand
Add PrometheusWidgetSource._fetch_chart hitting /api/v1/query_range directly
(SC-101..104). New shared helpers in widgets/prometheus_range.py:
step_for_window (window preset -> step, ~200pts) and normalize_prometheus_matrix
(extracted label/dedup rule, retargeted at Prom matrix, robust to malformed
data). Chart widget kind moved grafana->prometheus in both registries;
GrafanaChartWidget renamed -> PrometheusChartWidget (git mv, recharts body
preserved). Grafana binding/service untouched (removed in slice 3).

Backend: 298 pytest pass, ruff clean. Frontend: 128 vitest pass, build+lint green.
2026-07-08 21:55:02 +00:00
Developer d906b0392b spec(prometheus-direct-charting): add tasks (3 slices, each <=400 lines)
S1 adds Prom chart (range query + rebrand, no grafana removal); S2 adds
gauge+mean; S3 removes grafana + config rewrite + changelog. Each slice
leaves pytest/npm build/npm lint green. Review-workload forecast per slice.
2026-07-08 21:33:16 +00:00
Developer 9b7415080b spec(prometheus-direct-charting): patch SC-116/118 for service-tabs refactor
ObservabilityPage.tsx was refactored into service-tabs/; update removal
criteria to name real targets and whitelist the Dashboard.test fixture
shortcut-label collision.
2026-07-08 21:29:09 +00:00
Developer 78e273efe8 spec(prometheus-direct-charting): add design
5 locked design decisions: step derivation formula (max(15, round(s/200))),
shared normalize_prometheus_matrix helper in widgets/prometheus_range.py,
recharts RadialBarChart gauge w/ threshold bands, mean via client-side
avg over query_range, 3-slice plan each <=400 lines.
Source-verified: ObservabilityPage refactored into service-tabs/ (map stale).
2026-07-08 21:28:02 +00:00
Developer b7e5ca3cbc spec(prometheus-direct-charting): add proposal + spec
Drop Grafana as chart middleman; query Prometheus directly via
/api/v1/query_range. Rebrand GrafanaChartWidget -> PrometheusChartWidget,
add gauge + mean widget kinds, remove Grafana surface, rewrite stale
thin-dashboard rule in config.yaml. 27 acceptance requirements (SC-101..127).
2026-07-08 21:21:14 +00:00
Developer 01527ae4f0 Rebase services-as-hub-ia onto mobile-responsive-parity
Combine both branches into a single coherent branch:
- Full mobile responsive parity (useIsMobile, MobileCardRow, SheetForm,
  .mobile-touch-target, mobile cards, SheetForm forms, 44px targets,
  dirty-state confirm, TablePagination, refetchIntervalInBackground).
- Full services-as-hub IA (data-driven nav, service-page tab skeleton,
  new service types, Authentik directory + messaging, named dashboards,
  legacy routes 404, Observability split, Jellyseerr absorbed).

Enhancement: service tabs now use mobile-parity primitives:
- MediaTab: MobileCardRow below md (title/size/HDR/library/year) +
  TablePagination; DataTable at md+ (desktop branch preserved).
- FilesTab: MobileCardRow below md (name/type/size/modified) +
  handleRowClick; DataTable at md+.
- ServicePage: SheetForm branch below md (open-on-mount, sticky header
  + save bar, cancel navigates back to /services, dirty-state guard).
- Dashboard: single-column + section anchors below md (from mobile-parity)
  + empty-state CTA (from services-hub).
- App.tsx: useIsMobile() replaces inline matchMedia (from mobile-parity)
  + data-driven useNavItems (from services-hub).
- Backup tables (BackupAlerts/Jobs/Runs) already have MobileCardRow from
  mobile-parity; JobsTab inherits mobile behavior through its sub-components.

Conflict resolutions:
- Backend: entirely from services-hub (mobile didn't touch it).
- Deleted pages (Media/FileBrowser/Actions/Users/UsersPage/Applications/
  ObservabilityPage/BackupsPage + hooks/useUsers + tests): kept deleted
  (services-hub deleted them; content moved into service tabs).
- New service-tabs/*: from services-hub, enhanced with mobile patterns.
- App.tsx: services-hub's data-driven nav + mobile-parity's useIsMobile.
- Dashboard.tsx: merged (services-hub CTA + mobile-parity sections/anchors).
- ServicePage.tsx: services-hub's tab skeleton + mobile-parity's SheetForm.
- Primitives (useIsMobile/mobile-card/sheet-form/etc.): from mobile-parity.

117 frontend tests pass (mobile-parity's 122 - 5 deleted page tests +
services-hub's new tab/dashboard tests); 271 backend tests pass; lint/
build green both sides.
2026-06-26 21:08:51 +00:00
Developer b583d5a365 Update verify report: 4 of 5 residual risks resolved
R1 (R4.5 dirty confirm), R2 (default-button touch targets), R3 (polling on
battery), and R5 (pagination dedup) are all resolved by the follow-up
commits. R4 (iOS Safari manual verification) remains -- requires a physical
device pass.
2026-06-26 15:59:00 +00:00
Developer 32516f6e3b Docs + verify report for mobile responsive parity (Slice 10)
Add Mobile Responsive Design section to docs/REQUIREMENTS.md documenting
the breakpoint policy (single md:768px), hybrid table strategy (cards below
md), SheetForm edit flows, 44px touch targets, dashboard single-column +
anchors, unchanged polling, and HoverEditButton behavior.

Add openspec verify-report.md with per-AC evidence (AC1-AC8), residual
risks (R4.5 dirty-state confirm, default-button touch targets, polling on
battery, iOS Safari manual verification, pagination duplication), and
non-goals confirmation.

All 9 routes fully operable at 375px. 116 frontend tests pass; lint/build
green. Desktop layout unchanged. No backend changes.

Refs openspec/changes/mobile-responsive-parity/ (tasks slice 10).
2026-06-26 14:40:04 +00:00
Developer 18ee77a4e4 Plan mobile responsive parity (OpenSpec change)
Add proposal/spec/design/tasks for full mobile parity across all 9 routes.
Decisions: hybrid tables (cards below md for big four), Sheet-based forms,
always-visible edit affordance, 44px touch targets, single-column dashboard
with anchors, responsive web only (no PWA), phone portrait at md:768px cut.
Polling unchanged (risk flagged). Delivery: 10 chained PRs, primitives first.
2026-06-26 11:49:47 +00:00
Developer 648320abfd chore(project-map): refresh .pi-map role/arch summaries
Regenerate project map artifacts across backend, docs, openspec, and
root to refresh role descriptions and architectural notes after recent
service-registry and observability changes.

Co-authored-by: el Gentleman <gentleman@pi.local>
2026-06-26 09:10:19 +00:00
Developer 6919158012 docs: complete Jellyfin migration, archive jellyfin-service-registry
Slice 3 (final) of jellyfin-service-registry. Documents the completed
migration and archives the SDD change.

- docs/REQUIREMENTS.md: marked the machine-level Jellyfin follow-up
  resolved; added a decision-log entry (Jellyfin no longer a machine
  service, dead media_root/path_prefix removed; global config +
  path_utils retained for Jellyfin->SSH path resolution).
- CHANGELOG.md: struck through the old follow-up note; added a
  Follow-up #2 section describing the machine field + service removal.
- Archived openspec/changes/jellyfin-service-registry (no active SDD
  changes remain).

Backend ruff clean / 239 tests pass; frontend 0 lint errors / build
clean / 72 tests pass.
2026-06-24 14:56:25 +00:00
Developer 38b2de54ff chore: archive observability-service-registry, track pi-map artifacts
- Archive the completed observability-service-registry SDD change into
  openspec/changes/archive/ (delivered across 5 slices; only
  jellyfin-service-registry remains active).
- Stop ignoring .pi-map.md / .pi-map.index.md so the navigation maps are
  versioned alongside the code, and add the regenerated map pairs repo-wide.
2026-06-24 13:28:23 +00:00
Developer c13e274ca4 docs(openspec): re-scope observability-service-registry change
Rename grafana-prometheus-polish -> observability-service-registry and
rewrite proposal/design/tasks for the approved vision: all observability
integration (alertmanager, grafana, prometheus) configured as service-
registry instances in the UI, surfaced on a dedicated page, with widgets
per service definition -- nothing in the env.

Key scope decisions captured:
- Add alertmanager as a 6th service type + active_alerts widget.
- Rewire /alerts + /alertmanager-status to resolve from service records
  (first-enabled-instance default; no primary flag in v1).
- Add /grafana-status + /prometheus-status health endpoints.
- Observability page discovers services; kill VITE_GRAFANA_URL /
  VITE_PROMETHEUS_URL deep-links.
- Webhook receiver stays log-only (drop the outbound forward).
- Remove PROMETHEUS_FILE_SD_DIR + the file-writer; external Prometheus
  uses http_sd_configs against GET /api/monitoring/prometheus-targets.
  build_node_exporter_targets + that endpoint stay.
- PROMETHEUS_ENABLED stays (Manage's own /metrics toggle).
- End state: zero observability *service* env vars.

Plan = 5 slices, each <=400 changed lines, green tests/lint/build,
commit per slice.
2026-06-23 21:48:58 +00:00
Developer 4d520ab0e3 docs(openspec): add SDD artifacts for next changes
- jellyfin-service-registry: proposal, design, and tasks for completing
  the Jellyfin migration off machine-level config.
- grafana-prometheus-polish: proposal, design, and tasks for improving
  the Grafana/Prometheus observability integration.

Both are planning-only artifacts; implementation not started.
2026-06-23 20:40:35 +00:00
Developer ca8927834e chore(openspec): archive completed changes
Move finished change directories to openspec/changes/archive/:
- configurable-dashboard-widgets
- decommission-monitoring-poller
- service-registry
- unify-tasks-on-services

All associated implementation has been merged to main.
2026-06-23 19:38:34 +00:00
Developer 50eb76a10d feat(tasks): unify saved tasks on ssh_tasks services
- Add shared task_runner.run_saved_task helper used by routers/tasks.py and
  widgets/sources.py SshTaskWidgetSource.
- Saved tasks now target ssh_tasks service instances via default_service_id;
  the legacy default_machine_id and saved_task_runs are removed.
- Actions page lists ssh_tasks services for default and run-time selection.
- Update types, API client, hooks, tests, docs, and changelog.

Backend tests: 222 passed. Frontend lint/build/test: clean (71 passed).
2026-06-23 16:46:46 +00:00
Developer 8c69911252 docs(unify-tasks): SDD proposal, design, and tasks
Design-only artifacts for unifying saved tasks on ssh_tasks services.
No implementation yet.

- proposal: two-path problem (Actions→machine vs widget→service), goals,
  non-goals, grilling decisions (SSH-only, service_task_runs only, keep override)
- design: shared run_saved_task helper, column rename, saved_task_runs dropped,
  API + frontend changes, 2-slice plan
- tasks: backend (shared runner + router) + frontend (Actions page)
2026-06-23 13:05:52 +00:00
Developer cbb703341e feat(services): select Jellyfin via jellyfin_service_id on the frontend
Slice 4b frontend half. Jellyfin-touching pages now select a Jellyfin service
instance instead of a machine.

- api/client.ts: Jellyfin-backed calls (counts/libraries/activity/users, media
  status/build/stop/force-stop, queryMedia) send jellyfin_service_id.
- hooks/useDashboard, useUsers, useMedia: selector param renamed to
  jellyfinServiceId.
- pages/Media + Applications: list jellyfin service instances and persist
  jellyfin_service_id in the URL.
- Dashboard (widgets) and Users (default instance) need no selector change.
- Update Applications + Media tests for the new hook/param.

Files/SSH transport keeps machine_id. Verification: frontend lint 0 errors,
build success, 70 tests; backend ruff clean, 222 tests.
2026-06-23 12:10:27 +00:00
Developer 8ff735d644 feat(services): resolve Jellyfin/Jellyseerr from the service registry (backend)
Slice 4b backend half. Jellyfin and Jellyseerr clients are now resolved from
service instances instead of machine-level app config.

- Add jellyseerr service definition (6 service types total); add user_id to
  the Jellyfin service config.
- dependencies.py: jellyfin_service_id query param + _service_record
  (decrypt-on-read); get_jellyfin_client / get_jellyseerr_client / get_user_id
  resolve against the service registry (first enabled instance as fallback).
- SSH/Files transport (get_ssh_client) unchanged; still uses machine_id.
- Update service-registry tests for 6 types.

Selection model: split params — ?jellyfin_service_id= for Jellyfin/Jellyseerr,
?machine_id= for SSH/Files. Frontend threading follows in the next PR.

Verification: backend ruff clean, pytest 222 passed; frontend green (unchanged).
2026-06-23 11:43:33 +00:00
Developer c9c72be0b6 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.
2026-06-23 10:57:30 +00:00
Developer 1da67f38c7 feat(services): frontend services runtime and widget rebind
PR 3 of 4 for the runtime service registry change.

- Add service + new-shape widget TypeScript types; widgets carry service_id
  + widget_kind (service-bound) or null (built-in).
- Add services API client + TanStack Query hooks; reconcile the widget API
  client/hooks to the new endpoints (remove sources/types; add builtin kinds).
- Add closed frontend service registry (integrations/registry.ts) mirroring the
  backend, with resolveWidget(widget, services) mapping a widget to its
  component + refresh interval.
- Add ServicePage at /services/:serviceType/:serviceId with config view,
  empty-on-edit secret inputs + 'set' badges, enable toggle, delete, and the
  service's widget-kind list.
- Register /services/:serviceType/:serviceId in App.tsx.
- Reconcile the six widget components to refreshIntervalMs + description props;
  rewrite WidgetConfigDialog around a service -> widget-kind picker.
- Update Dashboard test; add integrations/registry.test.ts.

Verification: frontend lint 0 errors, build success, 70 tests passed; backend
ruff clean, 222 tests passed.
2026-06-22 18:59:41 +00:00
Developer 10fd4ead4a feat(widgets): rebind widgets to the service registry
PR 2 of 4 for the runtime service registry change.

- dashboard_widgets gains service_id + widget_kind columns (legacy
  addon_id/widget_type kept but unused).
- Source adapters take (service: ServiceRecord | None, widget_kind, config).
  SERVICE_ADAPTERS keyed by service_type; BUILTIN_ADAPTERS for backups/static.
- Backups and static stay as service-less built-ins (service_id nullable),
  exposed via GET /api/widgets/builtin.
- SSH task adapter resolves the task + instance, runs over SSH, and appends a
  service_task_runs history row on success/failure/timeout/error.
- Retire widgets/registry.py; widget metadata now comes from the integrations
  registry + widgets/builtin. Remove /api/widgets/types and /api/widgets/sources.
- Stop default widget seeding (fresh install = empty dashboard).
- Rewrite widget tests around the service-bound + built-in model (26 tests).

Backend-only breaking change; frontend is reconciled in Slice 3. Build/lint
stay green; pytest 222 passed.
2026-06-22 16:42:56 +00:00
Developer 8cdeadd6dd feat(services): backend service registry foundation (encryption, definitions, CRUD)
PR 1 of 4 for the runtime service registry change.

- Add Fernet encryption helper (services/secrets.py) with a required
  MANAGE_ENCRYPTION_KEY; validate it on startup.
- Add closed integrations/ registry with Pydantic config + widget-config
  definitions for grafana, prometheus, jellyfin, nextcloud, and ssh_tasks.
- Add services + service_task_runs tables and SettingsStore CRUD with
  cascade-delete (defensive until widgets carry service_id).
- Add /api/services/types and /api/services/instances CRUD (encrypted secrets,
  secrets_set flags only; never plaintext).
- Declare cryptography as a direct dependency.
- Require MANAGE_ENCRYPTION_KEY in compose + .env.example + README.
- Add 25 backend tests (registry, encryption, CRUD, cascade, task-run history).

Verification: ruff clean; pytest 225 passed; frontend lint/build green.
2026-06-22 12:56:03 +00:00
Developer 9459de5c07 docs(service-registry): lock decisions (cascade delete, required key, SSH runner model)
- §11 decisions: cascade-delete services with widgets; MANAGE_ENCRYPTION_KEY
  always required; SSH task runner is multi-instance with reusable tasks.
- §12 SSH task runner model: instances absorb SSH task transport, tasks stay
  global/reusable with default_service_id, service_task_runs logs history,
  widget config { task_id, service_id? }.
- tasks.md: add service_task_runs table + cascade-delete tests to Slice 1,
  SSH run-logging to Slice 2, follow-ups (Actions rebuild, machine unification).
2026-06-22 11:14:01 +00:00
Developer 9782280a03 docs(service-registry): SDD proposal, design, and tasks
Design-only artifacts for the runtime service registry change. No
implementation yet.

- proposal: motivation, goals, non-goals, grilling decisions, risks
- design: data model, Pydantic service definitions, encryption, API,
  frontend structure, migration/breaking changes, 4-PR slice plan
- tasks: backend foundation, backend widget rebind, frontend services
  runtime, dashboard + settings rework + docs
2026-06-22 10:07:25 +00:00
Developer ed7a7a5ce0 feat(widgets): dashboard loop, widget config UI, and addon pages
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
2026-06-21 20:45:42 +00:00
Developer e1356b20f1 feat(widgets): add frontend widget runtime (types, API, hooks, registry, components)
PR 3 of 4 for configurable dashboard widgets.

- Add TypeScript widget interfaces (WidgetInstance, WidgetInstanceInput,
  WidgetTypeInfo, WidgetDataResponse).
- Create widget API client for CRUD, registry metadata, and per-widget data.
- Create TanStack Query hooks for instances, data, sources, types, and mutations.
- Create closed frontend widget registry with metadata, source type, refresh
  intervals, and config fields.
- Add six shadcn/ui-based widget components: Jellyfin, Backups, Grafana link,
  Prometheus metric, SSH task output, and static text.
- Add Vitest unit tests for registry metadata.

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
2026-06-21 16:24:44 +00:00
Developer 1cd8e926de feat(widgets): add backend source adapters and per-widget data endpoint
PR 2 of 4 for configurable dashboard widgets.

- Add grafana_url and prometheus_url settings (config.py + compose/env).
- Create WidgetSource protocol and adapters for jellyfin, backups, grafana,
  prometheus, ssh_task, and static sources.
- Add GET /api/widgets/instances/{id}/data endpoint.
- Extract shared dashboard helpers into domain/dashboard.py so widgets and
  the dashboard router reuse the same logic.
- Add adapter and data-endpoint tests.
- Update apply-progress.md.

Verification: ruff clean; backend pytest 200 passed; frontend lint/build green.
2026-06-21 10:09:45 +00:00
Developer 200d319fb0 feat(widgets): add backend CRUD, registry, and default seeding
Introduce a closed, compile-time widget registry and backend CRUD for
dashboard widget instances.

- Add dashboard_widgets SQLite table in SettingsStore with CRUD helpers and
  default seeding (Jellyfin + Backups) on first install.
- Add Pydantic models with credential-key and secret-value rejection.
- Add widgets router: /api/widgets/sources, /types, /instances CRUD.
- Call ensure_defaults() in app lifespan so fresh installs seed defaults.
- Add backend tests covering registry, CRUD, validation, and seeding.
- Include SDD artifacts: exploration, proposal, spec, design, tasks.
2026-06-19 20:07:47 +00:00
Developer 08a3b616f6 refactor(monitoring): decommission legacy SSH-scraping poller (slice 1)
The 2026-06-16/17 observability update externalised metrics to
Prometheus + node_exporter + Grafana, but the legacy Manage-side
SSH-scraping monitor was never removed. It duplicated the new stack,
ran SSH df on every machine every 300s, and fed nothing (its UI was
deleted in e2ad731). This slice decommissions the duplication.

Removed (backend):
- services/monitoring_poller.py (MonitoringPoller) — entire file
- services/monitoring_actions.py (disk_space, run_machine_operation,
  poll_machine_snapshot, build_machine_client) — entire file;
  run_machine_operation had only 2 callers (the poller + /disk), both gone
- tests/test_monitoring_actions.py
- endpoints: POST /api/monitoring/poller, GET /machines/{id}/actions,
  GET /disk (and the now-dead _resolve_machine helper)
- lifespan wiring (main.py), dependency wrapper (dependencies.py),
  poller.start()/kick() from machine save (routers/settings.py)
- SettingsStore: monitoring_machine_actions table CREATE + 2 indexes +
  record/list/prune_machine_actions methods; DROP TABLE IF EXISTS on
  startup cleans existing DBs (user-approved)
- config knobs: monitoring_poll_interval_seconds,
  monitoring_poll_initial_delay_seconds, monitoring_action_retention_days
- test_api.py: TestMonitoring._ensure_machine + test_disk

Kept (fits the new model): /machines, /prometheus-targets, /alerts,
/alertmanager-status, /alertmanager-webhook; the disk_usage JOB template
(manual on-demand, not monitoring); node_exporter_* machine fields
(they point Prometheus at the right host).

Gate: backend pytest 173 passed; ruff clean.
2026-06-17 20:48:56 +00:00
Developer 1c29299e8c chore(openspec): archive web-ui-rework change
Move the completed, verified, synced change folder to the dated archive:
openspec/changes/web-ui-rework/ -> openspec/changes/archive/2026-06-17-web-ui-rework/

Canonical spec stays in place at openspec/specs/web-ui/spec.md (not moved).
This is the physical archive step deferred (documented-pending-manual) in
archive-report.md ec46e26; user-approved.
2026-06-17 19:44:57 +00:00
Developer 9cae5fc98c chore(openspec): archive report — web-ui-rework lifecycle complete
Disposition: documented-pending-manual (archive-move deferred to avoid guessing
the OpenSpec archive convention destructively). Remaining manual step recorded:
move openspec/changes/web-ui-rework/ -> openspec/changes/archive/2026-06-17-web-ui-rework/
(canonical openspec/specs/web-ui/spec.md stays in place).

All 8 lifecycle phases done: proposal/spec/design/tasks/apply/verify/sync/archive.
71/71 tasks complete; gates green (build + lint + vitest 23/64 + node 5/5).

Carry-over follow-ups recorded for future cleanup:
- slice-5 shipped without the prescribed 5a/5b sub-split (correct + tested)
- tasks.md 'node --test tests' cross-check is a pre-existing broken command
  (correct: 'node --test'); recommend docs/npm-script follow-up
- no visual/browser smoke performed (component tests are structural)
2026-06-17 19:36:23 +00:00
Developer 7646f3236f chore(openspec): verify + sync reports, canonical web-ui spec (rework PASS)
Web UI rework lifecycle: verify + sync phases.
- verify-report.md: PASS verdict (23/64 vitest, 5/5 node, zero @mui in src,
  71/71 tasks, all spec scenarios green). Non-blocking findings recorded:
  slice-5 review-budget overage (5a/5b not split); node --test tests cmd typo.
- sync-report.md + openspec/specs/web-ui/spec.md: canonical domain spec
  distilled from the verified change (design system, thin-dashboard model,
  TanStack visibility-only tables, reconciled IA, removed deps, Vitest harness).
- Change-side delta spec under openspec/changes/web-ui-rework/specs/.
2026-06-17 19:15:15 +00:00
Developer 9de2d5b8d2 feat(frontend): slice 8 — remove MUI/@emotion deps + update REQUIREMENTS
Web UI rework. Final slice.
- Remove from package.json: @mui/material, @mui/icons-material,
  @mui/x-data-grid, @emotion/react, @emotion/styled (zero consumers
  remain in src after slices 1-7b; grep-verified).
- Update docs/REQUIREMENTS.md (+63 lines): single design system
  (shadcn/ui + Tailwind v4 + lucide-react), thin-dashboard observability
  model (no in-app charts; Grafana deep-links), TanStack tables
  (visibility-only parity), reconciled IA (Backups top-level nav;
  Media at /media with /applications redirect), repurposed chart-*
  status cues, removed deps list.
- Note: AGENTS.md 'node --test tests' invocation is a pre-existing
  broken command (treats tests/ as a module); correct form is
  'node --test' (auto-discover, 5/5 pass) — verified identical at
  pre-rework baseline ef5311b.

Gate: build + lint + vitest (23/64) + node --test (5/5) green.
Web UI rework complete.
2026-06-17 18:52:27 +00:00
Developer 3dc1b31fc3 feat(frontend): slice 7b — Media on TanStack Table (server pagination)
Web UI rework. Completes the DataGrid migration (7a + 7b):
- pages/Media.tsx off @mui/x-data-grid + @mui/material onto DataTable:
  15 locked columns (title/series/season/episode/type/year/runtime_min/
  size/bitrate/hdr/video/resolution/date_added/library/path);
  enablePagination + manualPagination + rowCount from queryResult.total;
  page state (pageIndex/pageSize) -> offset/limit into useMediaQuery;
  onRowClick -> navigate('/files?path=...') preserved; stable path-derived
  getRowId so selection survives server paging; column-visibility toggle.
  Hard rule honored: NO sorting, NO resizing (visibility-only).
- Migrate Media shell (Select/Input/Progress/Card/grid/Typography/Tabs).
- Media component tests (column set + row-click nav).
- Harness fix: polyfill ResizeObserver in test/setup.ts — jsdom lacks it
  and Radix primitives (Select/ScrollArea/etc.) reference it; was causing
  cross-test failures once Media pulled shadcn Select into the pool.

Gate: build + lint + test green (23 files / 64 tests).
2026-06-17 18:33:59 +00:00
Developer e8b0f1144b feat(frontend): slice 7a — DataTable wrapper + FileBrowser (TanStack Table)
Web UI rework. Highest-risk slice, part 1 of 2:
- New components/ui/data-table.tsx: generic TanStack Table wrapper on the
  shadcn Table primitive. Controlled rowSelection/columnVisibility/
  pagination, optional selection column (stopPropagation on cell click),
  row-click, column-visibility dropdown, manual-pagination support.
  Hard rule honored: NO getSortedRowModel, NO column resizing/sizing.
- Migrate pages/FileBrowser.impl.tsx off @mui/x-data-grid + @mui/material
  onto DataTable: 5 columns (type/name/ext/size/modified), row-click ->
  ffprobe preview preserved, column-visibility toggle, no pagination.
- DataTable + FileBrowser component tests (RED->GREEN).

Gate: build + lint + test green (22 files / 58 tests).
2026-06-17 18:02:47 +00:00
Developer 1e23c07a20 feat(frontend): slice 6a — Users directory surface + drawer (shadcn)
Web UI rework. Slice 6a (force-split; 6b = compose dialog next):
- UsersPage.impl.tsx directory surface off @mui: shadcn Table family +
  Checkbox + Badge (status: success=chart-2/destructive/secondary) +
  Avatar + Tooltip + Progress + Alert/Button/Stack/Typography
- MUI Drawer -> shadcn Sheet side="right" for user detail drawer
  (buildUserDrawerModel rendering preserved)
- Selection-across-pagination + search/filter parity preserved
- Compose-dialog MUI subset (Dialog/TextField/Divider/IconButton +
  9 icons) intentionally LEFT for slice 6b

Gate: build + lint + test green.
2026-06-17 14:36:07 +00:00
Developer b6da7df7f9 feat(frontend): slice 5 — migrate Settings + Actions to shadcn/Tailwind
Web UI rework. Form-heavy pair (controlled useState parity, no form lib):
- pages/Settings.tsx off @mui: monitoring-machine CRUD, SSH-key mgmt,
  SSH test/validation feedback, danger-zone reset (ConfirmDialog), tabs
- pages/Actions.tsx off @mui: saved-task editor, machine selection,
  run history, tabs
- Both reuse migrated shared components (SectionCard/SelectionRailCard/
  TabbedCard/HoverEditButton/ConfirmDialog/DialogFooter) as before
- Behavioral tests added (mocked hooks; no live SSH)

Gate: build + lint + test green (19 files / 39 tests).
2026-06-17 13:47:57 +00:00
Developer c721f0dece feat(frontend): slice 4 — migrate Dashboard + Applications to shadcn/Tailwind
Web UI rework.
- Migrate pages/Dashboard.tsx off @mui (shortcut CRUD dialogs, machine
  switcher, BackupDashboardWidget mount; shortcuts reuse ConfirmDialog/
  DialogFooter from slice 2)
- Migrate pages/Applications.tsx shell off @mui (tabs + library counts);
  keeps <Media/> child intact (Media.tsx still MUI, deferred to slice 7
  with its DataGrid)
- Behavioral tests for both pages

Gate: build + lint + test green.
2026-06-17 13:15:51 +00:00