54daaf949a
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
88 lines
5.2 KiB
Markdown
88 lines
5.2 KiB
Markdown
# 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 `<button>`. **PASS**
|
||
- `TestHandleHostDetail` — identity/guest/controller-version/storage/SMART(41°C)/role/customer-link
|
||
present; DR + escrow both "present"; **`api_key` absent (no-secrets)**; no `<button>`. **PASS**
|
||
- `TestHandleHostDetail_Unknown` — unknown host → 404. **PASS**
|
||
- `TestHandleHostDetail_NoReport` — renders "waiting for first report", no panic. **PASS**
|
||
|
||
## Deploy (GitOps via ArgoCD — the `felhom` app; auto-sync off)
|
||
|
||
Image `gitea.dooplex.hu/admin/felhom-hub:0.27.0` built+pushed on the build server (180); `manifests/hub.yaml`
|
||
bumped + committed; ArgoCD `felhom` hard-refreshed → OutOfSync → synced → **Synced / Healthy**;
|
||
`deploy/hub` rolled out on image `:0.27.0`; logs show `[INFO] Listening on :8080`.
|
||
|
||
## Live validation (claude-in-chrome, operator-authed, hub.felhom.eu)
|
||
|
||
- **`/hosts`** — lists `demo-felhom-01` **ONLINE** (agent `0.51.0`, guests 11/15, CPU 3% / MEM 21% /
|
||
DISK 29%, worst storage 29% `local`) + two drill hosts **NO REPORT**. Hosts nav active; footer `0.27.0`.
|
||
- **`/hosts/demo-felhom-01`** — Identity (customer **Demo Ügyfél** cross-linked to `/customers/…`, agent
|
||
`0.51.0`, enrolled 21d ago, last report 8 min ago, desired generation 2), Vitals, **Guests** (15 rows,
|
||
VMID/name/status running=green/stopped=red/last-seen), **Storage Targets** (felhom-usb **SMART PASSED
|
||
33°C**; local-lvm lvmthin 11% fill / 11% thin-pool; others UNKNOWN/graceful "—"), **DR / Backup** DR
|
||
Recipe + Key Escrow both **present**. Confirmed: **no action buttons**, and the page text carries **no
|
||
`api_key`/secret**.
|
||
|
||
## Remaining audit follow-ups (not this slice)
|
||
|
||
Controller-side geo intent sync; a read-only reported-vs-desired "Show Diff"; the cosmetic `controllerURL`
|
||
cleanup in `configs.go`.
|