diff --git a/steward/alerts/routes.py b/steward/alerts/routes.py index d0107aa..39786b7 100644 --- a/steward/alerts/routes.py +++ b/steward/alerts/routes.py @@ -139,6 +139,7 @@ async def list_alerts(): # pressing of their states. rule_state = {r.id: alert_svc.alert_label(states[r.id]) for r in rules if r.id in states} children: dict[str, list] = {} + uneasy: dict[str, list[str]] = {} for r in rules: if r.parent_id: children.setdefault(r.parent_id, []).append(r) @@ -149,11 +150,15 @@ async def list_alerts(): rule_state[parent_id] = min( (lb for lb in labels if lb and lb not in ("inactive", "resolved")), key=lambda lb: alert_svc.ACTIVE_ORDER.get(lb, 9), default="inactive") + # The hosts that aren't quiet: named on the rule's row, and they keep + # its hosts open (#5483). + uneasy[parent_id] = [k.resource_name for k, lb in zip(kids, labels) + if k.enabled and lb and lb not in ("inactive", "resolved")] fired[parent_id] = max((fired[k.id] for k in kids if k.id in fired), default=None) rules = sorted((r for r in rules if not r.parent_id), key=lambda r: (not r.enabled, r.name.lower())) return await render_template( - "alerts/list.html", active=active, rules=rules, children=children, + "alerts/list.html", active=active, rules=rules, children=children, uneasy=uneasy, rule_state=rule_state, last_fired={k: v for k, v in fired.items() if v}, notify=await _notify(), operators=_OPERATORS, poll_interval=current_app.config.get("MONITORS_POLL_INTERVAL", 60)) diff --git a/steward/static/steward.css b/steward/static/steward.css index 336e8c4..ea1841b 100644 --- a/steward/static/steward.css +++ b/steward/static/steward.css @@ -186,7 +186,10 @@ strong, b, th { font-weight: 500; } .table { width: 100%; border-collapse: collapse; } .table th { text-align: left; padding: 0.6rem 1rem; color: var(--text-muted); font-weight: 500; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em; border-bottom: 1px solid var(--border-mid); } .table td { padding: 0.7rem 1rem; border-bottom: 1px solid var(--border); font-size: 0.9rem; } -.table tbody tr:last-child td { border-bottom: none; } +/* The table's last row has no rule under it; a table may have several tbodys + (the Alerts list: one per rule, its hosts in another, maybe hidden). */ +.table tbody:last-child tr:last-child td, +.table tbody:has(+ tbody[hidden]:last-child) tr:last-child td { border-bottom: none; } .table tbody tr:hover td { background: var(--bg-elevated); } .table .td-actions { text-align: right; white-space: nowrap; } /* A header that sorts its column (sort_th in _macros.html): reads as the @@ -216,9 +219,17 @@ strong, b, th { font-weight: 500; } /* The Docker page's one-line answer, above its tiles (#5298). */ .page-verdict { margin: 0 0 0.75rem; font-size: 0.95rem; } .table tr.row-off > td { opacity: 0.55; } -/* A covering alert rule's per-host rules (#5464), indented under it. */ -.table tr.row-child > td { border-top-style: dashed; } +/* A covering alert rule's per-host rules (#5464), indented on a sunken band + under it, opened by its "N hosts" toggle (#5483). */ +.table tr.row-child:not(:hover) > td { background: var(--bg); } .table tr.row-child .cell-name { padding-left: 2rem; } +.row-toggle { background: none; border: 0; padding: 0; font: inherit; color: var(--text-muted); + cursor: pointer; text-decoration: underline dotted; text-underline-offset: 2px; } +.row-toggle::before { content: "▸"; display: inline-block; margin-right: 0.25rem; + transition: transform var(--fs-dur-fast) var(--fs-ease); } +.row-toggle[aria-expanded="true"]::before { transform: rotate(90deg); } +.row-toggle:hover { color: var(--text); } +.row-toggle:focus-visible { outline: none; box-shadow: var(--fs-focus-ring); border-radius: var(--fs-radius-sm); } /* The rule form's metric preview (#5475): now, then the last day beside its sparkline. */ .metric-preview { display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem 1rem; margin-top: 0.4rem; font-size: 0.85rem; } diff --git a/steward/templates/alerts/list.html b/steward/templates/alerts/list.html index 715b74b..338235c 100644 --- a/steward/templates/alerts/list.html +++ b/steward/templates/alerts/list.html @@ -3,7 +3,8 @@ when it last fired, what it fires on, and its History and Disable. The whole row opens the rule (.row-link, its name is the link); deleting one is on its own page, behind the confirm dialog. A rule covering hosts (#5464) - lists its per-host rules under it; each opens its history. #} + lists its per-host rules under it, closed behind its "N hosts" toggle + unless one of them isn't quiet (#5483); each opens its history. #} {% extends "base.html" %} {% block title %}Alerts — Steward{% endblock %} {% from "_macros.html" import page_header, status_badge, empty_state %} @@ -35,22 +36,27 @@