# felhom.eu — task reports > **Overwrite** this file with a summary of the most recent task only (uniform with the other repos; not cumulative). The cumulative hub history lives in [hub/CHANGELOG.md](hub/CHANGELOG.md). --- # REPORT — Hosts page: read-only fleet view (hub v0.27.0) **Date:** 2026-07-01 · **Repo:** `felhom.eu` (`hub/`) · **Audit finding:** F-M1 (resolved) ## What & why 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. ## Baseline hub `v0.26.0` @ `73b3f6a` (clean tree) → **`v0.27.0`**. Trunk-based `main`; commits `2289fc9` (code) + `f8b8730` (manifest bump). ## Files **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`. **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`). ## 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 `