diff --git a/CONTEXT.md b/CONTEXT.md index 7bc0e2e..5038811 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -3,11 +3,26 @@ > Created with the REUSE.md rollout (2026-07-03). Authoritative history: `hub/CHANGELOG.md` (hub), > `website/CHANGELOG.md`, `scripts/CHANGELOG.md`; end-of-task detail in `REPORT.md`. -- **⚠️ 2026-07-11 — hub v0.47.0 host-delete WIP rode along in commit `146d165`** (a docs commit's - `git add -A` swept the uncommitted working tree: `store.go` DeleteHost + `hosts.go` handler + - delete tests + host_detail_body template additions, continuing the `ae950e5` v0.47.0 arc). The - full hub green gate passes on it and NOTHING deployed (manifest still 0.46.0) — but it shipped to - main unreviewed by its author. Review/continue the v0.47.0 arc from there. +- **2026-07-11 — HUB v0.47.0 UI REORGANIZATION SHIPPED** (CHANGELOG v0.47.0; commits `9f29bf3` → + `0daddcd` + docs). Five deliverables: **(1)** `.data-table td a:not(.btn)` button-contrast fix; + **(2)** customer page = **8 hash tabs** (`#tab=…`, sticky summary strip, Events error badge, + no-JS graceful degradation — panels hide only via a JS-added body class); **(3)** shared + `host_detail_body` sub-template rendered by `/hosts/{id}` AND the new per-customer **Host tab** + (a list by design; `store.ListHostsByCustomer` + `hostDetailData` builder); **(4)** **stale host + removal** — `GET /hosts/{id}/delete-impact` (counts/booleans only) + `POST /hosts/{id}/delete` + behind type-to-confirm; ONLINE → 409 always (no override), escrow needs an explicit checkbox + (`ErrHostEscrowPresent`, tx never starts), one-tx cascade incl. the bound wg peer (wgsync's 5-min + declarative push converges the endpoint; log bundles die by `scope_id == host_id` only); + **(5)** **/offsite multi-endpoint management UI** — all `wg_endpoints` rows as cards + + add/edit/delete with 409 guards (peers-in-subnet pins subnet + delete). **Deferral (explicit): + peer allocation, the wgsync reconciler push and the desired-state merge stay lowest-endpoint-id + (`GetWGEndpoint` untouched); the future arc is a `wg_peers.endpoint_id` migration + per-endpoint + allocation/reconciler/desired-state.** Five red-proofs ran (online gate, escrow ack, bundle + scope, endpoint-delete guard, subnet-change guard). *Resolved:* the earlier ⚠️ about `146d165` + sweeping the Part-4 WIP — the sweep caught `hosts.go` mid-red-proof (escrow ack bypassed); + `068427a` restored the gate; the arc is now complete and author-reviewed. **Stale-doc note:** the + workspace-root CLAUDE.md's hub deploy wording predates GitOps — deploys are manifest-tag bumps + + deliberate ArgoCD sync, never `kubectl set image`. - **2026-07-11 — RCA FIXES 1+2+4 SHIPPED + Q1c GREEN (agent v0.84.0 + controller v0.117.0 LIVE on demo).** Reboot survival: automatic since agent 0.84.0, live-proven 2026-07-11 21:20 (`pct reboot diff --git a/REUSE.md b/REUSE.md index 9695e1b..2a7f461 100644 --- a/REUSE.md +++ b/REUSE.md @@ -55,6 +55,16 @@ | `parseSQLiteTime` | hub/internal/store/store.go (~L1160) | `(s string) time.Time` | Parsing ANY timestamp read from SQLite | modernc/sqlite returns multiple formats; raw `time.Parse` will intermittently zero out. Always use this. | | `compareVersions` | hub/internal/web/server.go (~L571) | `(a, b string) int` | X.Y.Z comparisons in web (floor checks, update-available) | Returns 0 on parse error — unparseable compares as "equal" (see §3). | +### Host views & lifecycle / offsite endpoints (v0.47.0, hub/internal/web + store) + +| Symbol | File | Short signature | Use for | Gotchas | +|---|---|---|---|---| +| `(*Server).hostDetailData` | hub/internal/web/hosts.go (~L282) | `(host *store.Host, r) map[string]interface{}` | The ONE view-model builder for the shared `host_detail_body` sub-template (standalone `/hosts/{id}` + customer Host tab) | Booleans/counts only for DR/escrow; carries `Deletable` (= status != "ok") which gates the danger-zone card. Never add a secret field. | +| `host_detail_body` sub-template | hub/internal/web/templates/host_detail_body.html | `{{template "host_detail_body" .}}` | Rendering a host's detail sections on ANY surface | One namespace across ParseFS (icons.html pattern). Renders per-host — id-suffix any new element ids with `{{.HostID}}` (the customer page renders N instances). | +| `(*Store).ListHostsByCustomer` | hub/internal/store/store.go (~L1620) | `(customerID) ([]Host, error)` | A customer's hosts, host_id order | A LIST by design (HA-cluster roadmap) — don't collapse to GetHostByCustomer. | +| `(*Store).CountHostArtifacts` / `DeleteHost` | hub/internal/store/store.go (~L1640/~L1690) | `(hostID) (HostArtifacts, error)` / `(hostID, deleteEscrow bool) error` | Host-delete impact preview + the ONE-transaction cascade | ONLINE gate lives in the handler, escrow gate in the store (`ErrHostEscrowPresent`, tx never starts). log_bundles die by `scope_id == host_id` ONLY (customer-scoped bundles survive). The wg_peers delete is INSIDE the tx — never split it out. | +| `(*Store).ListWGEndpoints` / `DeleteWGEndpoint` | hub/internal/store/wg.go (~L64/~L86) | `() ([]WGEndpoint, error)` / `(endpointID) error` | The /offsite endpoint-management surface | `GetWGEndpoint` (lowest id, LIMIT 1) stays THE allocation/sync endpoint — do not switch allocator/reconciler/desired-state to the list without the `wg_peers.endpoint_id` migration arc. Peers-in-subnet guards live in web/offsite.go. | + ### Artifact manifest / Day-0 trust root | Symbol | File | Short signature | Use for | Gotchas | diff --git a/hub/CHANGELOG.md b/hub/CHANGELOG.md index a60cdd3..4ef4052 100644 --- a/hub/CHANGELOG.md +++ b/hub/CHANGELOG.md @@ -1,5 +1,55 @@ # Felhom Hub — Changelog +## v0.47.0 — UI reorganization: customer tabs, Host tab, stale-host removal, offsite multi-endpoint UI, button contrast (2026-07-11) + +Five hub-side deliverables; no agent/controller/protocol changes. Baseline `8e1a3f0` +(v0.46.0); commits `9f29bf3` → `ae950e5` → `146d165`(swept WIP) → `068427a` → `0daddcd`. + +- **CSS button contrast** (`templates/style.css`): `.data-table td a` → `:not(.btn)` (base + + hover) — `` inside data-table cells (host-detail Diagnostics View/Download, + customer log-tail buttons) rendered blue-bright on blue-bright, i.e. invisible. Plain table + links keep the bright-link style; `.btn` itself untouched, no `!important`. +- **Customer page tabs** (`templates/customer_unified.html`, `style.css`): the ~18 stacked + sections split into 8 client-side hash tabs (`#tab=` overview / applications / setup / + settings / backup / events / notifications / host) + a sticky summary strip (name, status, + controller version, last report, containers chip). Graceful degradation is load-bearing: + panels hide only under a JS-added `body.js-tabs` class — no JS = every section visible, all + existing render tests pass unmodified. Events tab carries a red error-count badge (reuses + the already-fetched `CountEventsBySeverity` data — no new query). The auto-refresh reload + preserves the hash → the active tab survives. No handler/data-model change for the tabs. +- **Host tab + shared sub-template** (`templates/host_detail_body.html`, `web/hosts.go`, + `web/configs.go`, `store.ListHostsByCustomer`): the host-detail body extracted into a + `{{define "host_detail_body"}}` rendered by BOTH `/hosts/{id}` (chrome + call) and the new + per-customer Host tab (a LIST by design — 1 host today, N for a later HA cluster; empty + state otherwise). `handleHostDetail`'s data assembly extracted into `hostDetailData`. +- **Stale host removal** (`store.CountHostArtifacts`/`DeleteHost`, `web/hosts.go` handlers, + routes above the `/hosts/` catch-all): `GET /hosts/{id}/delete-impact` (counts/booleans + ONLY) + `POST /hosts/{id}/delete` behind a type-to-confirm dialog (global-floor pattern). + Gates: ONLINE host → 409 always (no override — a live agent would 401 forever; enroll is + passphrase-gated mint-once); confirm mismatch → 400; escrow present without the explicit + checkbox → 409 with the tx never started (`ErrHostEscrowPresent`, fail-safe-to-refuse). + One transaction cascades guests, host_reports, signed_jobs, host_recovery, + host_pbs_secrets, host-scoped log bundles (`scope_id == host_id` ONLY — customer-scoped + bundles survive), the bound wg peer (inside the tx — no stranded peer on crash), escrow + (only when acked), then the host row. The wgsync 5-min declarative push converges the + endpoint afterwards — no reconciler change. Danger-zone card renders only when deletable, + so the hosts-list zero-`