diff --git a/hub/CHANGELOG.md b/hub/CHANGELOG.md index f92ad1e..dd071e2 100644 --- a/hub/CHANGELOG.md +++ b/hub/CHANGELOG.md @@ -1,5 +1,44 @@ # Felhom Hub — Changelog +## v0.27.0 — Hosts page: read-only fleet view (audit F-M1) (2026-07-01) + +Resolves audit finding **F-M1**: 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 whole host domain was **invisible in the GUI** (email-only). 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 way the pull/desired-state model demands; it does not reintroduce inbound control +(retired in v0.26.0). + +- **New store reader `ListGuestsForHost(hostID)` (`internal/store/store.go`).** `SELECT … FROM guests + WHERE host_id = ? ORDER BY vmid`, via a new `scanGuest` helper over `guestRealitySelectCols` — the + reality columns only. It deliberately **omits the secret/inert columns** (`api_key`, + `desired_spec_json`), so the read-only view can never surface them. Returns `[]` (never nil-error) on + no guests. (There was previously no guests *reader* — only `UpsertGuestFromReport`.) +- **New handlers (`internal/web/hosts.go`).** + - `handleHostsList` — `ListHosts` + a per-host status badge from `hostStatus()` (which reuses + `s.staleThreshold` — the **same** thresholds as the `HostStalenessChecker`: stale after the + threshold, down at 2× — so the badge agrees with the alerting) + per-host guest counts + (`ListGuestsForHost`) + vitals parsed from `GetLatestHostReportJSON` + worst storage fill grouped + from `GetHostStorageTargets`. + - `handleHostDetail` — `GetHost` (404 if absent) + `ListGuestsForHost` + rich storage targets (role, + state, fill %, thin-pool, SMART health/temp/wear parsed from the latest report body) + vitals + + `GetHostDRBundle`/`GetHostEscrow` **presence booleans only** (never the opaque blobs). Nil/missing + (no report, no guests, no storage, no DR) render empty states — never a panic. +- **New templates `hosts.html` + `host_detail.html`** (existing dark operator-console styling reused — + `data-table`, `status-badge-*`, `info-grid`, `empty-state`; no restyle). A no-report host shows a + STALE/NO-REPORT badge and "waiting for first report". +- **Nav:** added the `Hosts` link (between Apps and Configuration) to every page's ` diff --git a/hub/internal/web/templates/apps.html b/hub/internal/web/templates/apps.html index 2b1c2f4..a014444 100644 --- a/hub/internal/web/templates/apps.html +++ b/hub/internal/web/templates/apps.html @@ -14,6 +14,7 @@ Dashboard Customers Apps + Hosts Configuration diff --git a/hub/internal/web/templates/config_form.html b/hub/internal/web/templates/config_form.html index 1b723ce..5a698b4 100644 --- a/hub/internal/web/templates/config_form.html +++ b/hub/internal/web/templates/config_form.html @@ -14,6 +14,7 @@ Dashboard Customers Apps + Hosts Configuration diff --git a/hub/internal/web/templates/configs.html b/hub/internal/web/templates/configs.html index 344c1b6..b611b9d 100644 --- a/hub/internal/web/templates/configs.html +++ b/hub/internal/web/templates/configs.html @@ -14,6 +14,7 @@ Dashboard Customers Apps + Hosts Configuration diff --git a/hub/internal/web/templates/configuration.html b/hub/internal/web/templates/configuration.html index 5b3b7f1..b5354b5 100644 --- a/hub/internal/web/templates/configuration.html +++ b/hub/internal/web/templates/configuration.html @@ -14,6 +14,7 @@ Dashboard Customers Apps + Hosts Configuration diff --git a/hub/internal/web/templates/customer_unified.html b/hub/internal/web/templates/customer_unified.html index dd7afa8..1bddd4c 100644 --- a/hub/internal/web/templates/customer_unified.html +++ b/hub/internal/web/templates/customer_unified.html @@ -15,6 +15,7 @@ Dashboard Customers Apps + Hosts Configuration ← All Customers diff --git a/hub/internal/web/templates/dashboard.html b/hub/internal/web/templates/dashboard.html index e2efbfd..8064cc2 100644 --- a/hub/internal/web/templates/dashboard.html +++ b/hub/internal/web/templates/dashboard.html @@ -15,6 +15,7 @@ Dashboard Customers Apps + Hosts Configuration diff --git a/hub/internal/web/templates/host_detail.html b/hub/internal/web/templates/host_detail.html new file mode 100644 index 0000000..a454b74 --- /dev/null +++ b/hub/internal/web/templates/host_detail.html @@ -0,0 +1,194 @@ + + + + + + {{.HostID}} — Felhom Hub + + + +
+
+

Felhom Hub

+ +
+ + ← Hosts + + +
+
+

{{.HostID}}

+ {{.StatusLabel}} +
+
+
+ Host ID + {{.HostID}} +
+ +
+ Agent Version + {{if .AgentVersion}}{{.AgentVersion}}{{else}}—{{end}} +
+
+ Enrolled + {{timeAgo .CreatedAt}} +
+
+ Last Report + {{if .HasReport}}{{timeAgoPtr .LastReportAt}}{{else}}waiting for first report{{end}} +
+
+ Desired Generation + {{.DesiredGeneration}} +
+ {{if .RecoveryMode}} +
+ Recovery Mode + ACTIVE (until {{timeAgoPtr .RecoveryUntil}}) +
+ {{end}} +
+
+ + {{if .HasReport}} + +
+

Vitals

+
+
+ CPU + {{formatFloat .Vitals.CPUPercent}}% +
+
+ Memory + {{formatFloat .Vitals.MemoryPercent}}% +
+
+ Disk (root fs) + {{formatFloat .Vitals.DiskPercent}}% +
+
+ Cloudflared + {{if .Vitals.CloudflaredStatus}}{{.Vitals.CloudflaredStatus}}{{else}}—{{end}} +
+
+ Guests + {{.GuestRunning}}/{{.GuestTotal}} running +
+
+
+ {{else}} +
+
+

Waiting for first report.

+

Vitals, guests and storage appear once this host's agent sends a host-report.

+
+
+ {{end}} + + +
+

Guests

+ {{if .Guests}} + + + + + + + + + + + + {{range .Guests}} + + + + + + + + {{end}} + +
VMIDNameStatusControllerLast Seen
{{.VMID}}{{if .DisplayName}}{{.DisplayName}}{{else}}—{{end}}{{if eq .Status "running"}}{{.Status}}{{else if eq .Status "stopped"}}{{.Status}}{{else}}{{.Status}}{{end}}{{if .ControllerVersion}}{{.ControllerVersion}}{{else}}—{{end}}{{timeAgoPtr .LastSeenAt}}
+ {{else}} +
+

No guests reported on this host.

+
+ {{end}} +
+ + +
+

Storage Targets

+ {{if .StorageTargets}} + + + + + + + + + + + + + + + + {{range .StorageTargets}} + + + + + + + + + + + + {{end}} + +
NameRoleTypeStateFillThin PoolSMARTTempWear
{{.Name}}{{if .Role}}{{.Role}}{{else}}—{{end}}{{if .Type}}{{.Type}}{{else}}—{{end}}{{if .State}}{{.State}}{{else}}—{{end}}{{formatFloat .FillPct}}%{{if .HasThin}}{{formatFloat .ThinDataPct}}%{{else}}—{{end}}{{if .SmartHealth}}{{if eq .SmartHealth "PASSED"}}{{.SmartHealth}}{{else if eq .SmartHealth "FAILED"}}{{.SmartHealth}}{{else}}{{.SmartHealth}}{{end}}{{else}}—{{end}}{{if .TempC}}{{.TempC}}°C{{else}}—{{end}}{{if .WearPct}}{{.WearPct}}%{{else}}—{{end}}
+ {{else}} +
+

No storage targets reported.

+
+ {{end}} +
+ + +
+

DR / Backup

+
+
+ DR Recipe + {{if .DRPresent}}present{{else}}none{{end}} +
+
+ Key Escrow + {{if .EscrowPresent}}present{{else}}none{{end}} +
+
+
+ + +
+ + diff --git a/hub/internal/web/templates/hosts.html b/hub/internal/web/templates/hosts.html new file mode 100644 index 0000000..ffbd553 --- /dev/null +++ b/hub/internal/web/templates/hosts.html @@ -0,0 +1,71 @@ + + + + + + Hosts — Felhom Hub + + + +
+
+

Felhom Hub

+ +
+ +

Hosts

+ + {{if .Hosts}} +
+ + + + + + + + + + + + + + + + + {{range .Hosts}} + + + + + + + + + + + + + {{end}} + +
HostCustomerAgentStatusGuestsCPUMemDiskCloudflaredWorst Storage
{{.HostID}}{{if .CustomerName}}{{.CustomerName}}{{else}}{{.CustomerID}}{{end}}{{if .AgentVersion}}{{.AgentVersion}}{{else}}—{{end}}{{.StatusLabel}}{{if .HasReport}}{{.GuestRunning}}/{{.GuestTotal}}{{else}}—{{end}}{{if .HasReport}}{{formatFloat .Vitals.CPUPercent}}%{{else}}—{{end}}{{if .HasReport}}{{formatFloat .Vitals.MemoryPercent}}%{{else}}—{{end}}{{if .HasReport}}{{formatFloat .Vitals.DiskPercent}}%{{else}}—{{end}}{{if .Vitals.CloudflaredStatus}}{{.Vitals.CloudflaredStatus}}{{else}}—{{end}}{{if .HasStorage}}{{formatFloat .WorstFillPct}}% {{.WorstFillName}}{{else}}—{{end}}
+
+ {{else}} +
+

No hosts enrolled yet.

+

A host appears here after its agent enrolls and sends its first host-report.

+
+ {{end}} + + +
+ +