feat(dashboard): an Alerts widget beside the Overview; the Overview's overflow links to Alerts and Monitors (#5510)
The Alerts widget (/alerts/widget) is shaped like every widget: a verdict
("2 firing, 1 silent." / "Nothing firing: all 12 rules are quiet."), then
what is firing or silent with its reading now, what the rule fires on and
since when, then what someone acknowledged and who; a line when alert
email failed in the last 7 days or is retrying (outbox.alert_trouble),
linking admins to Settings -> System; the quiet rules counted, a covering
rule once. It sits beside the Overview in the recommended layout (6x4
each).
The Overview's "+N more" line pointed at the removed /status/; alerts now
link to /alerts/ and monitors to /monitors/.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -178,6 +178,19 @@ async def active_fragment():
|
||||
rules_off=sum(1 for r in rules if not r.enabled))
|
||||
|
||||
|
||||
@alerts_bp.get("/widget")
|
||||
@require_role(UserRole.viewer)
|
||||
async def widget():
|
||||
"""The dashboard Alerts widget (#5510): a verdict, what is firing or
|
||||
silent, then what someone has taken, a line when alert email is failing,
|
||||
and the quiet rules counted."""
|
||||
async with current_app.db_sessionmaker() as db:
|
||||
active = await alert_svc.active_rows(db, datetime.now(timezone.utc))
|
||||
view = alert_svc.widget_view(active, await alert_svc.list_rules(db))
|
||||
email = await outbox.alert_trouble(db)
|
||||
return await render_template("alerts/widget.html", **view, email=email)
|
||||
|
||||
|
||||
def _delivery(label: str, sent: bool | None, error: str | None) -> dict | None:
|
||||
"""A channel's outcome as recorded on the event; None when not tried."""
|
||||
if sent is None:
|
||||
|
||||
@@ -231,11 +231,13 @@ async def active_rows(db, now: datetime) -> list[dict]:
|
||||
since = started.get(rule.id, state.last_transition_at)
|
||||
else:
|
||||
since = state.last_transition_at
|
||||
value = await latest_value(db, rule)
|
||||
rows.append({
|
||||
"state": state, "rule": rule, "label": label, "since": since,
|
||||
"breach_since": started.get(rule.id) if label != "no_data" else None,
|
||||
"acked_by": names.get(state.acknowledged_by, state.acknowledged_by),
|
||||
"says": alert_sentence(rule, label, await latest_value(db, rule), since, now),
|
||||
"value": value,
|
||||
"says": alert_sentence(rule, label, value, since, now),
|
||||
"url": await resource_url(db, rule),
|
||||
})
|
||||
epoch = datetime.max.replace(tzinfo=timezone.utc)
|
||||
@@ -243,6 +245,19 @@ async def active_rows(db, now: datetime) -> list[dict]:
|
||||
return rows
|
||||
|
||||
|
||||
def widget_view(active: list[dict], rules: list[AlertRule]) -> dict:
|
||||
"""The dashboard Alerts widget (#5510) from active_rows and every rule:
|
||||
the alerts in that order, each with what its rule fires on, and how many
|
||||
enabled rules, as people made them, are quiet (a covering rule counts
|
||||
once, and is quiet only while every host under it is).
|
||||
{"rows", "quiet", "total"}."""
|
||||
busy = {r["rule"].id for r in active}
|
||||
busy |= {r["rule"].parent_id for r in active if r["rule"].parent_id}
|
||||
made = [r for r in rules if r.enabled and not r.parent_id]
|
||||
return {"rows": [{**r, "condition": rule_condition(r["rule"])} for r in active],
|
||||
"quiet": sum(1 for r in made if r.id not in busy), "total": len(made)}
|
||||
|
||||
|
||||
def watches_host(rule: AlertRule, host_name: str, monitor_keys: set) -> bool:
|
||||
"""Whether a rule watches something on this host: its agent series
|
||||
("<host>" or "<host>:<part>"), one of its containers ("<host>/<name>"),
|
||||
|
||||
Reference in New Issue
Block a user