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>
This commit is contained in:
@@ -251,6 +251,34 @@ async def active_count(db) -> int:
|
||||
)).scalar_one()
|
||||
|
||||
|
||||
def notify_targets(smtp: dict, webhook: dict) -> list[str]:
|
||||
"""Where an alert's notifications go, in words, by the same tests
|
||||
core.notifications.dispatch_notifications sends by: email when a server
|
||||
and recipients are set, a webhook when its URL is. Empty: nobody (#5315)."""
|
||||
from urllib.parse import urlsplit
|
||||
out = []
|
||||
recipients = smtp.get("recipients") or []
|
||||
if isinstance(recipients, str):
|
||||
recipients = [r.strip() for r in recipients.split(",") if r.strip()]
|
||||
if smtp.get("host") and recipients:
|
||||
out.append("email to " + ", ".join(recipients))
|
||||
if url := webhook.get("url"):
|
||||
out.append("a webhook to " + (urlsplit(url).hostname or url))
|
||||
return out
|
||||
|
||||
|
||||
async def last_fired(db, rule_id: str | None = None) -> dict[str, datetime]:
|
||||
"""{rule id: when it last went to firing}, for the rules that ever have;
|
||||
one rule's when `rule_id` is given."""
|
||||
stmt = (select(AlertEvent.rule_id, func.max(AlertEvent.transitioned_at))
|
||||
.where(AlertEvent.to_state == AlertStateEnum.firing.value)
|
||||
.group_by(AlertEvent.rule_id))
|
||||
if rule_id:
|
||||
stmt = stmt.where(AlertEvent.rule_id == rule_id)
|
||||
rows = await db.execute(stmt)
|
||||
return {rid: at for rid, at in rows.all()}
|
||||
|
||||
|
||||
async def list_rules(db) -> list[AlertRule]:
|
||||
return list((await db.execute(select(AlertRule).order_by(AlertRule.name))).scalars())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user