fleet-manager ui: readability + reboot-now
- Contrast: lighten the muted/faint text tokens (AA on the hull panels) and make Monitor host-card data values bright ink against dim faint labels, so version/ tmpfs/seen details read clearly on the blue-grey panels (was grey-on-grey). - Lifecycle: reboot selector now offers none | now | at... — 'now' sets reboot_at to the current time (agent reboots on reconcile); still confirm-gated as a high-consequence action.
This commit is contained in:
@@ -27,10 +27,10 @@
|
||||
--color-red: #FF3B2F; /* alert / out-of-range */
|
||||
--color-amber-hot: #FF8A3D; /* caution band */
|
||||
|
||||
/* text */
|
||||
--color-ink: #E9EEF6; /* primary text (warm off-white) */
|
||||
--color-muted: #7E8DA3; /* secondary text, labels */
|
||||
--color-faint: #46566E; /* tertiary, timestamps, meta */
|
||||
/* text — tuned for legibility on the dark hull panels (AA on hull2) */
|
||||
--color-ink: #EAF1F9; /* primary text (warm off-white) */
|
||||
--color-muted: #AEC0D6; /* secondary text / data values — clearly readable */
|
||||
--color-faint: #7688A2; /* tertiary labels, timestamps, meta */
|
||||
|
||||
/* hairlines */
|
||||
--color-line: #223146; /* structural */
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
let selected = $state(null)
|
||||
let busy = $state(false)
|
||||
let confirmOpen = $state(false)
|
||||
let rebootMode = $state('none') // none | now | at
|
||||
let form = $state({ target_version: '', kiosk_url: '', reboot_at: '', drain: false })
|
||||
|
||||
async function load() {
|
||||
@@ -24,6 +25,7 @@
|
||||
function pick(h) {
|
||||
selected = h
|
||||
okMsg = ''
|
||||
rebootMode = 'none'
|
||||
form = {
|
||||
target_version: h.desired?.target_version || '',
|
||||
kiosk_url: h.desired?.kiosk_url || '',
|
||||
@@ -31,9 +33,8 @@
|
||||
drain: !!h.desired?.drain,
|
||||
}
|
||||
}
|
||||
let consequence = $derived(
|
||||
[form.drain ? 'DRAIN this host' : null, form.reboot_at ? 'schedule a REBOOT' : null].filter(Boolean).join(' and ')
|
||||
)
|
||||
let rebootConseq = $derived(rebootMode === 'now' ? 'REBOOT it NOW' : rebootMode === 'at' && form.reboot_at ? 'schedule a REBOOT' : null)
|
||||
let consequence = $derived([form.drain ? 'DRAIN this host' : null, rebootConseq].filter(Boolean).join(' and '))
|
||||
async function apply() {
|
||||
confirmOpen = false
|
||||
busy = true
|
||||
@@ -44,7 +45,8 @@
|
||||
kiosk_url: form.kiosk_url || undefined,
|
||||
drain: form.drain,
|
||||
}
|
||||
if (form.reboot_at) body.reboot_at = new Date(form.reboot_at).toISOString()
|
||||
if (rebootMode === 'now') body.reboot_at = new Date().toISOString()
|
||||
else if (rebootMode === 'at' && form.reboot_at) body.reboot_at = new Date(form.reboot_at).toISOString()
|
||||
try {
|
||||
const res = await api.put(`/api/hosts/${selected.identity.cert_serial}/desired`, body)
|
||||
okMsg = res.pushed_live ? 'applied — pushed to the live host' : 'applied — host reconciles on next connect'
|
||||
@@ -58,7 +60,7 @@
|
||||
}
|
||||
function submit() {
|
||||
// Gate destructive changes behind a confirm; a plain version/URL change applies directly.
|
||||
if (form.drain || form.reboot_at) confirmOpen = true
|
||||
if (form.drain || rebootMode === 'now' || (rebootMode === 'at' && form.reboot_at)) confirmOpen = true
|
||||
else apply()
|
||||
}
|
||||
$effect(() => {
|
||||
@@ -94,10 +96,19 @@
|
||||
<span class="tag">kiosk url</span>
|
||||
<input class="mt-1 w-full rounded border border-line bg-void2 px-2 py-1.5 text-sm text-ink" bind:value={form.kiosk_url} placeholder="http://boot.fleet.lab/kiosk/" />
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="tag">reboot at (optional)</span>
|
||||
<input type="datetime-local" class="mt-1 w-full rounded border border-line bg-void2 px-2 py-1.5 text-sm text-ink" bind:value={form.reboot_at} />
|
||||
</label>
|
||||
<div class="block">
|
||||
<span class="tag">reboot</span>
|
||||
<div class="mt-1 flex items-center gap-2">
|
||||
<select class="rounded border border-line bg-void2 px-2 py-1.5 text-sm {rebootMode !== 'none' ? 'text-violet-soft' : 'text-ink'}" bind:value={rebootMode}>
|
||||
<option value="none">none</option>
|
||||
<option value="now">now</option>
|
||||
<option value="at">at…</option>
|
||||
</select>
|
||||
{#if rebootMode === 'at'}
|
||||
<input type="datetime-local" class="flex-1 rounded border border-line bg-void2 px-2 py-1.5 text-sm text-ink" bind:value={form.reboot_at} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<label class="mt-4 flex items-center gap-2">
|
||||
<input type="checkbox" bind:checked={form.drain} />
|
||||
<span class="font-display text-sm text-violet-soft">drain (stop taking work)</span>
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
<StatePill state={h.online ? healthState(h.health) : 'off'} />
|
||||
</div>
|
||||
<div class="tag mt-0.5">{shortSerial(h.identity.cert_serial)} · {h.binding?.location || 'unbound'}</div>
|
||||
<div class="mt-2 grid grid-cols-3 gap-2 font-mono text-xs text-muted">
|
||||
<div class="mt-2 grid grid-cols-3 gap-2 font-mono text-xs text-ink">
|
||||
<div><span class="text-faint">ver </span>{h.version?.image_version || '—'}{#if drift(h)}<span class="text-amber-hot"> ⇢ {h.desired.target_version}</span>{/if}</div>
|
||||
<div><span class="text-faint">tmpfs </span>{pct(h.health?.tmpfs_used_pct)}</div>
|
||||
<div><span class="text-faint">seen </span>{h.online ? 'live' : relTime(h.last_seen)}</div>
|
||||
|
||||
Reference in New Issue
Block a user