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
This commit is contained in:
@@ -20,10 +20,28 @@
|
||||
|
||||
{{if .Flash}}
|
||||
<div class="flash flash-success">
|
||||
{{if eq .Flash "deleted"}}Customer configuration deleted.{{end}}
|
||||
{{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>
|
||||
@@ -43,6 +61,7 @@
|
||||
<th>Domain</th>
|
||||
<th>Status</th>
|
||||
<th>Version</th>
|
||||
<th>Floor</th>
|
||||
<th>Config</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -64,6 +83,10 @@
|
||||
{{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>
|
||||
|
||||
@@ -433,6 +433,30 @@
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<!-- Phase 2 managed-update floor (operator-enforced minimum) -->
|
||||
<div class="info-grid" style="margin-top: 0.75rem; border-top: 1px solid #334155; padding-top: 0.75rem;">
|
||||
<div class="info-item">
|
||||
<span class="label">Effective floor (min. version)</span>
|
||||
<span class="value">
|
||||
{{if .EffectiveFloor}}v{{.EffectiveFloor}}
|
||||
{{if .BelowFloor}}<span style="color: #f59e0b; margin-left: 0.3em;">● below floor — will auto-update</span>
|
||||
{{else}}<span style="color: #94a3b8; margin-left: 0.3em;">— at/above floor</span>{{end}}
|
||||
{{else}}<span style="color: #94a3b8;">none (Phase 2 inert)</span>{{end}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">Global floor</span>
|
||||
<span class="value">{{if .GlobalFloor}}v{{.GlobalFloor}}{{else}}<span style="color: #94a3b8;">unset</span>{{end}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<form method="POST" action="/customers/{{.CustomerID}}/floor" style="margin-top: 0.5rem; display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap;">
|
||||
{{.CSRFField}}
|
||||
<label style="font-size: 0.85em; color: #cbd5e1;">Per-customer override</label>
|
||||
<input type="text" name="min_controller_version" value="{{.FloorOverride}}" placeholder="e.g. 0.87.0 (blank = use global)" style="padding: 0.3em 0.5em; font-size: 0.85em; width: 14em;">
|
||||
<button class="btn btn-outline btn-sm" type="submit">Save floor</button>
|
||||
<span style="font-size: 0.8em; color: #94a3b8;">Boxes below the effective floor auto-update on their next report. Blank clears the override.</span>
|
||||
</form>
|
||||
|
||||
{{if and .HasConfig .ConfigSyncStatus}}
|
||||
<div class="info-item" style="margin-top: 0.5rem;">
|
||||
<span class="label">Config Sync</span>
|
||||
|
||||
Reference in New Issue
Block a user