Commit Graph

258 Commits

Author SHA1 Message Date
Developer 9bc8fab971 chore: fold pre-existing ServicesPage.tsx formatter stray
Whitespace-only JSX reflow (prettier) from earlier #1 validation-surfacing fix;
folding so the working tree goes pristine before the final push.
2026-07-10 00:17:51 +00:00
Developer 29650ca512 chore(per-instance-hook-scoping): archive verified+synced change
Move to openspec/changes/archive/2026-07-09-per-instance-hook-scoping/
(R100 renames preserved). 9 artifacts. Canonical openspec/specs/
service-instance-scoping/ remains. Resolves multi-instance wrong-data bug
(hooks now scope by instance.id; instance switcher re-scopes).
Carry-overs: fetchBackupDashboard untouched (design decision 5); subquery
scoping for runs/alerts (schema asymmetry).
2026-07-10 00:17:51 +00:00
Developer f921524d37 spec(per-instance-hook-scoping): sync into new canonical domain
New canonical openspec/specs/service-instance-scoping/spec.md (21 reqs
PI-101..121). Change-side delta + sync-report. web-ui/prometheus-charting/
service-storage/service-credential-testing canonicals untouched.
2026-07-10 00:12:54 +00:00
Developer 8d3c44d87f spec(per-instance-hook-scoping): verify + reconcile tracking
Write apply-progress.md, tick all 17 tasks, add verify-report.md (21/21 PI-101..121
PASS). Gates green: 368 pytest, ruff clean, npm build+lint 0 errors, 165 vitest.
fetchBackupDashboard/useBackupDashboard/get_backup_dashboard confirmed untouched
(design decision 5). No blocking code findings.
2026-07-10 00:05:58 +00:00
Developer 3bc7ce5269 feat(per-instance-hook-scoping): scope observability + backup hooks by instance 2026-07-09 23:54:31 +00:00
Developer ad61d92b32 spec(per-instance-hook-scoping): add tasks (single slice, ~257 lines)
Backend backup endpoint+store filter (subquery for runs/alerts); frontend 6
hooks + 7 API fns + 3 tabs. fetchBackupDashboard excluded (widget path).
Each gate green.
2026-07-09 23:40:39 +00:00
Developer dbc332d1b6 spec(per-instance-hook-scoping): add design
6 decisions: queryKey appends serviceId??''; API client reuses get(path,params);
service_id: str|None=None on backup endpoints; store filter via subquery for
runs/alerts (schema asymmetry — only backup_jobs has service_id column);
fetchBackupDashboard excluded (widget path, PI-117 risk). Single slice ~257
lines. 3 source findings: Alertmanager/Prom endpoints confirmed already take
service_id; backup runs/alerts attributed via FK chain (subquery needed).
2026-07-09 23:37:35 +00:00
Developer 87f42b4ec3 spec(per-instance-hook-scoping): add spec (21 reqs PI-101..121)
Correctness fix for multi-instance: 6 hooks gain optional serviceId in queryKey;
7 API fns append ?service_id; 4 backup endpoints gain service_id filter
(Alertmanager/Prometheus status already take it — zero backend change there);
3 tabs pass instance.id; dashboard widgets unaffected; all params optional
(backward-compat). usePrometheusTargets + useMonitoringMachines stay global.
2026-07-09 23:32:40 +00:00
Developer 5addc9dae9 chore(service-credential-tester): archive verified+synced change
Move to openspec/changes/archive/2026-07-09-service-credential-tester/
(R100 renames preserved). 9 artifacts. Canonical openspec/specs/
service-credential-testing/ remains. Resolves qBit 'login failed' #3 pain
at the UI layer (auth failure surfaced in result pill, no log-digging).
Carry-overs in archive-report incl N-2 strengthened, N-6 presentational panel,
edit-surface-is-Settings.tsx source-finding.
2026-07-09 23:29:19 +00:00
Developer 6bcb60a74d spec(service-credential-tester): sync into new canonical domain
New canonical openspec/specs/service-credential-testing/spec.md (21 reqs
CT-101..121). Change-side delta + sync-report. web-ui/prometheus-charting/
service-storage canonicals untouched.
2026-07-09 23:22:53 +00:00
Developer 98bf496a98 spec(service-credential-tester): verify + strengthen no-secret-logs test + reconcile
Strengthen test_secrets_not_logged (N-2): now sends real-looking secrets
through prometheus + qbittorrent test_callables (mocked at network boundary),
asserts no fragments leak into caplog, verified non-vacuous. Write
apply-progress.md, tick all 29 tasks, add verify-report.md (21/21 PASS).
Gates green: 362+ pytest, ruff clean, npm build+lint 0 errors, 158 vitest.
2026-07-09 23:15:47 +00:00
Developer f6c67bd3ff feat(service-credential-tester): slice 2 — Test button + gating (shared ServiceTestPanel)
Presentational ServiceTestPanel (props-driven, no internal hooks) wired into
both CreateServiceDialog (ServicesPage.tsx) and ServiceConfigEditor
(Settings.tsx). Parent owns testResult + saveAnyway state; store-previous
pattern resets on input change (avoids setState-in-effect). Create/Save
button gated on testPassed || saveAnyway. 7 panel tests (button states,
success/failure pills, checkbox toggle). All gates: 158 vitest, build exit 0,
lint 0 errors, 362 backend pytest (regression).
2026-07-09 22:57:02 +00:00
Developer 3391fbc85d feat(service-credential-tester): slice 1 — backend test endpoint + per-type routines
TestResult dataclass + translate_connection_error shared helper in base.py.
test_callable field on ServiceDefinition (default None). 7 per-type
test_connection routines (qbittorrent, prometheus via Grafana gateway,
alertmanager, jellyfin, authentik, ssh_tasks via build_ssh_client, nextcloud).
POST /api/services/test endpoint: validation-first (422 on malformed config),
dispatch, no-persistence, no-secret-logs. backups has test_callable=None.
qBit 'Fails.' → specific auth message (resolves #3 at API layer).
Backend: 362 pytest pass (+31 new), ruff clean. Frontend: build green.
2026-07-09 22:41:06 +00:00
Developer c4f68b4938 spec(service-credential-tester): add tasks (2 slices, each <=400 lines)
S1 backend: TestResult + test_callable + translate_connection_error + POST
/api/services/test + 7 per-type routines + tests. S2 frontend: type + API fn +
hook + shared ServiceTestPanel wired into CreateServiceDialog + Settings.tsx
ServiceConfigEditor (per design source-finding). Each slice leaves pytest/npm
build/npm lint green.
2026-07-09 22:25:27 +00:00
Developer 1fc3127b58 spec(service-credential-tester): add design
8 decisions: TestResult dataclass, test_callable(store, config, secrets),
translate_connection_error shared helper (extracts test_machine_ssh patterns),
POST /api/services/test validation-first, shared ServiceTestPanel component,
field-edit-clears-result, Prom test via Grafana gateway, ssh_tasks reuses
build_ssh_client. 2-slice plan. Source findings: edit dialog is in Settings.tsx
ServiceConfigEditor (not ServicePage.tsx — spec drift); test_machine_ssh is
inlined in router (not reusable as-is).
2026-07-09 22:21:03 +00:00
Developer ce5ee4f0a0 spec(service-credential-tester): add spec (21 reqs CT-101..121)
Per-type test routines for 7 remote types (qbittorrent, prometheus via Grafana
gateway, alertmanager, jellyfin, authentik, ssh_tasks, nextcloud) + backups
(no test). Endpoint POST /api/services/test, no persistence, friendly error
translation (resolves qBit login #3 at UI layer). Frontend Test button + gate
Create/Save on pass with Save-anyway override. Stale-proposal correction:
jellyseerr is not in the registry (merged into jellyfin).
2026-07-09 22:12:32 +00:00
Developer a5ca1521fe spec(service-credential-tester): update prom test to gateway path
Dependency on grafana-metric-gateway: the prometheus service now sources via
Grafana /api/ds/query (no direct Prom endpoint). Test routine changes from
GET /api/v1/query?query=up to POST {grafana_url}/api/ds/query with api_key +
datasource_uid, expr 'up'.
2026-07-09 22:07:37 +00:00
Developer 9236fd8ac2 chore(grafana-metric-gateway): archive verified+synced change
Move to openspec/changes/archive/2026-07-09-grafana-metric-gateway/
(R100 renames preserved). 9 artifacts. Canonical openspec/specs/
prometheus-charting/ (30 reqs, first non-additive sync) remains. Carry-overs
in archive-report: SC-106 stale component name (cosmetic); partial revert of
prometheus-direct-charting per new network constraint.
2026-07-09 22:06:43 +00:00
Developer cb8dd13514 spec(grafana-metric-gateway): sync — FIRST non-additive (MODIFIED) canonical
16 MODIFIED prometheus-charting requirements (transport: direct Prom -> Grafana
gateway; intent preserved where applicable), 3 ADDED (SC-128 gateway status,
SC-129 startup validation, SC-130 sanctioned transport), 11 PRESERVED, 0 REMOVED.
All 16 MODIFIED headers matched canonical exactly. Post-sync: 30 requirements.
web-ui + service-storage canonicals untouched.
2026-07-09 22:01:55 +00:00
Developer c886fcdf09 spec(grafana-metric-gateway): verify + close GM-115 + reconcile tracking
Add 3 test cases (startup old-config validation warning; status auth_failed
for 401/403) closing the GM-115 PARTIAL. Write apply-progress.md, tick all 33
tasks, add verify-report.md (15/16 PASS, 1 PARTIAL->PASS). Gates green: 331+
pytest, ruff clean, npm build+lint 0 errors, 151 vitest.
2026-07-09 21:53:18 +00:00
Developer 7e91e7f931 feat(grafana-metric-gateway): slice 2 — rename widgets to Metric*
git mv PrometheusChartWidget→MetricChartWidget, PrometheusGaugeWidget→
MetricGaugeWidget, PrometheusMeanWidget→MetricMeanWidget (+ 3 test files,
R100 history preserved). Update registry imports/refs + barrel exports.
Adapt PrometheusMetricWidget for §3.4 Option A: read normalized {result:
[{label,points}]} series shape (last-point extraction) instead of old Prom
{resultType,result} vector. PrometheusMetricWidget NOT renamed (design §3.1).
GM-111/112/116 satisfied. All gates: 151 vitest, build exit 0, lint 0 errors.
2026-07-09 21:34:26 +00:00
Developer df80c68f89 feat(grafana-metric-gateway): slice 1 — backend gateway transport
Route all prometheus widget queries through Grafana /api/ds/query instead of
direct Prom HTTP. PrometheusConfig: drop base_url, add grafana_url +
datasource_uid; secret grafana_api_key (required). PrometheusWidgetSource →
MetricSource with _gateway_query POST method. normalize_grafana_frames
recovered from 65bae95 + shared _dedup_label helper. Gateway-path status
check. Startup old-config validation. CHANGELOG migration note. All adapter
tests rewritten for POST /api/ds/query + Grafana frames mock. Backend: 331
pytest pass, ruff clean. Frontend: build green (unchanged in S1).
2026-07-09 21:26:05 +00:00
Developer 798196ffc7 spec(grafana-metric-gateway): add tasks (2 slices, each <=400 lines)
S1 backend transport: gateway config + normalize_grafana_frames (from 65bae95)
+ MetricSource adapter + status + validation + CHANGELOG + tests. S2 frontend:
git mv widget renames to Metric* + registry/barrel updates. Each slice leaves
pytest/npm build/npm lint green. 5 risk flags incl first non-additive sync.
2026-07-09 21:01:38 +00:00
Developer 872e95f8f7 spec(grafana-metric-gateway): add design
8 design decisions: PrometheusConfig (grafana_url/datasource_uid + grafana_api_key
secret), /api/ds/query body per widget kind (window presets -> intervalMs/
maxDataPoints), normalize_grafana_frames refactored from 65bae95 into
prometheus_range.py (shares label-dedup with matrix normalizer), MetricSource
adapter, gateway-path status check, git mv widget renames, startup old-config
validation. 2-slice plan. 3 source findings flagged.
2026-07-09 20:57:07 +00:00
Developer bf8de32815 spec(grafana-metric-gateway): add spec (16 reqs GM-101..116)
Gateway transport: prometheus service config gains grafana_url/api_key/
datasource_uid; all queries via POST /api/ds/query; frames->series restored.
First non-additive canonical sync: MODIFIES 17 SC- requirements (transport
changes, intent preserved), PRESERVES 11, ADDS 3 (gateway status, startup
validation, sanctioned-transport statement). 3 spec assumptions settled where
proposal was silent.
2026-07-09 20:49:45 +00:00
Developer 8afdd9c2bc spec(grafana-metric-gateway): add proposal
Route metric queries through Grafana /api/ds/query instead of direct Prom
(Prom is firewalled / unreachable from Manage; Grafana is the only path).
Partial revert of prometheus-direct-charting: keep the gauge/mean/
LineSeriesChart rendering, restore the frames->series normalizer (recovered
from git 65bae95), change prometheus service config to hold Grafana gateway
fields (url + api_key + datasource_uid). Rename widgets to neutral Metric*.
First non-additive canonical sync (prometheus-charting MODIFIED). Updates the
pending service-credential-tester proposal dependency.
2026-07-09 20:41:59 +00:00
Developer 3e3e69b0be spec(service-credential-tester): add proposal
On-demand per-service-type credential tester for the add/edit dialog. Model on
test_machine_ssh. POST /api/services/test dispatching to per-type routines
(qBit login+maindata, Prom instant query, Jellyfin /Users, etc.) returning
{ok, detail, evidence}. Frontend Test button + gate Create/Save on pass with
'Save anyway' override. Resolves #3 (qBit login failures visible in UI not
logs) and complements #1 (validation surfacing, already shipped).
2026-07-09 20:13:57 +00:00
Developer cadb6d0991 fix(nav): add qBittorrent to per-service-type navbar entries
qBittorrent was missing from SERVICE_TYPE_NAV_ENTRIES, so configured qBit
instances never appeared in the left nav (unlike jellyfin/prometheus/etc.).
Add entry with Magnet icon. navEntries.test.ts filters by configured types
(no fixed-count assertion) so it stays green.
2026-07-09 20:11:12 +00:00
Developer 493c0e1aeb fix(services): surface validation errors in add-service dialog
CreateServiceDialog.save() awaited mutateAsync without a try/catch, so a
backend 422 (e.g. base_url missing http:// schema) threw uncaught and the
dialog sat silent with no feedback. Wrap in try/catch, hold the error in
local state, render a destructive Alert above the footer. Reset/onClose
only on success; on error the user can fix and retry.
2026-07-09 20:10:46 +00:00
Developer a9381e2471 spec(per-instance-hook-scoping): add proposal
Correctness fix: observability + backup hooks query globally, so multi-instance
service pages show data for the wrong instance. Tabs already accept instance
prop with TODO comments; backend mostly supports service_id already. Scope:
add serviceId to 6 hooks + fetch fns + 3 tabs; add service_id to backup
endpoints. Backward-compatible (optional params). ~250-350 lines, single slice.
2026-07-09 14:35:14 +00:00
Developer e461279566 chore(services-as-hub-ia): archive verified change (paperwork-only)
Code merged to main on 2026-06-26 (01527ae) + fix passes. All 10 ACs PASS.
~8600 ins / ~3900 del across 103 files. Only SDD artifacts were untracked.
No code changes, no canonical sync. NOTE: verify-report describes a Grafana
LinksTab later removed by prometheus-direct-charting (2026-07-08); historically
accurate point-in-time record — see archive-report.md footnote.
2026-07-09 14:11:15 +00:00
Developer 1fe7c06083 chore(mobile-responsive-parity): archive verified change (paperwork-only)
Code merged to main on 2026-06-26 via rebase (01527ae) + bug-fix passes
(5a43894/04871bd/f7f590f). All 8 ACs PASS. Only SDD artifacts were untracked;
this archive closes the paperwork gap. No code changes, no canonical sync.
See archive-report.md for carry-over notes (iOS real-device test residual).
2026-07-09 14:11:05 +00:00
Developer ec7ebd7013 chore: regenerate stale .pi-map.md / .pi-map.index.md project maps
Project map had drifted: claimed recharts unused (was used), ObservabilityPage.tsx
exists (refactored to service-tabs/), missed JellyfinNowPlayingWidget +
authentik/backups service types. Regenerate to reflect post-change reality:
new Qbittorrent/LineSeriesChart/Prometheus{Gauge,Mean}Widget files,
service_data.py/qbittorrent_store.py, canonical prometheus-charting +
service-storage specs, archived changes.
2026-07-09 14:05:56 +00:00
Developer 98f17f6e5d chore: apply pre-existing formatter reformat to MediaTab.tsx
Whitespace-only line reflow (formatter-on-save artifact from a prior session,
not logic). Committing to pristine the working tree.
2026-07-09 13:50:36 +00:00
Developer 5cb5e79032 chore(service-storage-harness): archive verified+synced change
Move to openspec/changes/archive/2026-07-09-service-storage-harness/
(history preserved via rename detection). 9 artifacts: proposal/spec/design/
tasks/apply-progress/verify-report/sync-report/archive-report + delta spec.
Canonical openspec/specs/service-storage/ remains. Native status engine
discrepancy (ambiguous change selection) disregarded per parent verification.
2026-07-09 09:47:05 +00:00
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 75c949ad25 feat(service-storage-harness): slice 4 — cascade-delete wiring + integration test
Wire ServiceDataHarness.cascade_delete into SettingsStore.delete_service
(best-effort try/except, logs on failure). Fix migration runner to also
catch 'no such table' on fresh DBs (ALTER TABLE before init_schema).
Integration test proves end-to-end cascade across both concerns (qBit
samples + media items) with multi-instance preservation.

Backend: 322 pytest pass, ruff clean.
2026-07-09 09:11:42 +00:00
Developer c87f398e37 feat(service-storage-harness): slice 3 — migrate MediaIndex onto harness (scoped replace_items, +service_id)
Register MediaIndex as a harness concern with ALTER TABLE migration to add
service_id column (idempotent). Scope replace_items by service_id (FIXES latent
global-clear bug where building for one Jellyfin wiped another's rows). Scope
query by service_id (empty-string = all rows, backward-compat). Thread
service_id through build_media_index + worker + query_media router. New
regression test proves scoped replace preserves other services' rows.

Backend: 321 pytest pass, ruff clean. Frontend: build green.
2026-07-09 09:00:31 +00:00
Developer 1fb12b8a0a feat(service-storage-harness): slice 2 — qbit widgets + LineSeriesChart extract 2026-07-09 08:49:20 +00:00
Developer e7bd0afdd1 feat(service-storage-harness): slice 1 — harness + qbit store + client + integration
ServiceDataHarness (services/service_data.py): lifecycle-only registry of
per-concern storage — DB provisioning, idempotent migrations (ALTER TABLE
duplicate-column-name caught per-statement), cascade_delete(service_id).
QbittorrentSampleStore: append/window/prune (MAX_SAMPLES=120) in
qbittorrent.db. QbittorrentClient: cookie-login Web API client (403 re-login,
/sync/maindata). Integration registered with 3 widget kinds (totals/active/
speed). Harness initialized in main.py lifespan.

Backend: 314 pytest pass (21 new), ruff clean.
2026-07-09 08:24:08 +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