Files
FabledSteward/steward/templates/alerts/list.html
T
bvandeusenandClaude Opus 5.5 dc22a35e9d
CI / lint (push) Successful in 3s
CI / integration (push) Successful in 50s
CI / unit (push) Successful in 1m7s
CI / publish (push) Successful in 1m4s
feat(alerts): alert history page — transitions, deliveries, mutes and actions (#5066)
#5066 "Alert history page". /alerts/history lists every alert transition
in a range (1h–30d, the shared range picker), newest first, all rules or
one (?rule=). Each row: from → to, the value against the rule's condition
("no data" for a silent one), email and webhook sent or failed with the
error, "muted · <window>" (and "notified when the window ended") taken out
of the event note, other notes, and a link to the Ansible run an action
started. Range and rule changes swap only the rows (HTMX). Viewer role.

The Alerts page links each rule's history and the whole history. The query
moves to alert_svc.history, which MCP get_alert_history now uses too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 14:06:04 -04:00

114 lines
5.1 KiB
HTML

{% extends "base.html" %}
{% block title %}Alerts — Steward{% endblock %}
{% block content %}
<h1 class="page-title">Alerts</h1>
{% if active %}
<h2 class="section-title" style="margin-bottom:1rem;">Active</h2>
<div class="card-flush" style="margin-bottom:2rem;">
<table class="table">
<thead>
<tr>
<th>State</th>
<th>Rule</th>
<th>Resource</th>
<th>Metric</th>
<th></th>
</tr>
</thead>
<tbody>
{% for state, rule in active %}
<tr>
<td>
{% if state.state.value == 'firing' %}
<span class="badge badge-red">FIRING</span>
{% elif state.state.value == 'acknowledged' %}
<span class="badge badge-yellow">ACK</span>
{% elif state.state.value == 'pending' %}
<span class="badge badge-dim">PENDING</span>
{% endif %}
{% if state.no_data_since %}
<span class="badge badge-yellow"
title="No sample since {{ state.last_sample_at.strftime('%Y-%m-%d %H:%M UTC') if state.last_sample_at else 'the rule started watching' }}">NO DATA</span>
<div style="font-size:0.72rem;color:var(--text-dim);margin-top:0.2rem;">
since {{ state.no_data_since.strftime("%Y-%m-%d %H:%M") }} UTC
</div>
{% endif %}
</td>
<td>{{ rule.name }}</td>
<td style="color:var(--text-muted);">{{ rule.resource_name }}</td>
<td style="color:var(--text-muted);">{{ rule.metric_name }} {{ rule.operator.value }} {{ rule.threshold }}</td>
<td class="td-actions">
{% if state.state.value == 'firing' %}
<form method="post" action="/alerts/{{ rule.id }}/acknowledge" style="display:inline;">{{ csrf_field() }}
<button type="submit" class="btn btn-sm btn-warn">Acknowledge</button>
</form>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p style="color:var(--text-muted);margin-bottom:2rem;">No active alerts.</p>
{% endif %}
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem;">
<h2 class="section-title">Rules</h2>
<div style="display:flex;gap:0.5rem;">
<a class="btn btn-ghost" href="/alerts/history">History</a>
<a class="btn btn-ghost" href="/alerts/maintenance">Maintenance</a>
<a class="btn" href="/alerts/rules/new">New Rule</a>
</div>
</div>
{% if rules %}
<div class="card-flush">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Condition</th>
<th style="text-align:center;" title="Checks in a row to fire / to clear">Fire / clear</th>
<th></th>
</tr>
</thead>
<tbody>
{% for rule in rules %}
<tr {% if not rule.enabled %}style="opacity:0.55;"{% endif %}>
<td>
{{ rule.name }}
{% if not rule.enabled %}
<span style="font-size:0.72rem;color:var(--text-dim);margin-left:0.35rem;">disabled</span>
{% endif %}
</td>
<td style="color:var(--text-muted);font-size:0.85rem;">
<span style="color:var(--text-dim);">{{ rule.source_module }}/</span>{{ rule.resource_name }}
<span style="font-family:ui-monospace,monospace;font-size:0.8rem;">
:{{ rule.metric_name }} {{ rule.operator.value }} {{ rule.threshold }}
</span>
</td>
<td style="color:var(--text-muted);text-align:center;">{{ rule.consecutive_failures_required }} / {{ rule.consecutive_ok_required }}</td>
<td class="td-actions">
<a href="/alerts/history?rule={{ rule.id }}" class="btn btn-sm btn-ghost">History</a>
<a href="/alerts/rules/{{ rule.id }}/edit" class="btn btn-sm btn-ghost">Edit</a>
<form method="post" action="/alerts/rules/{{ rule.id }}/toggle" style="display:inline;">{{ csrf_field() }}
<button type="submit" class="btn btn-sm btn-ghost">
{% if rule.enabled %}Disable{% else %}Enable{% endif %}
</button>
</form>
<form method="post" action="/alerts/rules/{{ rule.id }}/delete" style="display:inline;">{{ csrf_field() }}
<button type="submit" class="btn btn-sm btn-danger"
onclick="return confirm('Delete rule {{ rule.name }}?')">Delete</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p style="color:var(--text-muted);">No alert rules configured.</p>
{% endif %}
{% endblock %}