feat(ui): status_badge, status_dot, stat_tile, empty_state, page_header, icon_button and form classes (#5094)

#5094 "Status and layout macros, and form classes".

- formatting.STATES: one state -> (tone, label) map for up, down, stale,
  firing, pending, acknowledged, no_data, ok, warn, crit, resolved and
  inactive; status_info() reads it (an unmapped state is drawn dim under its
  own name). required() lets a macro refuse a missing argument.
- _macros.html: status_badge and status_dot (a dot always carries its label,
  sr-only unless shown), stat_tile, empty_state, page_header (actions via a
  call block) and icon_button, which raises without a label.
- steward.css: tone classes (badge-/dot-/stat- ok, warn, crit, info, dim),
  .sr-only, .page-header, .stat-tile, .empty-state, .btn-icon and the form
  classes .field, .field-label, .field-hint and .input. Info text is a 40%
  mix (--blue): the house --fs-info-fg falls to 4.18:1 on its tint over a
  hovered row, 40% holds 5.09:1.
- First users: the Alerts list, history and maintenance pages (headers,
  state badges, empty states) and the alert forms' hints; the inline inp/lbl
  style sets on the monitor and host forms become .input / .field-label.
- tests/core/test_macros.py renders each macro through the app environment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 14:34:21 -04:00
co-authored by Claude Opus 5.5
parent f47fc23265
commit 477b05b9d0
16 changed files with 296 additions and 117 deletions
+4 -7
View File
@@ -1,6 +1,5 @@
{# alerts/_history_rows.html — the history table; swapped in on a range or rule change #}
{% set state_badge = {"firing": "badge-red", "pending": "badge-yellow", "acknowledged": "badge-gold",
"resolved": "badge-green", "inactive": "badge-dim"} %}
{% from "_macros.html" import status_badge, empty_state %}
{% if events %}
<div class="card-flush" style="overflow-x:auto;">
<table class="table">
@@ -25,9 +24,9 @@
<div style="font-size:0.78rem;color:var(--text-dim);">{{ ev.rule.source_module }}/{{ ev.rule.resource_name }}</div>
</td>
<td style="white-space:nowrap;">
<span class="badge {{ state_badge.get(ev.from_state, 'badge-dim') }}">{{ ev.from_state }}</span>
{{ status_badge(ev.from_state) }}
<span style="color:var(--text-dim);">→</span>
<span class="badge {{ state_badge.get(ev.to_state, 'badge-dim') }}">{{ ev.to_state }}</span>
{{ status_badge(ev.to_state) }}
{% for n in ev.notes %}
<div style="font-size:0.78rem;color:var(--text-muted);margin-top:0.25rem;white-space:normal;">{{ n }}</div>
{% endfor %}
@@ -69,7 +68,5 @@
</p>
{% endif %}
{% else %}
<p style="color:var(--text-muted);">
No alert changed state in the last {{ current_range }}{% if chosen %} for {{ chosen.name }}{% endif %}.
</p>
<div class="card">{{ empty_state("No alert changed state in the last " ~ current_range ~ ((" for " ~ chosen.name) if chosen else "") ~ ".") }}</div>
{% endif %}