hub (unreleased): the System page answers 'is anything wrong?' and 'is anything waiting for me?' first
gates / gates (push) Successful in 6m17s
gates / gates (push) Successful in 6m17s
Needs attention, Waiting for you (one card per kernel/Docker/Proxmox set the button may approve), a 7-column Boxes table whose rows open to every old value, and a closed Details (release ids, cancelled approvals, ring-0 counts, floors, crash guard and root files, Approve now - which now asks first). Same data, buttons, routes and CSRF field. No deploy. Screenshots in audits/hub-system-page-2026-10-10/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
This commit is contained in:
@@ -6,12 +6,65 @@
|
||||
<title>System — Felhom Hub</title>
|
||||
<link rel="stylesheet" href="/style.css?v={{hubVersion}}">
|
||||
<style>
|
||||
.sys td, .sys th { white-space: nowrap; font-size: 0.82em; vertical-align: top; }
|
||||
.sys .grp { border-left: 2px solid var(--border, #444); }
|
||||
.c-warn { color: var(--warn); font-weight: 600; }
|
||||
.c-bad { color: var(--danger, #e5534b); font-weight: 700; }
|
||||
.c-bad { color: var(--crit); font-weight: 700; }
|
||||
.sys-sec { margin-bottom: 1.5rem; }
|
||||
.sys-sec > h3 { margin: 0 0 0.15rem; font-size: 1.05rem; color: var(--text-1); }
|
||||
.att a, .bx-row a, .bx-more a { color: var(--blue-bright); text-decoration: none; }
|
||||
.sys-sec > .why { margin: 0 0 0.8rem; color: var(--text-2); font-size: 0.85em; }
|
||||
.term { text-decoration: underline dotted; cursor: help; }
|
||||
.sys form { display: inline; }
|
||||
.rel-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); gap: 0.75rem; }
|
||||
/* Needs attention */
|
||||
.att { list-style: none; margin: 0; padding: 0; }
|
||||
.att li { padding: 0.45rem 0; border-top: 1px solid var(--line); }
|
||||
.att li:first-child { border-top: 0; }
|
||||
.att .rs { display: block; margin: 0.15rem 0 0 1.4rem; font-size: 0.88em; }
|
||||
.att .rs span { font-weight: normal; }
|
||||
.mark { display: inline-block; min-width: 3.4rem; font-size: 0.8em; text-transform: uppercase; letter-spacing: 0.03em; }
|
||||
.mark::before { content: "● "; }
|
||||
.mark.c-warn::before { content: "▲ "; }
|
||||
.mark.c-bad::before { content: "✕ "; }
|
||||
.ok-line::before { content: "● "; color: var(--blue-bright); }
|
||||
/* Waiting for you */
|
||||
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); gap: 0.75rem; }
|
||||
.wcard { border: 1px solid var(--warn); border-radius: var(--radius); padding: 0.8rem 1rem; }
|
||||
.wcard h4 { margin: 0 0 0.3rem; font-size: 1.05em; }
|
||||
.wcard p { margin: 0.25rem 0; font-size: 0.9em; }
|
||||
.wcard form { margin-top: 0.5rem; }
|
||||
.testing { margin: 0.8rem 0 0; padding-left: 1.1rem; font-size: 0.88em; }
|
||||
/* Boxes: one narrow grid; each row is a <details> that opens in place (works without JavaScript) */
|
||||
.boxes { font-size: 0.9em; }
|
||||
.bx-row { display: grid; grid-template-columns: 1.7fr 0.5fr 0.7fr 1.2fr 1.1fr 1.3fr 1.2fr; gap: 0.6rem; align-items: start;
|
||||
padding: 0.55rem 0.8rem; }
|
||||
.bx-head { color: var(--text-2); font-size: 0.85em; border-bottom: 1px solid var(--line); }
|
||||
details.bx { border-bottom: 1px solid var(--line); }
|
||||
details.bx > summary { list-style: none; cursor: pointer; color: var(--text-1); font-size: inherit; }
|
||||
details.bx > summary::-webkit-details-marker { display: none; }
|
||||
details.bx > summary:hover { background: rgba(127,127,127,0.07); }
|
||||
details.bx > summary .name::before { content: "▸ "; color: var(--text-2); }
|
||||
details.bx[open] > summary .name::before { content: "▾ "; }
|
||||
.bx-row > div { min-width: 0; overflow-wrap: anywhere; }
|
||||
.bx-row .sub { display: block; color: var(--text-2); font-size: 0.88em; font-weight: normal; }
|
||||
.bx-more { padding: 0.4rem 0.8rem 1rem 1.8rem; display: grid; grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); gap: 0.4rem 1.5rem; }
|
||||
.bx-more h5 { margin: 0.6rem 0 0.3rem; font-size: 0.85em; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-2); }
|
||||
.kv { display: grid; grid-template-columns: max-content 1fr; gap: 0.15rem 0.8rem; margin: 0; font-size: 0.92em; }
|
||||
.kv dt { color: var(--text-2); }
|
||||
.kv dd { margin: 0; overflow-wrap: anywhere; }
|
||||
.kv form { display: inline; margin-left: 0.3rem; }
|
||||
/* Details */
|
||||
details.more > summary { cursor: pointer; font-weight: 600; font-size: 1.05rem; }
|
||||
details.more h3 { margin-top: 1.3rem; }
|
||||
.tbl-wrap { overflow-x: auto; }
|
||||
.sys td, .sys th { font-size: 0.85em; vertical-align: top; }
|
||||
@media (max-width: 760px) {
|
||||
.nav-links { flex-wrap: wrap; gap: 0.3rem 1rem; }
|
||||
.bx-head { display: none; }
|
||||
.bx-row { grid-template-columns: 1fr 1fr; }
|
||||
.bx-row > div:first-child { grid-column: 1 / -1; }
|
||||
.bx-row > div[data-label]::before { content: attr(data-label); display: block; color: var(--text-2); font-size: 0.78em; font-weight: normal; }
|
||||
.bx-more { padding-left: 0.8rem; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -37,8 +90,138 @@
|
||||
{{if .Flash}}<div class="flash flash-success" style="margin-bottom: 1rem;">{{.Flash}}</div>{{end}}
|
||||
{{if .FlashErr}}<div class="flash flash-error" style="margin-bottom: 1rem;">{{.FlashErr}}</div>{{end}}
|
||||
|
||||
<section class="card" style="margin-bottom: 1.5rem;">
|
||||
<h3 style="margin-top: 0;">Approved releases</h3>
|
||||
<section class="card sys-sec" id="attention">
|
||||
<h3>Needs attention</h3>
|
||||
<p class="why">One line per box that is amber or red, and why. Amber: worth a look. Red: an operator alarm fires (`08` §6.3).</p>
|
||||
{{if .Rows}}
|
||||
{{if .Attention}}
|
||||
<ul class="att">
|
||||
{{range .Attention}}
|
||||
<li><span class="mark c-{{.Mark.Class}}" title="{{.Mark.Title}}">{{.Mark.Text}}</span>
|
||||
<a href="/hosts/{{.HostID}}"><strong>{{.HostID}}</strong></a>{{if .CustomerName}} <span class="text-muted">{{.CustomerName}}</span>{{end}}
|
||||
<span class="rs">{{range $i, $r := .Reasons}}{{if $i}} · {{end}}<span class="{{if $r.Class}}c-{{$r.Class}}{{end}}"{{if $r.Title}} title="{{$r.Title}}"{{end}}>{{$r.Text}}</span>{{end}}</span></li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{else}}<p class="ok-line" style="margin: 0;">All boxes look fine.</p>{{end}}
|
||||
{{else}}<p class="text-muted" style="margin: 0;">No boxes yet.</p>{{end}}
|
||||
</section>
|
||||
|
||||
<section class="card sys-sec" id="waiting">
|
||||
<h3>Waiting for you</h3>
|
||||
<p class="why">Update sets that only you approve: the kernel, the Docker engine and the Proxmox packages. Guest and host Debian updates approve themselves after 24 h and one night on the <span class="term" title="Ring 0 = the demo boxes. They install every new fix first; the other boxes (ring 1) install only what is approved.">ring-0</span> boxes.</p>
|
||||
{{if .Cards}}
|
||||
<div class="cards">
|
||||
{{range .Cards}}
|
||||
<div class="wcard">
|
||||
<h4>{{.What}}</h4>
|
||||
<p>{{.Evidence}}</p>
|
||||
<p class="text-muted">{{.FirstSeen}}. {{.After}}</p>
|
||||
<form method="POST" action="{{.Action}}">
|
||||
<input type="hidden" name="_csrf" value="{{$.CSRFToken}}"><input type="hidden" name="return" value="/system">
|
||||
<button type="submit" class="btn btn-sm" data-confirm="{{.Confirm}}">{{.Button}}</button>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{else}}<p class="ok-line" style="margin: 0;">Nothing waits for your approval.</p>{{end}}
|
||||
{{if .Testing}}
|
||||
<ul class="testing">
|
||||
{{range .Testing}}<li><strong>{{.What}}</strong> — still being tested: <span class="text-muted">{{.Why}}</span></li>{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
</section>
|
||||
|
||||
{{if .Rows}}
|
||||
<section class="card sys-sec boxes" id="boxes" style="padding-left: 0; padding-right: 0;">
|
||||
<h3 style="padding: 0 1rem;">Boxes</h3>
|
||||
<p class="why" style="padding: 0 1rem;">Click a box to see every value it reports, and its ring and update switches. "unknown": the box could not read the value — never a guess.</p>
|
||||
<div class="bx-row bx-head"><div>Box</div><div>Ring</div><div>Health</div><div>Controller · agent</div><div>OS updates</div><div>Kernel</div><div title="The newest OS update run">Last night</div></div>
|
||||
{{range .Rows}}
|
||||
<details class="bx" id="box-{{.HostID}}">
|
||||
<summary class="bx-row">
|
||||
<div class="name"><a href="/hosts/{{.HostID}}">{{.HostID}}</a>{{if .CustomerName}}<span class="sub">{{.CustomerName}}</span>{{end}}</div>
|
||||
<div data-label="Ring"><span class="term" title="{{if eq .Ring 0}}Ring 0: a demo box — it installs every new fix first{{else}}Ring 1: a normal box — it installs only approved releases{{end}}">{{.Ring}}</span></div>
|
||||
<div data-label="Health"><span class="mark{{if .Mark.Class}} c-{{.Mark.Class}}{{end}}" title="{{.Mark.Title}}">{{.Mark.Text}}</span></div>
|
||||
<div data-label="Controller · agent"><span{{if .Controller.Class}} class="c-{{.Controller.Class}}"{{end}}>{{.Controller.Text}}</span><span class="sub{{if .Agent.Class}} c-{{.Agent.Class}}{{end}}" title="{{.Agent.Title}}">agent {{.Agent.Text}}</span></div>
|
||||
<div data-label="OS updates"><span{{if .Updates.Class}} class="c-{{.Updates.Class}}"{{end}} title="{{.Updates.Title}}">{{.Updates.Text}}</span></div>
|
||||
<div data-label="Kernel"><span{{if .KernelShort.Class}} class="c-{{.KernelShort.Class}}"{{end}}>{{.KernelShort.Text}}</span>{{if .KernelNext}}<span class="sub c-warn" title="the next boot changes the kernel">next boot: {{.KernelNext}}</span>{{end}}</div>
|
||||
<div data-label="Last night"><span{{if .LastNight.Class}} class="c-{{.LastNight.Class}}"{{end}} title="{{.LastNight.Title}}">{{.LastNight.Text}}</span></div>
|
||||
</summary>
|
||||
<div class="bx-more sys">
|
||||
<div>
|
||||
{{if .FactsNote}}<p class="c-warn" style="font-weight: normal; margin: 0.6rem 0 0;">{{.FactsNote}}</p>{{end}}
|
||||
<h5>Ring and updates</h5>
|
||||
<dl class="kv">
|
||||
<dt>Ring</dt><dd>ring {{.Ring}}
|
||||
<form method="POST" action="/os/ring/{{.HostID}}">
|
||||
<input type="hidden" name="_csrf" value="{{$.CSRFToken}}"><input type="hidden" name="return" value="/system">
|
||||
{{if eq .Ring 0}}<input type="hidden" name="ring" value="1"><button type="submit" class="btn btn-sm btn-outline" data-confirm="Make {{.HostID}} a normal (ring 1) box? It then installs only approved releases.">→ normal</button>
|
||||
{{else}}<input type="hidden" name="ring" value="0"><button type="submit" class="btn btn-sm btn-outline" data-confirm="Make {{.HostID}} a DEMO (ring 0) box? It then installs every new fix first and takes unsigned Docker steps if its root-owned ring-0 mark allows.">→ demo</button>{{end}}
|
||||
</form></dd>
|
||||
<dt>OS updates</dt><dd>updates {{if .Enabled}}<strong>ON</strong>{{else}}<span class="c-warn">OFF</span>{{end}}
|
||||
<form method="POST" action="/os/enabled/{{.HostID}}">
|
||||
<input type="hidden" name="_csrf" value="{{$.CSRFToken}}"><input type="hidden" name="return" value="/system">
|
||||
{{if .Enabled}}<input type="hidden" name="on" value="0"><button type="submit" class="btn btn-sm btn-outline" data-confirm="Switch OS updates OFF for {{.HostID}}? It keeps reporting and installs nothing.">switch off</button>
|
||||
{{else}}<input type="hidden" name="on" value="1"><button type="submit" class="btn btn-sm btn-outline">switch on</button>{{end}}
|
||||
</form></dd>
|
||||
<dt>Tunnel</dt>{{template "sys_dd" .Tunnel}}
|
||||
<dt>Controller</dt>{{template "sys_dd" .Controller}}
|
||||
<dt>Agent</dt>{{template "sys_dd" .Agent}}
|
||||
<dt>Last OS leg</dt>{{template "sys_dd" .LastLeg}}
|
||||
</dl>
|
||||
<h5>Docker engine</h5>
|
||||
<dl class="kv">
|
||||
<dt>Docker</dt>{{template "sys_dd" .Engine}}
|
||||
<dt>containerd</dt>{{template "sys_dd" .Containerd}}
|
||||
<dt>live-restore</dt>{{template "sys_dd" .LiveRestore}}
|
||||
<dt>Docker release</dt>{{template "sys_dd" .DockerRelease}}
|
||||
</dl>
|
||||
</div>
|
||||
<div>
|
||||
<h5>Host</h5>
|
||||
<dl class="kv">
|
||||
<dt>Proxmox</dt>{{template "sys_dd" .PVE}}
|
||||
<dt>Kernel (running)</dt>{{template "sys_dd" .KernelRunning}}
|
||||
<dt>Kernel (next boot)</dt>{{template "sys_dd" .KernelNextBoot}}
|
||||
<dt title="The kernel GRUB boots normally (R-836). Amber: a one-shot flag names another kernel for the next boot only.">Kernel (default)</dt>{{template "sys_dd" .KernelDefault}}
|
||||
<dt title="The kernel lane (R-836): the newest step, the kernel the box is due, and whether its household was told for tonight (no mail, no step).">Kernel step</dt>{{template "sys_dd" .KernelStep}}
|
||||
<dt>Debian</dt>{{template "sys_dd" .HostDebian}}
|
||||
<dt>Felhom release</dt>{{template "sys_dd" .HostRelease}}
|
||||
<dt>Pending</dt>{{template "sys_dd" .HostPending}}
|
||||
<dt>Not covered</dt>{{template "sys_dd" .HostNotCovered}}
|
||||
<dt>Held</dt>{{template "sys_dd" .Held}}
|
||||
<dt>Reboot needed</dt>{{template "sys_dd" .RebootSince}}
|
||||
<dt>kernel.panic</dt>{{template "sys_dd" .KernelPanic}}
|
||||
<dt>Oops</dt>{{template "sys_dd" .Oops}}
|
||||
<dt>Crash restarts 24 h</dt>{{template "sys_dd" .CrashRestarts24h}}
|
||||
<dt>Crash guard</dt>{{template "sys_dd" .Guard}}
|
||||
<dt title="The root-owned config bundle: sudoers, wrappers, units (R-840)">Root files</dt>{{template "sys_dd" .Bundle}}
|
||||
</dl>
|
||||
</div>
|
||||
<div>
|
||||
<h5>Guest</h5>
|
||||
<dl class="kv">
|
||||
<dt>Guest Debian</dt>{{template "sys_dd" .GuestDebian}}
|
||||
<dt>Felhom release</dt>{{template "sys_dd" .GuestRelease}}
|
||||
<dt>Pending</dt>{{template "sys_dd" .GuestPending}}
|
||||
<dt>Restart needed</dt>{{template "sys_dd" .GuestRestart}}
|
||||
<dt title="The newest pct fstrim of the guest's disks, and what it freed (R-444). The boxes trim weekly.">Last disk trim</dt>{{template "sys_dd" .Trim}}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
{{end}}
|
||||
</section>
|
||||
{{else}}
|
||||
<div class="empty-state"><p>No boxes yet.</p></div>
|
||||
{{end}}
|
||||
|
||||
<details class="card more" id="details">
|
||||
<summary>Details</summary>
|
||||
<p class="why text-muted" style="font-size: 0.85em;">Release ids, cancelled approvals, what ring 0 runs, the version floors, and every box's crash guard and root files.</p>
|
||||
|
||||
<h3>Approved releases</h3>
|
||||
<p class="text-muted" style="font-size: 0.85em; margin-top: 0;">The newest approved set of each layer. A <span class="term" title="Approved while a TEST wait override was active. It is cancelled when the hub starts without the override (`11` §5.3.1).">TEST approval</span> is amber.</p>
|
||||
<div class="rel-grid">
|
||||
{{range .Releases}}
|
||||
<div><strong>{{.Layer}}</strong>: <code>{{.ID}}</code><br>
|
||||
@@ -46,6 +229,11 @@
|
||||
{{if .Test}}<br><span class="c-warn" title="Approved while a TEST wait override was active. It is cancelled when the hub starts without the override (`11` §5.3.1).">TEST approval</span>{{end}}</div>
|
||||
{{else}}<div class="text-muted">No release approved yet.</div>{{end}}
|
||||
</div>
|
||||
<form method="POST" action="/os/approve-now" style="margin-top: 0.8rem;">
|
||||
<input type="hidden" name="_csrf" value="{{$.CSRFToken}}"><input type="hidden" name="return" value="/system">
|
||||
<button type="submit" class="btn btn-sm btn-danger" data-confirm="Approve the guest and host sets now, without the usual 24 h and one night? Every ring-1 box installs them at its next night run.">Approve now (guest + host)</button>
|
||||
<span class="text-muted" style="font-size: 0.85em;">An urgent fix only — normally the hub approves after 24 h and one night.</span>
|
||||
</form>
|
||||
{{if .Cancelled}}
|
||||
<h3>Cancelled approvals (last 7 days)</h3>
|
||||
<div class="rel-grid">
|
||||
@@ -56,42 +244,24 @@
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<h3>What ring 0 runs now</h3>
|
||||
<div class="rel-grid">
|
||||
{{range .Candidates}}
|
||||
<div><strong>{{.Layer}}</strong>:
|
||||
{{if .Fingerprint}}{{.Packages}} packages, first seen {{.FirstSeen.UTC.Format "2006-01-02 15:04"}} UTC{{else}}<span class="text-muted">—</span>{{end}}<br>
|
||||
{{if .Approved}}<span class="text-muted">approved as {{.Approved}}</span>
|
||||
{{else if .Waiting}}<span class="text-muted">{{.Waiting}}</span>{{end}}
|
||||
{{if and (eq .Layer "docker") .Fingerprint (not .Approved) (eq .Waiting "")}}
|
||||
<form method="POST" action="/os/approve-docker" style="margin-top: 0.3rem;">
|
||||
<input type="hidden" name="_csrf" value="{{$.CSRFToken}}"><input type="hidden" name="return" value="/system">
|
||||
<button type="submit" class="btn btn-sm" data-confirm="Approve this Docker engine set? Ring-1 boxes take it only through a signed operator job.">Approve Docker set</button>
|
||||
</form>{{end}}
|
||||
{{if and (eq .Layer "pve") .Fingerprint (not .Approved) (eq .Waiting "")}}
|
||||
<form method="POST" action="/os/approve-pve" style="margin-top: 0.3rem;">
|
||||
<input type="hidden" name="_csrf" value="{{$.CSRFToken}}"><input type="hidden" name="return" value="/system">
|
||||
<button type="submit" class="btn btn-sm" data-confirm="Approve this Proxmox package set (no kernel)? Ring-1 boxes take it only through a signed operator job.">Approve Proxmox set</button>
|
||||
</form>{{end}}
|
||||
{{if and (eq .Layer "kernel") .Fingerprint (not .Approved) (eq .Waiting "")}}
|
||||
<form method="POST" action="/os/approve-kernel" style="margin-top: 0.3rem;">
|
||||
<input type="hidden" name="_csrf" value="{{$.CSRFToken}}"><input type="hidden" name="return" value="/system">
|
||||
<button type="submit" class="btn btn-sm" data-confirm="Approve this kernel set? Every ring-0 box booted it healthily after a night step. Ring-1 boxes take it only through a signed os_kernel_step, and restart only on a night their household was told about.">Approve kernel set</button>
|
||||
</form>{{end}}
|
||||
{{else if .Waiting}}<span class="text-muted">{{.Waiting}}</span>
|
||||
{{else if .Fingerprint}}<span class="text-muted">ready — see "Waiting for you"</span>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<form method="POST" action="/os/approve-now" style="margin-top: 0.8rem;">
|
||||
<input type="hidden" name="_csrf" value="{{$.CSRFToken}}"><input type="hidden" name="return" value="/system">
|
||||
<button type="submit" class="btn btn-sm btn-danger" data-confirm="Approve the guest and host sets ring 0 runs NOW, without the 24 h + 1 night wait? Every ring-1 box installs them at its next night run.">Approve now (guest + host)</button>
|
||||
<span class="text-muted" style="font-size: 0.85em;">An urgent fix only — normally the hub approves after 24 h and one night.</span>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="card" id="version-floors">
|
||||
<h3 style="margin-top: 0;">Version floors</h3>
|
||||
<h3 id="version-floors">Version floors</h3>
|
||||
<p>Global controller floor: <strong>{{if .GlobalFloor}}{{.GlobalFloor}}{{else}}none{{end}}</strong> · vouched agent: <strong>{{if .VouchedAgent}}{{.VouchedAgent}}{{else}}none{{end}}</strong></p>
|
||||
<p class="text-muted" style="font-size: 0.85em; margin-top: -0.5rem;"><span class="term" title="A floor is the lowest controller version a box must run; the hub moves a box below it up.">What is a floor?</span> · <span class="term" title="The agent version the operator checked and signed off for the fleet. Agents update only by a per-box signed job.">What is a vouched agent?</span></p>
|
||||
{{if .Floors}}
|
||||
<div class="tbl-wrap">
|
||||
<table class="data-table">
|
||||
<thead><tr><th>Customer</th><th>Own floor</th><th>Set</th><th>Global floor moves it?</th></tr></thead>
|
||||
<tbody>
|
||||
@@ -105,66 +275,33 @@
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{else}}<p class="text-muted">No per-customer floors: every box follows the global floor.</p>{{end}}
|
||||
</section>
|
||||
|
||||
{{if .Rows}}
|
||||
<section class="card" style="padding: 0; overflow-x: auto;">
|
||||
{{if .Rows}}
|
||||
<h3>Crash guard and root files</h3>
|
||||
<div class="tbl-wrap">
|
||||
<table class="data-table sys">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Box</th><th>Ring / updates</th><th>Tunnel</th>
|
||||
<th class="grp">Proxmox</th><th>Kernel (running)</th><th>Kernel (next boot)</th><th title="The kernel GRUB boots normally (R-836). Amber: a one-shot flag names another kernel for the next boot only.">Kernel (default)</th><th title="The kernel lane (R-836): the newest step, the kernel the box is due, and whether its household was told for tonight (no mail, no step).">Kernel step</th><th>Debian</th><th>Felhom release</th><th>Pending</th><th>Not covered</th><th>Held</th><th>Reboot needed</th><th>kernel.panic</th><th>Oops</th><th>Crash restarts 24 h</th><th>Crash guard</th><th title="The root-owned config bundle: sudoers, wrappers, units (R-840)">Root files</th><th title="The box's agent against the vouched one (R-530). Agents update only by a per-box signed job.">Agent</th>
|
||||
<th class="grp">Guest Debian</th><th>Felhom release</th><th>Pending</th><th>Restart needed</th><th title="The newest pct fstrim of the guest's disks, and what it freed (R-444). The boxes trim weekly.">Last disk trim</th>
|
||||
<th class="grp">Docker</th><th>containerd</th><th>live-restore</th><th>Docker release</th>
|
||||
<th class="grp">Last OS leg</th>
|
||||
</tr>
|
||||
<tr class="text-muted"><th></th><th></th><th></th><th class="grp" colspan="17">host</th><th class="grp" colspan="5">guest</th><th class="grp" colspan="4">Docker engine</th><th class="grp"></th></tr>
|
||||
</thead>
|
||||
<thead><tr><th>Box</th><th>kernel.panic</th><th>Oops</th><th>Crash restarts 24 h</th><th>Crash guard</th><th title="The root-owned config bundle: sudoers, wrappers, units (R-840)">Root files</th></tr></thead>
|
||||
<tbody>
|
||||
{{range .Rows}}
|
||||
<tr>
|
||||
<td><a href="/hosts/{{.HostID}}">{{.HostID}}</a>{{if .CustomerName}}<br><span class="text-muted">{{.CustomerName}}</span>{{end}}
|
||||
{{if .FactsNote}}<br><span class="c-warn" style="font-weight: normal;">{{.FactsNote}}</span>{{end}}</td>
|
||||
<td>
|
||||
ring {{.Ring}}
|
||||
<form method="POST" action="/os/ring/{{.HostID}}">
|
||||
<input type="hidden" name="_csrf" value="{{$.CSRFToken}}"><input type="hidden" name="return" value="/system">
|
||||
{{if eq .Ring 0}}<input type="hidden" name="ring" value="1"><button type="submit" class="btn btn-sm btn-outline" data-confirm="Make {{.HostID}} a normal (ring 1) box? It then installs only approved releases.">→ normal</button>
|
||||
{{else}}<input type="hidden" name="ring" value="0"><button type="submit" class="btn btn-sm btn-outline" data-confirm="Make {{.HostID}} a DEMO (ring 0) box? It then installs every new fix first and takes unsigned Docker steps if its root-owned ring-0 mark allows.">→ demo</button>{{end}}
|
||||
</form><br>
|
||||
updates {{if .Enabled}}<strong>ON</strong>{{else}}<span class="c-warn">OFF</span>{{end}}
|
||||
<form method="POST" action="/os/enabled/{{.HostID}}">
|
||||
<input type="hidden" name="_csrf" value="{{$.CSRFToken}}"><input type="hidden" name="return" value="/system">
|
||||
{{if .Enabled}}<input type="hidden" name="on" value="0"><button type="submit" class="btn btn-sm btn-outline" data-confirm="Switch OS updates OFF for {{.HostID}}? It keeps reporting and installs nothing.">switch off</button>
|
||||
{{else}}<input type="hidden" name="on" value="1"><button type="submit" class="btn btn-sm btn-outline">switch on</button>{{end}}
|
||||
</form>
|
||||
</td>
|
||||
{{template "sys_cell" .Tunnel}}
|
||||
<td class="grp {{if .PVE.Class}}c-{{.PVE.Class}}{{end}}">{{.PVE.Text}}</td>
|
||||
{{template "sys_cell" .KernelRunning}}{{template "sys_cell" .KernelNextBoot}}{{template "sys_cell" .KernelDefault}}{{template "sys_cell" .KernelStep}}{{template "sys_cell" .HostDebian}}
|
||||
{{template "sys_cell" .HostRelease}}{{template "sys_cell" .HostPending}}{{template "sys_cell" .HostNotCovered}}
|
||||
{{template "sys_cell" .Held}}{{template "sys_cell" .RebootSince}}{{template "sys_cell" .KernelPanic}}{{template "sys_cell" .Oops}}
|
||||
{{template "sys_cell" .CrashRestarts24h}}{{template "sys_cell" .Guard}}{{template "sys_cell" .Bundle}}{{template "sys_cell" .Agent}}
|
||||
<td class="grp {{if .GuestDebian.Class}}c-{{.GuestDebian.Class}}{{end}}">{{.GuestDebian.Text}}</td>
|
||||
{{template "sys_cell" .GuestRelease}}{{template "sys_cell" .GuestPending}}{{template "sys_cell" .GuestRestart}}{{template "sys_cell" .Trim}}
|
||||
<td class="grp {{if .Engine.Class}}c-{{.Engine.Class}}{{end}}">{{.Engine.Text}}</td>
|
||||
{{template "sys_cell" .Containerd}}{{template "sys_cell" .LiveRestore}}{{template "sys_cell" .DockerRelease}}
|
||||
<td class="grp {{if .LastLeg.Class}}c-{{.LastLeg.Class}}{{end}}" title="{{.LastLeg.Title}}">{{.LastLeg.Text}}</td>
|
||||
</tr>
|
||||
<tr><td><a href="/hosts/{{.HostID}}">{{.HostID}}</a></td>{{template "sys_cell" .KernelPanic}}{{template "sys_cell" .Oops}}{{template "sys_cell" .CrashRestarts24h}}{{template "sys_cell" .Guard}}{{template "sys_cell" .Bundle}}</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<p class="text-muted" style="font-size: 0.85em;">Amber: worth a look. Red: an operator alarm fires (`08` §6.3). "unknown": the box could not read the value — never a guess.</p>
|
||||
{{else}}
|
||||
<div class="empty-state"><p>No boxes yet.</p></div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</details>
|
||||
|
||||
<footer style="margin-top: 2rem; color: var(--text-muted); font-size: 0.8rem; text-align: center;">
|
||||
Felhom Hub <span style="font-family: var(--font-mono)">{{hubVersion}}</span>
|
||||
</footer>
|
||||
</div>
|
||||
<script>
|
||||
/* A link to a part of "Details" (e.g. /system#version-floors) opens it. Without JavaScript the section still opens by a click. */
|
||||
(function(){var h=location.hash&&document.getElementById(location.hash.slice(1));if(!h)return;var d=h.closest('details');while(d){d.open=true;d=d.parentElement&&d.parentElement.closest('details');}h.scrollIntoView();})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
{{define "sys_cell"}}<td{{if .Class}} class="c-{{.Class}}"{{end}}{{if .Title}} title="{{.Title}}"{{end}}>{{.Text}}</td>{{end}}
|
||||
{{define "sys_dd"}}<dd{{if .Class}} class="c-{{.Class}}"{{end}}{{if .Title}} title="{{.Title}}"{{end}}>{{.Text}}</dd>{{end}}
|
||||
|
||||
Reference in New Issue
Block a user