Files
FabledSteward/steward/templates/monitors/rows.html
T
bvandeusenandClaude Opus 5.5 7c23360bc7
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m18s
CI / unit (push) Successful in 1m25s
CI / publish (push) Successful in 1m11s
fix(norms): templates grade by the cutoffs when handed no grade; the parts test reads inside transactions (#5742)
CI 9128: the host vitals and monitor templates required the new grades, so
callers (and tests) that render them without one failed; they now fall back
to status_class. The new integration test read outside begin(), which
autobegan the session.

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

90 lines
5.0 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{# HTMX fragment — the Monitors list: counts, then every monitor, problems first
(service.problems_first), under one header: the last 30 checks, latest
response with its trend, uptime over 24h / 7d / 30d and TLS days. This is
the one home for monitor state; the Status page it replaced is gone (#5270).
Colours come from Settings → Thresholds. #}
{% from "_macros.html" import stat_tile, status_badge, status_dot, icon_button, empty_state %}
<div class="stat-strip">
{{ stat_tile("Down", counts.down, state="down" if counts.down else "inactive") }}
{{ stat_tile("Up", counts.up, state="up" if counts.up else "inactive",
hint=(counts.degraded ~ " degraded") if counts.degraded else none) }}
{{ stat_tile("Pending", counts.pending, state="pending" if counts.pending else "inactive") }}
{{ stat_tile("Paused", counts.paused, state="paused") }}
</div>
{% if monitor_data %}
{% set can_edit = session.user_role in ['operator', 'admin'] %}
{# TLS only when some monitor has a certificate: a column of dashes says nothing. #}
{% set any_tls = monitor_data | selectattr("tls_days", "ne", none) | list | length > 0 %}
<div class="card ping-card">
<div class="ping-row mon-head" aria-hidden="true">
<span class="ping-meta mon-col-name">Monitor</span>
<span class="ping-pills mon-pills">Last 30 checks</span>
<span class="mon-latest">Latest</span>
<span class="mon-up">24h</span><span class="mon-up">7d</span><span class="mon-up">30d</span>
{% if any_tls %}<span class="mon-tls">TLS</span>{% endif %}
{% if can_edit %}<span class="mon-act"></span>{% endif %}
</div>
{% for d in monitor_data %}
{% set m = d.monitor %}
{% set latest = d.latest %}
<div class="ping-row{% if d.state == 'paused' %} ping-row-paused{% endif %}">
<div class="ping-meta mon-col-name">
<div style="display:flex;align-items:baseline;gap:0.4rem;">
{{ status_dot(d.state) }}
<a class="ping-name" href="/monitors/{{ m.id }}">{{ m.name }}</a>
</div>
<span class="ping-addr" title="{{ m.target }}">{{ m.type|monitor_type }} · {{ m.target }}</span>
{% if d.state == 'down' %}
<span class="tone-crit" style="font-size:0.75rem;overflow-wrap:anywhere;">
Down{% if d.down_since %} {{ d.down_since|ago|replace(' ago', '') }}{% endif %}{% if d.down_reason %} · {{ d.down_reason }}{% endif %}
</span>
{% endif %}
</div>
<div class="ping-pills mon-pills">
{% for _ in range([30 - d.heartbeat|length, 0]|max) %}<span class="pill pill-empty" title="No data"></span>{% endfor %}
{% for h in d.heartbeat %}<span class="pill pill-{{ h.state }}" title="{{ h.title }}"></span>{% endfor %}
</div>
<div class="mon-latest">
{% if d.state in ('down', 'paused', 'pending') %}{{ status_badge(d.state) }}
{% elif latest.response_ms is not none %}
{% if d.spark %}<span class="mon-spark" title="Response time over the last 30 checks">{{ d.spark|safe }}</span>{% endif %}
<span class="tone-{{ d.latency or status_class(latest.response_ms, 'latency') }}"{% if d.latency_usual %} title="{{ d.latency_usual|capitalize }}"{% endif %}>{{ "%.0f"|format(latest.response_ms) }} ms</span>
{% elif m.type == 'dns' and latest.resolved_ip %}<span class="tone-ok">{{ latest.resolved_ip }}</span>
{% else %}{{ status_badge('up') }}{% endif %}
</div>
{% for k in ("24h", "7d", "30d") %}{% set u = d.uptime.get(k) %}
<div class="mon-up">{% if u is none %}<span class="tone-unknown">—</span>{% else %}<span class="tone-{{ status_class(u, 'uptime') }}">{{ "%.2f"|format(u) }}%</span>{% endif %}</div>
{% endfor %}
{% if any_tls %}
<div class="mon-tls">
{% if d.tls_days is none %}<span class="tone-unknown">—</span>
{% elif d.tls_days < 0 %}<span class="tone-crit" title="Certificate expired">expired</span>
{% else %}<span class="tone-{{ status_class(d.tls_days, 'tls_days') }}" title="TLS certificate expires in {{ d.tls_days|round|int }} days">{{ d.tls_days|round|int }}d</span>{% endif %}
</div>
{% endif %}
{% if can_edit %}
<div class="row-actions mon-act">
{{ icon_button("✎", "Edit " ~ m.name, href="/monitors/" ~ m.id ~ "/edit") }}
<form method="post" action="/monitors/{{ m.id }}/toggle">{{ csrf_field() }}
{{ icon_button("⏸" if m.enabled else "▶", ("Pause " if m.enabled else "Resume ") ~ m.name, type="submit") }}
</form>
<form method="post" action="/monitors/{{ m.id }}/delete">{{ csrf_field() }}
{{ icon_button("×", "Delete " ~ m.name, type="submit", confirm="Delete monitor " ~ m.name ~ "?") }}
</form>
</div>
{% endif %}
</div>
{% endfor %}
</div>
<p class="field-hint" style="margin-top:0.6rem;">Last 30 checks: newest on the right; hover one for its result. Uptime is over the rolling window.{% if any_tls %} TLS is days until the certificate expires (HTTPS monitors).{% endif %}</p>
{% else %}
<div class="card">{{ empty_state("No monitors yet. Add one, or attach checks to a host from its page.",
"Add monitor" if session.user_role in ['operator', 'admin'] else none, "/monitors/new") }}</div>
{% endif %}