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
14 KiB
AUDIT — Hub web GUI vs. current architecture
- Date: 2026-06-30
- Repo / baseline:
felhom.euhub/,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-liveis 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
- F-S1 (+F-D2) — the
docker-setup.shsetup command. It actively misleads anyone deploying and is the clearest "fix-now". Cheap, high-impact. Removing/relabelling the deadconfig_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-filefor automation). This enrolls the host, installs+verifies the agent, and provisions the guest; the in-guest controller then pulls its owncontroller.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.
- Option 1 — Day-0 host bootstrap (recommended). On a freshly-PVE-installed Proxmox host, as
root:
- 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.
- 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.
- 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 distinct — configs.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 |
Present — configs.html:34-46 |
None. ✔ |
| Version floor — per-customer | handleSetCustomerFloor (configs.go:697) |
Present — customer_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
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.- 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.mdforbids. 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. - Host visibility (F-M1): is an operator-facing Hosts page wanted now, or is host health deliberately email-alert-only for the moment?
- 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.htmlCredentials 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.