feat(alerts): a covering rule's hosts fold away behind its "N hosts" (#5483)
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m7s
CI / unit (push) Successful in 1m26s
CI / publish (push) Successful in 1m9s

#5483 "Alerts list: a covering rule's hosts start collapsed behind a toggle;
no dashed rows", from the operator's review of the Alerts list. Each rule is
its own tbody; its hosts sit in another, hidden until "N hosts" is pressed.
A rule with a host that isn't quiet opens on its own and names the host where
"last fired" was, so a problem never hides behind the fold. Host rows lose the
dashed rules for a sunken band under the indent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 10:40:17 -04:00
co-authored by Claude Opus 5.5
parent 731e7cd6e6
commit a642b4c37c
4 changed files with 55 additions and 11 deletions
+25 -7
View File
@@ -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 @@
<th></th>
</tr>
</thead>
{% for rule in rules %}
{% set kids = (children or {}).get(rule.id, []) %}
{% set fired = (last_fired or {}).get(rule.id) %}
{% set restless = (uneasy or {}).get(rule.id, []) %}
<tbody>
{% for rule in rules %}
{% set kids = (children or {}).get(rule.id, []) %}
{% set fired = (last_fired or {}).get(rule.id) %}
<tr class="row-link{% if not rule.enabled %} row-off{% endif %}">
<td class="cell-name">
<a class="row-target" href="{{ ('/alerts/rules/' ~ rule.id ~ '/edit') if can_edit else ('/alerts/history?rule=' ~ rule.id) }}" title="{{ rule.name }}">{{ rule.name }}</a>
<div class="cell-sub">{{ source_label(rule.source_module) }}
{%- if rule.covers != "resource" %} · {{ kids|length }} host{{ "s" if kids|length != 1 }}{% endif %}</div>
{%- if rule.covers != "resource" %} · <button type="button" class="row-toggle row-inner"
aria-expanded="{{ 'true' if restless else 'false' }}" aria-controls="hosts-{{ rule.id }}"
onclick="toggleHosts(this)">{{ kids|length }} host{{ "s" if kids|length != 1 }}</button>{% endif %}</div>
</td>
<td>
{%- set now_label = (rule_state or {}).get(rule.id) %}
{%- if not rule.enabled %}<span class="tone-dim">off</span>
{%- elif now_label and now_label not in ('inactive', 'resolved') %}{{ status_badge(now_label) }}
{%- else %}<span class="tone-ok" title="Within its threshold">quiet</span>{% endif -%}
<div class="cell-sub">{% if fired %}last fired {{ fired|ago }}{% else %}never fired{% endif %}</div>
<div class="cell-sub">
{%- if restless %}on {{ restless|join(", ") }}
{%- elif fired %}last fired {{ fired|ago }}{% else %}never fired{% endif %}</div>
</td>
<td>
{% if rule.covers == "group" %}every host in {{ rule.cover_group }}{% elif rule.covers == "all_hosts" %}every host{% else %}{{ rule.resource_name }}{% endif %} · {{ rule_condition(rule) }}
@@ -71,6 +77,9 @@
{% endif %}
</td>
</tr>
</tbody>
{% if kids %}
<tbody id="hosts-{{ rule.id }}" class="row-children"{% if not restless %} hidden{% endif %}>
{% for kid in kids %}
{% set kid_label = (rule_state or {}).get(kid.id) %}
{% set kid_fired = (last_fired or {}).get(kid.id) %}
@@ -88,10 +97,19 @@
<td class="td-actions"><a href="/alerts/history?rule={{ kid.id }}" class="btn btn-sm btn-ghost">History</a></td>
</tr>
{% endfor %}
{% endfor %}
</tbody>
{% endif %}
{% endfor %}
</table>
</div>
<script>
// A covering rule's hosts (#5483): closed unless one of them isn't quiet.
function toggleHosts(btn) {
var open = btn.getAttribute('aria-expanded') !== 'true';
btn.setAttribute('aria-expanded', open ? 'true' : 'false');
document.getElementById(btn.getAttribute('aria-controls')).hidden = !open;
}
</script>
{% else %}
<div class="card">{{ empty_state("No alert rules yet.", "New rule" if can_edit else none, "/alerts/rules/new" if can_edit else none) }}</div>
{% endif %}