dashboard: spinners on in-flight lifecycle actions + cleaner row layout
backend: - TransientKind grows Starting / Stopping / Restarting / Rebuilding / Destroying alongside the existing Spawning. each dashboard handler (start/restart/kill/rebuild/destroy) wraps the lifecycle call with set_transient + clear_transient so the dashboard knows what's in flight. transient kind is surfaced inline on ContainerView.pending (existing-container actions) — only Spawning (pre-creation) lands in the separate transients list. frontend: - container row is now two lines: identity + meta on top, action buttons below. less cluttered, leaves room for the pending state pill. pending rows dim their actions and surface a pulsing '◐ spawning… / starting… / stopping… / restarting… / rebuilding… / destroying…' indicator next to the name. - 'needs login' / 'needs update' chips moved into a unified .badge styling for consistency. - auto-refresh kicks in not only on transient spawn but on any container with a pending action.
This commit is contained in:
parent
300be8afa9
commit
c337cc06f8
5 changed files with 157 additions and 38 deletions
|
|
@ -118,61 +118,65 @@
|
|||
return;
|
||||
}
|
||||
|
||||
const ul = el('ul');
|
||||
const ul = el('ul', { class: 'containers' });
|
||||
for (const c of s.containers) {
|
||||
const url = `http://${s.hostname}:${c.port}/`;
|
||||
const li = el('li');
|
||||
li.append(
|
||||
el('a', { href: url }, c.name),
|
||||
' ',
|
||||
const li = el('li', { class: 'container-row' + (c.pending ? ' pending' : '') });
|
||||
|
||||
// ── line 1: identity ─────────────────────────────────────────
|
||||
const head = el('div', { class: 'head' });
|
||||
head.append(
|
||||
el('a', { class: 'name', href: url }, c.name),
|
||||
el('span', { class: c.is_manager ? 'role role-m1nd' : 'role role-ag3nt' },
|
||||
c.is_manager ? 'm1nd' : 'ag3nt'),
|
||||
);
|
||||
if (c.needs_login) {
|
||||
li.append(' ', el('a',
|
||||
{ class: 'role role-pending', href: url }, 'needs login →'));
|
||||
if (c.pending) {
|
||||
head.append(el('span', { class: 'pending-state' },
|
||||
el('span', { class: 'spinner' }, '◐'), ' ', c.pending + '…'));
|
||||
} else if (c.needs_login) {
|
||||
head.append(el('a',
|
||||
{ class: 'badge badge-warn', href: url }, 'needs login →'));
|
||||
}
|
||||
if (c.needs_update) {
|
||||
li.append(' ', form(
|
||||
'/rebuild/' + c.name, 'role role-pending btn-inline', 'needs update ↻',
|
||||
head.append(form(
|
||||
'/rebuild/' + c.name, 'badge badge-warn btn-inline', 'needs update ↻',
|
||||
'rebuild ' + c.name + '? hot-reloads the container.',
|
||||
));
|
||||
}
|
||||
li.append(' ', el('span', { class: 'meta' }, `${c.container} :${c.port}`));
|
||||
head.append(el('span', { class: 'meta' }, `${c.container} :${c.port}`));
|
||||
li.append(head);
|
||||
|
||||
// ── line 2: action buttons ───────────────────────────────────
|
||||
const actions = el('div', { class: 'actions' });
|
||||
if (c.running) {
|
||||
li.append(
|
||||
' ',
|
||||
actions.append(
|
||||
form('/restart/' + c.name, 'btn-restart', '↺ R3ST4RT', 'restart ' + c.name + '?'),
|
||||
);
|
||||
if (!c.is_manager) {
|
||||
li.append(
|
||||
' ',
|
||||
actions.append(
|
||||
form('/kill/' + c.name, 'btn-stop', '■ ST0P', 'stop ' + c.name + '?'),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
li.append(
|
||||
' ',
|
||||
actions.append(
|
||||
form('/start/' + c.name, 'btn-start', '▶ ST4RT', 'start ' + c.name + '?'),
|
||||
);
|
||||
}
|
||||
li.append(
|
||||
' ',
|
||||
actions.append(
|
||||
form('/rebuild/' + c.name, 'btn-rebuild', '↻ R3BU1LD',
|
||||
'rebuild ' + c.name + '? hot-reloads the container.'),
|
||||
);
|
||||
if (!c.is_manager) {
|
||||
li.append(
|
||||
' ',
|
||||
actions.append(
|
||||
form('/destroy/' + c.name, 'btn-destroy', 'DESTR0Y',
|
||||
'destroy ' + c.name + '? container is removed; state + creds kept.'),
|
||||
' ',
|
||||
form('/destroy/' + c.name, 'btn-destroy', 'PURG3',
|
||||
'PURGE ' + c.name + '? container, config history, claude creds, '
|
||||
+ 'and /state/ notes are all WIPED. no undo.', { purge: 'on' }),
|
||||
);
|
||||
}
|
||||
li.append(actions);
|
||||
|
||||
ul.append(li);
|
||||
}
|
||||
root.append(ul);
|
||||
|
|
@ -304,8 +308,10 @@
|
|||
renderQuestions(s);
|
||||
renderInbox(s);
|
||||
renderApprovals(s);
|
||||
// Auto-refresh while a spawn is in flight; otherwise back off.
|
||||
const next = s.transients.length ? 2000 : 0;
|
||||
// Auto-refresh while a spawn is in flight OR while any container
|
||||
// has a pending lifecycle action; otherwise back off.
|
||||
const anyPending = s.containers.some((c) => c.pending);
|
||||
const next = (s.transients.length || anyPending) ? 2000 : 0;
|
||||
if (pollTimer) { clearTimeout(pollTimer); pollTimer = null; }
|
||||
if (next) pollTimer = setTimeout(refreshState, next);
|
||||
} catch (err) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue