90 lines
4.9 KiB
HTML
90 lines
4.9 KiB
HTML
{# 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-{{ status_class(latest.response_ms, 'latency') }}">{{ "%.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 %}
|