schedules fire-now: render FireNowReport inline on the button
damocles's #472 returns `{ ok, failed, missing, one_shot_consumed }` from the fire-now endpoint. Parse the response and flash the per-target outcome on the button itself for ~1.5s before refreshSchedules() repaints — operator sees the result immediately without a modal alert or an `/api/schedules` re-fetch round-trip. Button label transitions: ↯ fire now → ◐ firing… → ↯ fired: 3 ok, 1 missing — consumed (green flash, then row refresh)
This commit is contained in:
parent
7e5b496aa8
commit
f35382e57c
2 changed files with 33 additions and 3 deletions
|
|
@ -2360,7 +2360,7 @@ window.marked = marked;
|
|||
fireBtn.disabled = true;
|
||||
fireBtn.title = 'every target is cancelled — nothing to fire';
|
||||
}
|
||||
fireBtn.addEventListener('click', () => fireScheduleNow(s.id, isOneShot, activeTargets.map((t) => t.target)));
|
||||
fireBtn.addEventListener('click', () => fireScheduleNow(s.id, isOneShot, activeTargets.map((t) => t.target), fireBtn));
|
||||
actions.append(fireBtn);
|
||||
const editBtn = el('button', { type: 'button', class: 'btn btn-edit-schedule' },
|
||||
editingSchedules.has(s.id) ? '✎ close edit' : '✎ edit');
|
||||
|
|
@ -2568,7 +2568,7 @@ window.marked = marked;
|
|||
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = originalLabel; }
|
||||
}
|
||||
}
|
||||
async function fireScheduleNow(id, isOneShot, targets) {
|
||||
async function fireScheduleNow(id, isOneShot, targets, btn) {
|
||||
const targetList = targets.length ? targets.join(', ') : '(no active targets)';
|
||||
const prompt = isOneShot
|
||||
? `fire schedule #${id} now to ${targetList}?\n\n`
|
||||
|
|
@ -2578,6 +2578,8 @@ window.marked = marked;
|
|||
+ 'this is RECURRING — sends an extra pulse out-of-band. '
|
||||
+ 'the regular cadence keeps firing on schedule.';
|
||||
if (!confirm(prompt)) return;
|
||||
const originalLabel = btn ? btn.innerHTML : '';
|
||||
if (btn) { btn.disabled = true; btn.innerHTML = '<span class="spinner">◐</span> firing…'; }
|
||||
try {
|
||||
const resp = await fetch('/api/schedules/' + encodeURIComponent(id) + '/fire-now', {
|
||||
method: 'POST',
|
||||
|
|
@ -2586,11 +2588,30 @@ window.marked = marked;
|
|||
if (!resp.ok) {
|
||||
const text = await resp.text().catch(() => '');
|
||||
alert('fire-now failed: http ' + resp.status + (text ? '\n\n' + text : ''));
|
||||
if (btn) { btn.disabled = false; btn.innerHTML = originalLabel; }
|
||||
return;
|
||||
}
|
||||
await refreshSchedules();
|
||||
// Backend returns FireNowReport { ok, failed, missing, one_shot_consumed }.
|
||||
// Flash the per-target outcome on the button itself so the operator
|
||||
// sees the result immediately, then refresh to pick up the
|
||||
// authoritative per-target `last_result` annotations.
|
||||
let report = null;
|
||||
try { report = await resp.json(); } catch { /* shape drift / empty body — ignore */ }
|
||||
if (btn && report) {
|
||||
const bits = [];
|
||||
if (report.ok) bits.push(report.ok + ' ok');
|
||||
if (report.failed) bits.push(report.failed + ' failed');
|
||||
if (report.missing) bits.push(report.missing + ' missing');
|
||||
const suffix = report.one_shot_consumed ? ' — consumed' : '';
|
||||
btn.innerHTML = '↯ fired: ' + (bits.join(', ') || 'no targets') + suffix;
|
||||
btn.classList.add('btn-fire-now-flashed');
|
||||
}
|
||||
// Hold the flash briefly so the operator can read it before the
|
||||
// refresh wipes the row in place.
|
||||
setTimeout(() => { refreshSchedules(); }, 1500);
|
||||
} catch (err) {
|
||||
alert('fire-now failed: ' + err);
|
||||
if (btn) { btn.disabled = false; btn.innerHTML = originalLabel; }
|
||||
}
|
||||
}
|
||||
async function cancelScheduleAll(id) {
|
||||
|
|
|
|||
|
|
@ -851,6 +851,15 @@ ul form.inline { display: inline-block; }
|
|||
.btn-talk { color: var(--cyan); border-color: var(--cyan); }
|
||||
.btn-spawn { color: var(--amber); border-color: var(--amber); }
|
||||
.btn-fire-now { color: var(--mauve, #cba6f7); border-color: var(--mauve, #cba6f7); }
|
||||
/* Post-fire flash: report renders directly on the button for ~1.5s
|
||||
so the operator sees ok/failed/missing/consumed counts inline
|
||||
without a modal. Green when at least one ok; muted otherwise. */
|
||||
.btn-fire-now-flashed {
|
||||
color: var(--green);
|
||||
border-color: var(--green);
|
||||
text-shadow: 0 0 6px currentColor;
|
||||
box-shadow: 0 0 8px -2px currentColor;
|
||||
}
|
||||
/* #474: inline edit button on each schedule row. Yellow reads as a
|
||||
parallel destructive-adjacent action (edit changes state, but
|
||||
isn't deletion). */
|
||||
|
|
|
|||
Loading…
Reference in a new issue