Files
bvandeusenandClaude Opus 5.5 0c66c1e7d3
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 55s
CI / unit (push) Failing after 1m25s
CI / publish (push) Skipped
feat(alerts): Steward's own confirm dialog, a verdict-first rules list, and a rule page in three sections (#5314, #5315, #5317)
Confirm dialog (#5314):
- Every data-confirm form now asks in one native <dialog> (base.html + static/confirm.js)
  instead of window.confirm.
- The question becomes the heading and what follows it the detail; the act's
  button is named after the form's own button; Cancel has the focus and Esc
  backs out. Optional data-confirm-title/-detail/-action/-dismiss attributes.
- All 27 existing uses work as they are. Cancelling a run says 'Keep running'.

Alerts list (#5315):
- A verdict line (what is firing, how many rules, where notifications go,
  or 'notifies nobody yet' with a link) replaces the empty Active card.
- Each rule's name opens it; rows show 'last fired' and fold fire/clear into
  the condition; History and Disable stay; Delete leaves the list.
- .docker-verdict becomes the shared .page-verdict.

Rule page (#5317):
- Named for the rule (title and crumb), with when it last fired and how many
  changes of state it holds.
- Three sections: what it watches, when it fires (checks side by side,
  seconds echoed as a duration), what happens (where notifications go, no-data,
  the playbook, whose fields stay folded until ticked).
- Delete at the foot, behind the dialog, saying its history goes with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 22:58:38 -04:00

52 lines
2.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Steward's own confirm dialog (#5314): a form, or its submit button, with
// data-confirm="Question? What follows." asks in the #confirm-dialog that
// base.html renders before it submits, never through window.confirm.
//
// The prompt is attribute text read through dataset and written with
// textContent, never parsed as HTML or JavaScript, so a name inside it
// cannot run as script (#5099). Optional attributes beside data-confirm:
// data-confirm-title the heading (default: the prompt up to its first "? ")
// data-confirm-detail the line under it (default: the rest of the prompt)
// data-confirm-action the button that goes ahead (default: the submit
// button's own label, or the prompt's first word)
// data-confirm-dismiss the button that backs out (default: "Cancel")
// Cancel has the focus, and Esc backs out.
(function () {
function label(btn, title) {
var own = btn ? (btn.dataset.confirmAction || btn.textContent || btn.value || '').trim() : '';
if (own.length > 2) return own; // "×" and other icons say nothing
return title.split(' ')[0].replace(/\?$/, '');
}
function ask(form, btn) {
var src = btn && btn.dataset.confirm ? btn : form;
var prompt = src.dataset.confirm;
var cut = prompt.indexOf('? ');
var title = src.dataset.confirmTitle || (cut > 0 ? prompt.slice(0, cut + 1) : prompt);
var detail = src.dataset.confirmDetail || (cut > 0 ? prompt.slice(cut + 2) : '');
var d = document.getElementById('confirm-dialog');
d.querySelector('[data-part="title"]').textContent = title;
var p = d.querySelector('[data-part="detail"]');
p.textContent = detail;
p.hidden = !detail;
d.querySelector('[data-part="go"]').textContent = src.dataset.confirmAction || label(btn, title);
d.querySelector('[data-part="back"]').textContent = src.dataset.confirmDismiss || 'Cancel';
d.returnValue = '';
d.onclose = function () {
if (d.returnValue !== 'go') return;
form.dataset.confirmed = '1';
form.requestSubmit(btn && btn.form === form ? btn : undefined);
};
d.showModal();
}
document.addEventListener('submit', function (ev) {
var form = ev.target, btn = ev.submitter;
if (!((btn && btn.dataset.confirm) || form.dataset.confirm)) return;
if (form.dataset.confirmed === '1') { delete form.dataset.confirmed; return; } // the asked-for submit
ev.preventDefault();
ev.stopPropagation(); // before HTMX sees it: an hx-post form waits for the answer too
ask(form, btn);
}, true);
})();