fleet-manager: host uptime metric + reboot-pending status
The manager lets an admin schedule a reboot (desired.reboot_at) but never showed whether it had happened. Add host uptime to the health snapshot and derive a reboot status from it. - proto: HealthSnapshot.uptime_seconds (15); regenerated pb.go (manager + agent copy). - agent: probe /proc/uptime -> uptime_seconds (with parser + test). - manager: Health.UptimeSeconds cached in inventory; snapshotToHealth maps it; influx writes + whitelists uptime_seconds as a queryable field. - UI: fmt.js dur() + rebootStatus() — boot_time = last_seen - uptime (manager clock frame, no host/manager sync needed), compared to reboot_at. Monitor host detail shows an "uptime" row and a "reboot" row: pending in X / overdue by X / rebooted. Observability-only — works before the reboot ACTUATOR lands (deferred). Once a host boots after the scheduled time (BMC/manual now, agent later), the badge flips to "rebooted" automatically.
This commit is contained in:
@@ -35,3 +35,35 @@ export function healthState(h) {
|
||||
export function shortSerial(s) {
|
||||
return s ? s.slice(0, 8) : '—'
|
||||
}
|
||||
|
||||
// dur formats a span of SECONDS as a compact "3d 4h" / "5h 12m" / "45s".
|
||||
export function dur(s) {
|
||||
s = Math.max(0, Math.floor(s || 0))
|
||||
const d = Math.floor(s / 86400)
|
||||
const h = Math.floor((s % 86400) / 3600)
|
||||
const m = Math.floor((s % 3600) / 60)
|
||||
const sec = s % 60
|
||||
if (d) return `${d}d ${h}h`
|
||||
if (h) return `${h}h ${m}m`
|
||||
if (m) return `${m}m ${sec}s`
|
||||
return `${sec}s`
|
||||
}
|
||||
|
||||
// rebootStatus reports whether a scheduled reboot (desired.reboot_at) has been
|
||||
// actioned, by comparing the host's boot time to the requested time. Boot time is
|
||||
// derived in the manager's clock frame: last_seen (a manager-stamped snapshot
|
||||
// time) minus the reported host uptime — so it needs no host/manager clock sync.
|
||||
// Returns null when no reboot is scheduled. state ∈ pending | overdue | done.
|
||||
export function rebootStatus(host) {
|
||||
const raIso = host?.desired?.reboot_at
|
||||
if (!raIso) return null
|
||||
const ra = new Date(raIso).getTime()
|
||||
if (Number.isNaN(ra) || ra <= 0) return null // unset / Go zero time
|
||||
const now = Date.now()
|
||||
const up = host?.health?.uptime_seconds || 0
|
||||
const ls = host?.last_seen ? new Date(host.last_seen).getTime() : NaN
|
||||
const boot = up > 0 && ls > 0 ? ls - up * 1000 : NaN // manager-frame boot time
|
||||
if (!Number.isNaN(boot) && boot >= ra) return { state: 'done', label: 'rebooted' }
|
||||
if (ra > now) return { state: 'pending', label: 'reboot pending in ' + dur((ra - now) / 1000) }
|
||||
return { state: 'overdue', label: 'reboot overdue by ' + dur((now - ra) / 1000) }
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import Panel from '../components/Panel.svelte'
|
||||
import StatePill from '../components/StatePill.svelte'
|
||||
import Chart from '../components/Chart.svelte'
|
||||
import { relTime, pct, num, healthState, shortSerial } from '../lib/fmt.js'
|
||||
import { relTime, pct, num, healthState, shortSerial, dur, rebootStatus } from '../lib/fmt.js'
|
||||
|
||||
let hosts = $state([])
|
||||
let error = $state('')
|
||||
@@ -105,6 +105,11 @@
|
||||
<dt class="text-faint">selinux</dt><dd class="text-muted">{selected.health?.selinux || '—'}</dd>
|
||||
<dt class="text-faint">chrony</dt><dd class="text-muted">{selected.health?.chrony || '—'}</dd>
|
||||
<dt class="text-faint">kiosk</dt><dd class="text-muted">{selected.health?.kiosk_active ? 'active' : 'idle'} · {selected.usage?.session_uptime || '—'}</dd>
|
||||
<dt class="text-faint">uptime</dt><dd class="text-muted">{selected.health?.uptime_seconds ? dur(selected.health.uptime_seconds) : '—'}</dd>
|
||||
{#if rebootStatus(selected)}
|
||||
{@const rb = rebootStatus(selected)}
|
||||
<dt class="text-faint">reboot</dt><dd class={rb.state === 'overdue' ? 'text-amber-hot' : rb.state === 'pending' ? 'text-amber' : 'text-teal'}>{rb.label}</dd>
|
||||
{/if}
|
||||
<dt class="text-faint">last seen</dt><dd class="text-muted">{selected.online ? 'live' : relTime(selected.last_seen)}</dd>
|
||||
</dl>
|
||||
<div class="mt-4 space-y-3">
|
||||
|
||||
Reference in New Issue
Block a user