P2R-02: UI rewire against the slim-schedule + source-group model #2
Reference in New Issue
Block a user
Delete Branch "p2r-02-ui-rebuild"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Phase 4 of the P2 redesign — rebuilds the host-detail UI against the new model (slim schedules + source groups + host-level repo maintenance). Six slices, all walked end-to-end with Playwright on the live dev server.
host_chromepartial, version indicator restored.PUT /api/hosts/{id}/bandwidth._diag/p2r-02-sweep/.Side-fix: agent runner drops restic's noisy
statusevents fromlog.stream(already covered by the throttledjob.progressenvelope).Test plan
Sources tab now lists every source group on the host with per-row counts (used-by-N-schedules, snapshot count by tag), the v4 conflict tag (keep-* dimension that has no compatible cadence), and Run-now / Edit / Delete actions. Run-now reuses the existing HTMX-aware /hosts/{id}/source-groups/{gid}/run handler. New /hosts/{id}/sources/new and /sources/{gid}/edit form: name + includes/excludes textareas + the 3×2 keep-* retention grid + retry-on-offline knobs. Server-side validation re-renders with the operator's input intact; the inline conflict banner shows above the retention grid when ConflictDimension is set. Delete blocks (UI + server) when the group is referenced by any schedule. Every successful mutation calls pushScheduleSetAsync so an online agent re-arms within seconds. Adds .src-row and .keep-cell to input.css for the row + retention grid layout.Aligns Sources and Schedules tab rows with the dashboard's row-click UX: whole-row click navigates to the row's edit page (mirroring .host-row.clickable). Drops the redundant Edit buttons; Run-now and Delete remain in .row-action cells that sit above the row-link overlay via z-index. Schedule edit form's cron preset chips now carry human-readable title= tooltips ("Every day at 03:00", "Every Sunday at 03:00", etc). tasks.md gets a binding row-design rule covering all current and future list-row templates, and the P2R-02 entry is split into the six slices already agreed with the operator (slices 1–3 marked done, 4 next).Surface the Run-now button on every schedule when the host is online, not just enabled ones. Disabled rows render the button as a non-primary style + a HX-confirm dialog ("This schedule is paused — running it now won't change that. Fire it once anyway?"); enabled rows keep the zero-friction primary button. Server-side, Run-now no longer short-circuits on !Enabled — it dispatches the source groups inline rather than via dispatchScheduledJob (which always bails on disabled schedules, since cron-tick semantics are different from explicit operator intent). The audit-log entry inside dispatchBackupForGroup still records every fire.Three independent forms on /hosts/{id}/repo so saving one section doesn't disturb the others: * Connection: edits repo URL, username, password (pre-filled from the redacted GET /api/hosts/{id}/repo-credentials view; password field shows masked stored-creds placeholder; blank password = keep existing). On save, encrypts and pushes config.update to a connected agent. * Bandwidth: host-wide upload/download caps (KB/s; blank = no cap) written via store.SetHostBandwidth. New REST endpoint PUT /api/hosts/{id}/bandwidth for JSON callers. * Maintenance: forget/prune/check cadences + check subset %, with per-row enabled toggles. Reuses cronParser for validation; auto-seeds the row if a host pre-dates the migration. Right-rail surfaces repo size, snapshot count, snapshots-by-tag breakdown (counted from existing snapshot tag rows), and an 'untagged snapshots are left alone' note. Danger-zone re-init button is rendered but disabled with a hint pointing at P2R-09 (real implementation lands there). Validation re-renders the page with the relevant form's banner and all other section state intact. Successful saves redirect with a ?saved=<section> query param so the page surfaces a small ✓ saved indicator on the relevant form. ci.yml: bump golangci-lint-action v6→v7 (separate change picked up in this commit).Schedules tab Run-now used to silently HX-Redirect back to the list, leaving the operator wondering whether the click registered. Now: * Single-source-group schedule → HX-Redirect to that one job's live log, matching the per-source-group Run-now UX from Sources. * Multi-group schedule → stay on the schedules list and fire a success toast ("N backups dispatched: <group names>") via the existing rm:toast HX-Trigger channel, so the operator sees clear acknowledgement without losing their place. dispatchBackupForGroup now returns the persisted job ID so the caller can choose between job-log redirect and toast feedback; on any internal failure it returns "" and the warning still hits slog as before. The cron-fired path (dispatchScheduledJob) ignores the return value, behaviour unchanged.Replace the placeholder 'Open →' link with a per-host Run-now decision computed server-side once per render: * If the host has exactly one enabled schedule whose source-group set covers every group on the host → primary 'Run all groups' button (HX-POST to that schedule's /run endpoint, fires every backup the host knows about in one click). * Otherwise (zero matches, multiple matches, or any ambiguity) → ghost 'Open →' link to /hosts/{id}/sources, where the operator picks per-group from the source-group rows. dashboardPage.Hosts moves from []store.Host to []dashboardHostRow to carry the precomputed RunAllScheduleID; host_row.html now reads .Host.* and .RunAllScheduleID. Two extra store calls per host on dashboard render — fine at fleet sizes we care about; if we ever need to support thousands of hosts we'll batch these queries.Cleanup pass over the repo so CI can enforce lint going forward without the only-new-issues escape hatch: * gofumpt -w across the tree (31 hits, all formatting) * misspell --fix (25 hits, US-locale spelling) — but reverted on api.JobCancelled = "cancelled" since that literal is the wire + DB CHECK constraint value, plus matched the case in store/fleet.go back to "cancelled" and added //nolint:misspell on both for the next time someone reaches for the auto-fix * Wrap every `defer rows.Close()` / `defer stmt.Close()` / `defer res.Body.Close()` in `defer func() { _ = .Close() }()` to satisfy errcheck without losing the close itself * websocket.Dial callers (1 prod, 4 tests) now capture + close the upgrade response Body — coder/websocket can return res with a nil Body on success, so the test deferred-closes guard against that * Annotate the two genuine-by-design nilerr cases with //nolint comments explaining why nil-on-error is the contract (cookie missing = no session; ctx cancelled mid-backoff = clean shutdown) * Add brief godoc on the 10 exported const groups + types that revive flagged (api.HostOS/HostArch/JobKind/JobStatus/LogStream/ ErrorCode, restic.EventKind, store.Role, web.FS) * Drop the unused (*Server).userByID method * Inline the unparam baseView(active) — every UI page is under the dashboard primary nav today Result: `golangci-lint run ./...` reports 0 issues. CI lint job no longer needs only-new-issues: true; X-06 follow-up entry in tasks.md removed.The v2.1.6 release binary is built with Go 1.24, and golangci-lint refuses to load a config targeting a newer toolchain than itself ('Go language version (go1.24) used to build golangci-lint is lower than the targeted Go version (1.25.0)'). go.mod is on 1.25, so the binary needs to be too. Locally this didn't bite because 'go install …@v2.1.6' compiled v2.1.6 against the local Go 1.25 toolchain; CI uses the prebuilt release tarball which carries the build-time Go version. v2.5.0 is the first v2.x line built with Go 1.25 — pin in lockstep with go.mod going forward.