hub v0.45.0: floor-UI separation + effective-floor source + per-box MinAgent conditional floor
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -48,20 +48,77 @@
|
||||
<div class="flash flash-error">Couldn't set the checksum — the Gitea sha lookup failed (version missing / Gitea unreachable) or the manually-entered sha is invalid. Manifest unchanged.</div>
|
||||
{{end}}
|
||||
|
||||
<!-- Phase 2 managed updates: global controller-version floor (moved from the Customers page —
|
||||
it is a global setting). -->
|
||||
<!-- Phase 2 managed updates: global controller-version floor. ITS OWN card, separate from the
|
||||
Day-0 artifact manifest below (a manifest save must NEVER touch the live floor — the
|
||||
publish-train 0.81/0.113 incident). Saving acts IMMEDIATELY, so it is behind a
|
||||
type-to-confirm dialog that states the live below-floor blast radius first. -->
|
||||
<section class="card">
|
||||
<h3 style="margin-top: 0;">Managed updates — global floor</h3>
|
||||
<p class="text-muted" style="margin: 0 0 0.75rem; font-size: 0.85em;">
|
||||
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.
|
||||
<strong>Saving takes effect immediately</strong> — boxes below the floor update on their next
|
||||
report, no customer action. Blank = no global floor. This setting is independent of the Day-0
|
||||
artifact manifest below.
|
||||
</p>
|
||||
<form method="POST" action="/configuration/global-floor" style="display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap;">
|
||||
<!-- Effective-floor-and-source line: makes the DB-override-vs-env-fallback precedence
|
||||
permanently visible (the 9-minute-skew incident's root cause). -->
|
||||
<p style="margin: 0 0 0.75rem; font-size: 0.85em;">
|
||||
Effective floor:
|
||||
{{if .FloorRes.Effective}}<code>v{{.FloorRes.Effective}}</code>{{else}}<span class="text-muted">none</span>{{end}}
|
||||
{{if eq .FloorRes.Source "db"}}
|
||||
<span style="color: #cbd5e1;">— source: <strong>DB (hub_settings)</strong>{{if .FloorRes.EnvValue}}; env fallback would be <code>v{{.FloorRes.EnvValue}}</code>{{end}}</span>
|
||||
{{else if eq .FloorRes.Source "env"}}
|
||||
<span style="color: #cbd5e1;">— source: <strong>env fallback (DEFAULT_MIN_CONTROLLER_VERSION)</strong>; no DB override set</span>
|
||||
{{else}}
|
||||
<span class="text-muted">— no floor from either source</span>
|
||||
{{end}}
|
||||
</p>
|
||||
<form id="global-floor-form" method="POST" action="/configuration/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>
|
||||
<input type="text" id="global-floor-input" name="min_controller_version" value="{{.FloorRes.DBValue}}" placeholder="e.g. 0.86.0 (blank = clear DB override)" style="padding: 0.3em 0.5em; width: 16em;">
|
||||
<button class="btn btn-sm" type="button" onclick="confirmGlobalFloor()">Save global floor…</button>
|
||||
<span style="font-size: 0.85em; color: #cbd5e1;">DB override: {{if .FloorRes.DBValue}}<code>v{{.FloorRes.DBValue}}</code>{{else}}<span class="text-muted">none</span>{{end}}</span>
|
||||
</form>
|
||||
<div id="global-floor-confirm" style="display: none; margin-top: 0.75rem; padding: 0.75rem; border: 1px solid #7c3f00; background: #241a0a; border-radius: 6px; max-width: 44em;">
|
||||
<p id="global-floor-impact" style="margin: 0 0 0.5rem; font-size: 0.9em;">…</p>
|
||||
<p style="margin: 0 0 0.5rem; font-size: 0.85em; color: #cbd5e1;">Type the version again to confirm (or <code>CLEAR</code> to remove the DB override):</p>
|
||||
<input type="text" id="global-floor-confirm-input" placeholder="retype the version…" style="padding: 0.3em 0.5em; width: 16em;">
|
||||
<button class="btn btn-sm" type="button" onclick="submitGlobalFloor()">Confirm & apply</button>
|
||||
<button class="btn btn-sm btn-ghost" type="button" onclick="document.getElementById('global-floor-confirm').style.display='none';">Cancel</button>
|
||||
<p id="global-floor-confirm-err" style="margin: 0.4em 0 0; font-size: 0.8em; color: #f87171;"></p>
|
||||
</div>
|
||||
<script>
|
||||
function confirmGlobalFloor() {
|
||||
var v = document.getElementById('global-floor-input').value.trim();
|
||||
var box = document.getElementById('global-floor-confirm');
|
||||
var impact = document.getElementById('global-floor-impact');
|
||||
document.getElementById('global-floor-confirm-input').value = '';
|
||||
document.getElementById('global-floor-confirm-err').textContent = '';
|
||||
box.style.display = 'block';
|
||||
if (v === '') {
|
||||
impact.textContent = 'This will CLEAR the DB floor override (the box falls back to the env default). Type CLEAR to confirm.';
|
||||
return;
|
||||
}
|
||||
impact.textContent = 'Checking blast radius…';
|
||||
fetch('/configuration/global-floor/impact?v=' + encodeURIComponent(v))
|
||||
.then(function(r){ return r.json(); })
|
||||
.then(function(d){
|
||||
if (!d.valid) { impact.textContent = 'Invalid version — use X.Y.Z.'; return; }
|
||||
impact.textContent = 'Saving the minimum version v' + d.version +
|
||||
' takes effect immediately — currently ' + d.below +
|
||||
' box(es) are below this version and would update on their next report.';
|
||||
})
|
||||
.catch(function(){ impact.textContent = 'Could not compute the blast radius; proceed with caution.'; });
|
||||
}
|
||||
function submitGlobalFloor() {
|
||||
var v = document.getElementById('global-floor-input').value.trim();
|
||||
var typed = document.getElementById('global-floor-confirm-input').value.trim();
|
||||
var err = document.getElementById('global-floor-confirm-err');
|
||||
var expected = (v === '') ? 'CLEAR' : v;
|
||||
if (typed !== expected) { err.textContent = 'Confirmation does not match (' + expected + ').'; return; }
|
||||
document.getElementById('global-floor-form').submit();
|
||||
}
|
||||
</script>
|
||||
</section>
|
||||
|
||||
<!-- BUNDLE slice: Day-0 artifact manifest (agent binary + golden archive). The hub is the
|
||||
@@ -102,6 +159,9 @@
|
||||
<input type="text" name="golden_version" value="{{.Artifacts.GoldenVersion}}" placeholder="0.85.1" style="padding: 0.3em 0.5em;">
|
||||
{{end}}
|
||||
<input type="text" name="golden_sha256" id="golden_sha256" value="{{.Artifacts.GoldenSHA256}}" {{if .GoldenChoices}}readonly{{end}} placeholder="64-hex sha256 (blank = none)" style="padding: 0.3em 0.5em; font-family: monospace; {{if .GoldenChoices}}opacity: 0.7;{{end}}">
|
||||
<label style="font-size: 0.9em; color: #cbd5e1;">Min agent</label>
|
||||
<input type="text" name="min_agent" value="{{.Artifacts.MinAgent}}" placeholder="e.g. 0.81.0 (blank = uncoupled)" style="padding: 0.3em 0.5em;">
|
||||
<span style="font-size: 0.8em; color: #94a6bf;">The golden's controller CHANGELOG <code>MinAgent:</code>. The hub HOLDS the floor for any box whose agent is below this — blank = uncoupled release, no gating.</span>
|
||||
<span></span><span></span>
|
||||
<button class="btn btn-sm" type="submit" style="justify-self: start;">Save artifact manifest</button>
|
||||
</form>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<tr onclick="window.location='/hosts/{{.HostID}}'" style="cursor: pointer;">
|
||||
<td><a href="/hosts/{{.HostID}}">{{.HostID}}</a></td>
|
||||
<td>{{if .CustomerName}}{{.CustomerName}}{{else}}{{.CustomerID}}{{end}}</td>
|
||||
<td>{{if .AgentVersion}}<code>{{.AgentVersion}}</code>{{else}}—{{end}}</td>
|
||||
<td>{{if .AgentVersion}}<code>{{.AgentVersion}}</code>{{else}}—{{end}}{{if .FloorHeld}} <span class="status-badge status-warn" title="{{.HeldReason}}">floor held</span>{{end}}</td>
|
||||
<td><span class="status-badge {{.StatusClass}}">{{.StatusLabel}}</span></td>
|
||||
<td>{{if .HasReport}}{{.GuestRunning}}/{{.GuestTotal}}{{else}}—{{end}}</td>
|
||||
<td>{{if .HasReport}}{{formatFloat .Vitals.CPUPercent}}%{{else}}—{{end}}</td>
|
||||
|
||||
Reference in New Issue
Block a user