Files
felhom.eu/hub/internal/web/templates/configs.html
T
admin 30380a59f4 hub v0.15.0: Phase 2 managed updates — per-customer controller-version floor
Operator sets a minimum controller version (FLOOR), per-customer defaulting to a
global floor; the report ACK returns the effective floor + latest_version so the
controller auto-updates to the floor when below it (latest stays the opt-in button).

- store: min_controller_version column + hub_settings global floor + Effective/
  Get/SetGlobal/SetMin resolution + config/env DEFAULT_MIN_CONTROLLER_VERSION
- handler: report ACK {min_controller_version, latest_version}; LatestVersionProvider
- web: global floor editor + per-customer override form + Floor column (English)
- tests: floor resolution + ACK + render; override-precedence red-proof verified

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

109 lines
5.3 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Felhom Hub — Customers</title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<div class="container">
<header>
<h1>Felhom Hub</h1>
<nav class="nav-links">
<a href="/" class="nav-link">Dashboard</a>
<a href="/configs" class="nav-link active">Customers</a>
<a href="/apps" class="nav-link">Apps</a>
<a href="/configuration" class="nav-link">Configuration</a>
</nav>
</header>
{{if .Flash}}
<div class="flash flash-success">
{{if eq .Flash "deleted"}}Customer configuration deleted.
{{else if eq .Flash "floor_set"}}Controller-version floor saved.
{{else if eq .Flash "floor_invalid"}}Invalid version — use X.Y.Z (or blank to clear).
{{end}}
</div>
{{end}}
<!-- Phase 2 managed updates: global controller-version floor -->
<div class="card" style="margin-bottom: 1rem; padding: 1rem; border: 1px solid #334155; border-radius: 8px;">
<h2 style="margin: 0 0 0.5rem;">Managed updates — global floor</h2>
<p style="font-size: 0.85em; color: #94a3b8; margin: 0 0 0.5rem;">
The minimum controller version every box auto-updates to (unless a per-customer override is set).
Boxes below the floor update on their next report — no customer action. Blank = no global floor.
</p>
<form method="POST" action="/configs/global-floor" style="display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap;">
{{.CSRFField}}
<input type="text" name="min_controller_version" value="{{.GlobalFloor}}" placeholder="e.g. 0.86.0 (blank = none)" style="padding: 0.3em 0.5em; width: 14em;">
<button class="btn btn-sm" type="submit">Save global floor</button>
<span style="font-size: 0.85em; color: #cbd5e1;">Current: {{if .GlobalFloor}}<code>v{{.GlobalFloor}}</code>{{else}}<span class="text-muted">unset</span>{{end}}</span>
</form>
</div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<h2 style="margin: 0;">Customers</h2>
<a href="/configs/new" class="btn">+ Add Customer</a>
</div>
{{if not .Customers}}
<div class="empty-state">
<p>No customers yet.</p>
<p class="hint">Add a customer configuration or wait for a controller to report in.</p>
</div>
{{else}}
<table class="dashboard-table">
<thead>
<tr>
<th>Customer ID</th>
<th>Name</th>
<th>Domain</th>
<th>Status</th>
<th>Version</th>
<th>Floor</th>
<th>Config</th>
</tr>
</thead>
<tbody>
{{range .Customers}}
<tr class="{{if .IsBlocked}}row-blocked{{end}}" onclick="window.location='/customers/{{.CustomerID}}'">
<td><code>{{.CustomerID}}</code></td>
<td>{{if .CustomerName}}{{.CustomerName}}{{else}}<span class="text-muted"></span>{{end}}</td>
<td>{{if .Domain}}{{.Domain}}{{else}}<span class="text-muted"></span>{{end}}</td>
<td>
{{if .IsBlocked}}
<span class="config-badge config-badge-blocked">BLOCKED</span>
{{else if .OverallStatus}}
<span class="status-badge status-badge-{{.OverallStatus}}">
{{if eq .OverallStatus "ok"}}OK{{else if eq .OverallStatus "warn"}}WARN{{else if eq .OverallStatus "down"}}DOWN{{else if eq .OverallStatus "disabled"}}PAUSED{{else if eq .OverallStatus "pending"}}PENDING{{else}}{{.OverallStatus}}{{end}}
</span>
{{else}}
<span class="text-muted"></span>
{{end}}
</td>
<td>{{if .ControllerVersion}}<code>{{.ControllerVersion}}</code>{{else}}<span class="text-muted"></span>{{end}}</td>
<td>
{{if .EffectiveFloor}}<code>v{{.EffectiveFloor}}</code>{{if .FloorOverride}}<span class="text-muted" style="font-size:0.8em;"> (override)</span>{{end}}{{if .BelowFloor}}<span style="color:#f59e0b;" title="below floor — will auto-update"></span>{{end}}
{{else}}<span class="text-muted"></span>{{end}}
</td>
<td>
{{if .HasConfig}}
<span class="config-badge config-badge-managed">MANAGED</span>
{{else}}
<span class="config-badge config-badge-manual">MANUAL</span>
{{end}}
</td>
</tr>
{{end}}
</tbody>
</table>
{{end}}
<footer>
<p>Felhom Hub {{hubVersion}} — Customer Management</p>
</footer>
</div>
</body>
</html>