diff --git a/REPORT.md b/REPORT.md index 1b1523a..51fe549 100644 --- a/REPORT.md +++ b/REPORT.md @@ -4,52 +4,84 @@ --- -# REPORT — Hub GUI correctness + pull-based config delivery (hub v0.26.0) +# REPORT — Hosts page: read-only fleet view (hub v0.27.0) -**Date:** 2026-06-30 · **Repo:** `felhom.eu` (`hub/`) · **Companion:** felhom-controller v0.94.0 +**Date:** 2026-07-01 · **Repo:** `felhom.eu` (`hub/`) · **Audit finding:** F-M1 (resolved) -Implements the "retire the 5 inbound controls + pull-based config-refresh" task. Resolves audit -`documentation/audits/AUDIT-hub-gui-2026-06-30.md` F-S1/F-S4 + the dead-template findings. +## What & why -## Baselines -- hub `felhom.eu` was `e51e03b` (v0.25.0) → code `a3ac6c9`, manifest `d88b43a` (**v0.26.0**, live on k3s). -- (companion) felhom-controller v0.93.0 → v0.94.0. +The agent enrolls as a **host** and the hub stores rich host state (identity, agent version, guests, +storage targets with SMART, DR/escrow, staleness) and **alerts** on it — but the entire host domain was +**invisible in the GUI** (email-only). This adds a **Hosts** nav section: a fleet **list** + a per-host +**detail** page. **Read-only** (GET only, no host actions/mutation routes) — this surfaces state the +pull/desired-state model demands; it does not reintroduce the inbound control retired in v0.26.0. -## What changed (hub) -- **Pull-based config delivery.** New `customer_configs.config_version` (stored counter, NOT a YAML - hash — `configgen` emits a fresh `session_secret`+timestamp each call). `SaveCustomerConfig` bumps it - on every save (new=1, updates increment). The report ACK (`handleReport`) advertises `config_version` - beside the floor/latest; omitted for report-only customers. -- **Retired the 5 inbound (hub→box) controls** (`01-topology-and-trust.md:11` never-inbound): - Trigger Update, Push Config, Pull Config, Show Diff (+`compareYAMLValues`/`flattenYAML`/`maskSensitive` - helpers + dead `ConfigSyncStatus`/`ConfigDiffCount`) — handlers + routes + buttons + JS deleted. - Geo-disable KEEPS its hub→Cloudflare `RemoveGeoRules`; its inbound `notifyControllerGeoDisable` deleted. -- **GUI staleness + dead templates.** Setup Commands now show `sudo ./felhom-host-install.sh - --customer-id ` (no-echo passphrase) instead of `docker-setup.sh`; Option 2 relabelled - "Manual config fetch (debug only)". Deleted orphaned `customer.html` + `config_detail.html`. +## Baseline + +hub `v0.26.0` @ `73b3f6a` (clean tree) → **`v0.27.0`**. Trunk-based `main`; commits +`2289fc9` (code) + `f8b8730` (manifest bump). ## Files -`internal/store/store.go` (column + bump + 3 SELECTs + struct), `internal/api/handler.go` (ACK), -`internal/web/server.go` (routes + handleTriggerUpdate removed), `internal/web/configs.go` -(handlers/helpers removed, geo trimmed), `internal/web/templates/customer_unified.html` (setup cmd, -buttons/JS removed), deleted `customer.html`+`config_detail.html`, `manifests/hub.yaml` (tag). -Tests: `internal/store/config_version_test.go`, `internal/api/config_version_ack_test.go`. -## Tests & green gate -- `go build ./... && go vet ./... && go test ./...` — **all green**. -- **Red-proof (version bump):** with the `config_version + 1` bump defeated, both - `TestSaveCustomerConfig_BumpsConfigVersion` and `TestReportACK_ConfigVersion` FAIL - (`config_version after edit = 1, want 2`) — the bump is load-bearing. Restored → green. -- **No-inbound:** `grep client.Do internal/web/` → only Gitea registry/template fetches; **zero** - ControllerURL targets (ControllerURL remains a display-only link). +**Created** +- `hub/internal/web/hosts.go` — `handleHostsList`, `handleHostDetail`, `hostStatus`/`hostStatusClass`/ + `hostStatusLabel`, `parseHostVitals`, `parseHostStorageTargets`, `customerName`. +- `hub/internal/web/hosts_test.go` — 5 tests (incl. the no-secret assertion). +- `hub/internal/web/templates/hosts.html`, `hub/internal/web/templates/host_detail.html`. -## Live validation (demo, hub.felhom.eu + guest 9201) -- Hub v0.26.0 rolled out on k3s (Synced/Healthy, `[INFO] felhom-hub … starting`). -- Live ACK for demo-felhom: `{"config_version":1,"latest_version":"0.94.0","min_controller_version":"0.87.0","status":"ok"}`. -- Bumped demo-felhom `config_version` 1→2 in the hub DB (equivalent to a config save; the SaveCustomerConfig - bump itself is unit-tested + red-proofed). The controller detected it, re-pulled, and self-restarted - (see controller REPORT for the box-side trace) — full pull-based delivery, no inbound connection. +**Modified** +- `hub/internal/store/store.go` — **new** `ListGuestsForHost(hostID) ([]Guest, error)` + `scanGuest` + helper over `guestRealitySelectCols` (reality columns only; **omits** `api_key`, `desired_spec_json`). +- `hub/internal/store/host_test.go` — `TestListGuestsForHost`. +- `hub/internal/web/server.go` — `GET /hosts` (+`/hosts/`) and `GET /hosts/{id}` routes (modelled on + the `/apps` pair); new `timeAgoPtr` template helper for `*time.Time`. +- 7 existing templates (`dashboard`, `configs`, `apps`, `config_form`, `configuration`, + `customer_unified`, `app_detail`) — added the `Hosts` nav link (nav is duplicated per page, not a + shared partial). +- `hub/CHANGELOG.md` (newest-on-top), `manifests/hub.yaml` (`:0.26.0` → `:0.27.0`). -## Open follow-ups (from the audit, not in scope here) -Hosts page (F-M1); controller-side geo intent sync (geo-disable is now CF-only, no box notify); -Show-Diff could return later as a read-only hub-vs-reported view. +## Design decisions + +- **Status badge reuses the alerting threshold.** `hostStatus()` reuses `s.staleThreshold` and the + `HostStalenessChecker`'s bands (stale after the threshold, down at 2×) — one definition, so the GUI + badge agrees with the emails. `nil` last-report → "waiting for first report". +- **No new ingestion path.** Vitals + rich storage (role/state/fill/thin-pool/SMART) are parsed from the + already-stored latest `report_json` (`GetLatestHostReportJSON`); worst-fill from `GetHostStorageTargets`. + Guest counts from `ListGuestsForHost` (per-host accurate). +- **No secrets.** The guests reader never selects `api_key`/`desired_spec_json`; DR/escrow are shown as + presence booleans only — never the opaque blobs. A test asserts the host `api_key` is absent from the + rendered detail body. +- **Nil-safe.** No report / no guests / no storage / no DR all render empty states, never a panic. + +## Tests — `go build ./... && go vet ./... && go test ./...` all `ok` + +- `TestListGuestsForHost` — none→empty; multiple→vmid-ordered; a foreign-host guest excluded; the + `api_key` column is not surfaced. **PASS** +- `TestHostStatus` — nil→pending, fresh→ok, 45m→stale, 3h→down. **PASS** +- `TestHandleHostsList` — 2 host rows, ONLINE + NO REPORT badges, worst-fill (73%) rendered, no `