Files
felhom.eu/documentation/audits/AUDIT-hub-gui-2026-06-30.md
T
admin e51e03bd7b docs(audit): hub GUI vs current architecture findings (2026-06-30)
Categorized STALE/DEAD/MISSING/POLISH/CORRECTNESS findings for the hub web
GUI. No GUI code changed, no version bump — findings doc only, for operator
prioritization.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HxLA1mZurFq9kt8hneFeCs
2026-06-30 20:13:48 +02:00

14 KiB

AUDIT — Hub web GUI vs. current architecture

  • Date: 2026-06-30
  • Repo / baseline: felhom.eu hub/, main @ fdcbc04 — hub v0.25.0 (CHANGELOG top).

    The audit prompt cited v0.16.0; that is stale. Live build is v0.25.0. hub/CLAUDE.md "current state (v0.7.x)" is also stale — see F-S6.

  • Method: read-only source trace of every route in internal/web/server.go + its handler + its template, cross-referenced against the live API (internal/api/handler.go), the store (internal/store/), the host-install script (scripts/felhom-host-install.sh), the controller report builder (felhom-controller/.../report/builder.go), and the architecture docs (documentation/architecture/01-topology-and-trust.md).
  • Live-confirmed? All findings below were confirmed by source read, not by loading the rendered page. confirmed-live is therefore n for every row; the one item that would most benefit from a live check is F-C1 (storage %).
  • Scope guard: this pass ships only this doc. No GUI/template/handler code was changed and no version bumped, per the task's STOP rule.

0. Route → template → handler map (the served surface)

Routes that render an HTML page (from server.go ServeHTTP + the ExecuteTemplate calls):

Nav URL Handler Template
Dashboard / handleDashboard (server.go:444) dashboard.html
Customers /configs handleConfigList (configs.go:59) configs.html
Customers /customers/{id} handleCustomerUnified (configs.go:165) customer_unified.html
Customers /configs/new, /configs/{id}/edit handleConfigNewForm / handleConfigEditForm config_form.html
Apps /apps handleApps (apps.go) apps.html
Apps /apps/{name} handleAppDetail (apps.go) app_detail.html
Configuration /configuration handleConfiguration (server.go:664) configuration.html

/configs/{id} (any non-suffixed) 302-redirects to /customers/{id} (server.go:305-308). No {{template "..."}} partial-includes exist (grep clean), so a template is reachable only if a handler names it in ExecuteTemplate.

Two templates are named by no handler: customer.html, config_detail.html → see F-D1/F-D2.


1. Findings

ID Cat Location (file:line / page) What's wrong Recommendation Effort
F-S1 STALE customer_unified.html:386-390 (LIVE — Customer detail → "Setup Commands" → Option 1) Advertises sudo ./docker-setup.sh --hub-customer … --hub-password … — the pre-Proxmox bare-Docker install. The current Day-0 is the Proxmox host-bootstrap. Anyone following this is actively misled. Replace Option 1 with the host-install command (see §2 "proposed copy"). Decide remove vs. keep-as-legacy (Open-Q1). S
F-S2 STALE customer_unified.html:392-396 (LIVE — Option 2 "Direct download") curl …/api/v1/config/{id} -H "X-Retrieval-Password" -o controller.yaml hits a still-valid endpoint, but the workflow is wrong for Proxmox: the operator no longer hand-places controller.yaml; the in-guest controller pulls it itself (bootstrap-v2 config-retrieve) after the agent provisions the guest. Label/context is stale, endpoint is not. Re-label as "Manual config fetch (debug only)" or fold into the host-install flow. Keep the endpoint. S
F-S3 STALE customer_unified.html:347 (LIVE — "Waiting for First Report") "Use one of the setup commands below to deploy the controller on the customer node." — reinforces the SSH-into-a-customer-node mental model that Proxmox replaced. Reword to point at the host-install Day-0 procedure. S
F-S4 STALE / ARCH-DRIFT customer_unified.html buttons: Trigger Update (475/479), Push Config (484), Pull Config (487), Show Diff (466), Geo-disable (297); handlers handleTriggerUpdate (server.go:525), handlePushConfig/handlePullConfig/handleConfigDiff (configs.go:722/1089/999), notifyControllerGeoDisable (configs.go:1286) All five make the hub connect into the box (controllerURL + /api/...). The locked trust model says the opposite: 01-topology-and-trust.md:11 "the hub never connects into a customer box"; :79 "the hub never initiates inbound." These are pre-Proxmox data-plane controls. Under the Proxmox model controller updates are agent-driven (version floor / MaybeAutoUpdate) and config is box-pulled (config-retrieve), so these buttons are at best a trust-model violation and at worst dead (the box is only reachable via its CF tunnel public URL, not the hub). Decide per Open-Q2: retire these controls, or formally bless a documented exception. Until then flag them as legacy. High-value — this is the largest correctness/architecture gap. M
F-S5 STALE dashboard.html:25, customer_unified.html:311, customer.html:141 Empty/disabled hints say "Configure hub.enabled: true in customer controller.yaml". Still literally true for the controller, but the onboarding path is now host-install (which seeds it). Low harm. Light reword once F-S1 lands; otherwise leave. S
F-S6 STALE hub/CLAUDE.md:24 ("current state (v0.7.x)"), tables at :26-29 Project-instructions file describes a v0.7.x hub; live is v0.25.0 (host-disk/storage-fill alerting, artifact manifest, version floor, DR recipe all post-date it). Misleads the next contributor. Refresh the "current state" section. (Not GUI, but surfaced by the audit.) S
F-D1 DEAD templates/customer.html (entire file, 324 lines) The old, non-unified customer detail page. Superseded by customer_unified.html; named by no handler; not included anywhere. Also missing CSRF on its Trigger-Update fetch (line 223) and lacking floor / DR-recipe / app-telemetry sections — proof it's a stale fork. Delete (after Open-Q confirms no out-of-tree reference). S
F-D2 DEAD templates/config_detail.html (entire file, 156 lines) The old standalone config-detail page. /configs/{id} now redirects to /customers/{id} (server.go:305-308); named by no handler. Carries the same stale docker-setup.sh block (lines 107-117) as F-S1, so leaving it invites re-introduction. Delete. S
F-M1 MISSING No route in server.go; store has ListHosts/GetHost/GetHostStaleness/SaveHostReport + hosts/guests/host_reports tables There is no host view at all. The agent enrolls as a host and the hub runs HostStalenessChecker + HostDiskChecker + StorageFillChecker over host-reports (→ email alerts), but the operator cannot see any host, its guests, its storage targets, enrollment status, or host-down state in the UI. The whole host domain is invisible except via email. Add a Hosts page (list hosts + status + guests + storage-target fill). Biggest missing surface. L
F-M2 MISSING customer_unified.html "Controller Update" section (408-496) No surfacing of agentic update state / last result. The page shows current version, registry-latest, floor, and a "below floor — will auto-update" hint, but never "last update: 0.86.0→0.87.0 (auto-floor) at …" or success/failure. (The prompt's referenced "Utolsó frissítés" line does not exist in the code — grep clean.) Add a last-update-result row sourced from report/event data. M
F-M3 MISSING (minor) Host-domain ops APIs (escrow, desired-state, signed jobs, recovery-mode, re-enroll, restore-directive — see CHANGELOG) Rich operator APIs exist with zero GUI; all are global-key/CLI-only today. Likely intentional (operator CLI), but worth an explicit "GUI not planned" note so it isn't mistaken for a regression. Document as CLI-only, or add read-only visibility under the F-M1 Hosts page. M
F-C1 CORRECTNESS customer_unified.html:166-177 (Storage); source report/builder.go:93,122 The template binds index . "percent" / used_gb / total_gb. Field names are correct — the controller emits exactly percent/used_gb/total_gb (StorageReport{… Percent: sysInfo.DiskPercent}). So a "0% bar with non-zero sizes" is a data/source issue (sysInfo.DiskPercent computed 0, or a genuinely near-empty fresh drive), not a template-binding bug. No hub change. If 0% recurs on a non-empty mount, chase it controller-side (system/info_linux.go readDiskUsage). Verify live to close. S
F-P1 POLISH customer_unified.html (whole page, ~956 lines) One very long single-column scroll: Info → System → Storage → Containers → Backup → Geo → Health → Credentials → Setup → YAML → Controller Update → Events → App Telemetry → DR → Notifications → History. Dense for an operator hunting one fact. Optional: group into collapsible <details> or tabs (Overview / Config & Setup / Updates / Telemetry / DR). Suggestion, not a mandate. M
F-P2 POLISH HU/EN mix on customer_unified.html Page chrome is English, but the Geo-restriction section (239-301) and its confirm dialogs (707-735) and the config_form.html:80 hint are Hungarian. Operator UI is otherwise English. Pick one language for the operator console (English) and translate the Geo block, or accept the mix deliberately. S
F-P3 POLISH customer_unified.html:444 Floor unset renders literal "none (Phase 2 inert)" to the operator — internal phrasing ("Phase 2 inert") leaks implementation jargon into the UI. Reword to "none (no floor set)". S

2. Prioritized shortlist — fix first

  1. F-S1 (+F-D2) — the docker-setup.sh setup command. It actively misleads anyone deploying and is the clearest "fix-now". Cheap, high-impact. Removing/relabelling the dead config_detail.html (F-D2) in the same pass stops the stale block from creeping back. Proposed copy for the live "Setup Commands" card:
    • Option 1 — Day-0 host bootstrap (recommended). On a freshly-PVE-installed Proxmox host, as root: sudo ./felhom-host-install.sh --customer-id {{.CustomerID}} (the retrieval passphrase is entered at the no-echo prompt — never on the command line; --passphrase-file for automation). This enrolls the host, installs+verifies the agent, and provisions the guest; the in-guest controller then pulls its own controller.yaml.
    • Option 2 — Manual config fetch (debug only). curl -fsSL https://hub.felhom.eu/api/v1/config/{{.CustomerID}} -H "X-Retrieval-Password: …" -o controller.yaml — the same payload the controller pulls itself; for inspection, not normal provisioning.
  2. F-S4 — the inbound data-plane buttons. Largest architecture gap; decide retire-vs-bless (Open-Q2) before anyone relies on a button that the trust model forbids.
  3. F-M1 — Hosts page. The single biggest missing surface; host health is invisible in the UI. Larger effort, so plan it deliberately rather than rushing.
  4. F-D1 — delete customer.html. Zero-risk cleanup once references are confirmed.

3. Dead-code list (orphaned, safe to remove — flag, don't delete in this pass)

File Evidence it's dead
templates/customer.html Not named by any ExecuteTemplate (grep: only dashboard/customer_unified/config_form/configs/apps/app_detail/configuration/dr are). No {{template}} include. Superseded by customer_unified.html.
templates/config_detail.html Same — no handler names it. /configs/{id} redirects to /customers/{id} (server.go:305-308).

configs.html / configuration.html / config_form.html were checked for the overlap the prompt flagged: all three are live and distinctconfigs.html = Customers list (+global floor +artifact manifest forms), configuration.html = the "Configuration" nav page (asset re-seed), config_form.html = the new/edit customer form. No dead code among them.


4. Missing-controls list (shipped backend, no/weak GUI)

Capability Backend GUI today Gap
Artifact manifest (agent+golden ver+sha256) handleSetArtifacts (configs.go:664) Present — form on configs.html:52-71 None. ✔
Version floor — global SetGlobalMinControllerVersion Presentconfigs.html:34-46 None. ✔
Version floor — per-customer handleSetCustomerFloor (configs.go:697) Presentcustomer_unified.html:452-458 None. ✔
DR recipe (secret-free) dr_recipe.go, GetDRRecipe Present — section + JSON download, customer_unified.html:586-613 None; reflects the current secret-free model. ✔
Host enrollment / host identity / host status hosts/guests/host_reports, ListHosts, host stale/disk/fill checkers None F-M1 — no host view exists.
Agentic update state / last result floor auto-update (MaybeAutoUpdate), report data Hint only ("will auto-update") F-M2 — no last-result surfacing.
Host ops (escrow / desired-state / jobs / recovery / re-enroll) many */hosts/... APIs None (CLI/global-key) F-M3 — likely intentional; document.

5. Open questions for the operator

  1. docker-setup.sh (F-S1): remove the Option-1 block entirely, or keep it labelled "legacy (pre-Proxmox) — do not use"? Does any current customer still run the bare-Docker model? If none, remove.
  2. Inbound data-plane buttons (F-S4): Trigger Update / Push Config / Pull Config / Show Diff / Geo-disable all make the hub connect into the box, which 01-topology-and-trust.md forbids. Are these (a) to be retired in favour of agent-driven update + box-pulled config, (b) kept as a blessed, documented exception (and do they actually work in prod via the CF tunnel public URL?), or (c) already known-dead? This drives whether F-S4 is a delete or a doc change.
  3. Host visibility (F-M1): is an operator-facing Hosts page wanted now, or is host health deliberately email-alert-only for the moment?
  4. Operator-console language (F-P2): standardize the whole console on English (translate the Geo block), or keep the customer-facing-Hungarian / operator-English split inside this page?

6. Notes

  • No secrets were copied into this doc. The live page renders retrieval passwords / API keys (customer_unified.html Credentials card); those are referenced by location only, never reproduced.
  • Nothing here was changed in code. Implementing any of the above is a follow-up spec the operator prioritizes from §2.