schedules fire-now: textContent + DOM nodes instead of innerHTML
argus 🟡 note on #471 — values rendered into the button flash are all server-side ints/bool today, but textContent + element children is the safer pattern if a stringy field ever lands in FireNowReport. Captures original children on enter so error paths can restore faithfully (the previous innerHTML round-trip would have already lost any nested element structure).
This commit is contained in:
parent
f35382e57c
commit
c4be1e9841
1 changed files with 21 additions and 5 deletions
|
|
@ -2578,8 +2578,23 @@ window.marked = marked;
|
||||||
+ 'this is RECURRING — sends an extra pulse out-of-band. '
|
+ 'this is RECURRING — sends an extra pulse out-of-band. '
|
||||||
+ 'the regular cadence keeps firing on schedule.';
|
+ 'the regular cadence keeps firing on schedule.';
|
||||||
if (!confirm(prompt)) return;
|
if (!confirm(prompt)) return;
|
||||||
const originalLabel = btn ? btn.innerHTML : '';
|
// Capture child nodes so we can restore on error, then replace
|
||||||
if (btn) { btn.disabled = true; btn.innerHTML = '<span class="spinner">◐</span> firing…'; }
|
// with DOM-built content (textContent + element children rather
|
||||||
|
// than innerHTML — per argus's review note on #471, the format
|
||||||
|
// string only carries server-side ints/bool today but textContent
|
||||||
|
// is the safer pattern if a stringy field ever lands).
|
||||||
|
const originalChildren = btn ? Array.from(btn.childNodes) : [];
|
||||||
|
const restoreBtn = () => {
|
||||||
|
if (!btn) return;
|
||||||
|
btn.disabled = false;
|
||||||
|
while (btn.firstChild) btn.removeChild(btn.firstChild);
|
||||||
|
for (const n of originalChildren) btn.appendChild(n);
|
||||||
|
};
|
||||||
|
if (btn) {
|
||||||
|
btn.disabled = true;
|
||||||
|
while (btn.firstChild) btn.removeChild(btn.firstChild);
|
||||||
|
btn.append(el('span', { class: 'spinner' }, '◐'), ' firing…');
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const resp = await fetch('/api/schedules/' + encodeURIComponent(id) + '/fire-now', {
|
const resp = await fetch('/api/schedules/' + encodeURIComponent(id) + '/fire-now', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
@ -2588,7 +2603,7 @@ window.marked = marked;
|
||||||
if (!resp.ok) {
|
if (!resp.ok) {
|
||||||
const text = await resp.text().catch(() => '');
|
const text = await resp.text().catch(() => '');
|
||||||
alert('fire-now failed: http ' + resp.status + (text ? '\n\n' + text : ''));
|
alert('fire-now failed: http ' + resp.status + (text ? '\n\n' + text : ''));
|
||||||
if (btn) { btn.disabled = false; btn.innerHTML = originalLabel; }
|
restoreBtn();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Backend returns FireNowReport { ok, failed, missing, one_shot_consumed }.
|
// Backend returns FireNowReport { ok, failed, missing, one_shot_consumed }.
|
||||||
|
|
@ -2603,7 +2618,8 @@ window.marked = marked;
|
||||||
if (report.failed) bits.push(report.failed + ' failed');
|
if (report.failed) bits.push(report.failed + ' failed');
|
||||||
if (report.missing) bits.push(report.missing + ' missing');
|
if (report.missing) bits.push(report.missing + ' missing');
|
||||||
const suffix = report.one_shot_consumed ? ' — consumed' : '';
|
const suffix = report.one_shot_consumed ? ' — consumed' : '';
|
||||||
btn.innerHTML = '↯ fired: ' + (bits.join(', ') || 'no targets') + suffix;
|
while (btn.firstChild) btn.removeChild(btn.firstChild);
|
||||||
|
btn.textContent = '↯ fired: ' + (bits.join(', ') || 'no targets') + suffix;
|
||||||
btn.classList.add('btn-fire-now-flashed');
|
btn.classList.add('btn-fire-now-flashed');
|
||||||
}
|
}
|
||||||
// Hold the flash briefly so the operator can read it before the
|
// Hold the flash briefly so the operator can read it before the
|
||||||
|
|
@ -2611,7 +2627,7 @@ window.marked = marked;
|
||||||
setTimeout(() => { refreshSchedules(); }, 1500);
|
setTimeout(() => { refreshSchedules(); }, 1500);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert('fire-now failed: ' + err);
|
alert('fire-now failed: ' + err);
|
||||||
if (btn) { btn.disabled = false; btn.innerHTML = originalLabel; }
|
restoreBtn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function cancelScheduleAll(id) {
|
async function cancelScheduleAll(id) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue