Files
FabledSteward/steward/templates/monitors/rows.html
T
bvandeusen 35f658b573
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 8s
CI / integration (push) Successful in 2m17s
CI / publish (push) Successful in 1m10s
feat(monitors): unify ping/dns/http into one Monitor entity + custom targets
Collapse the three former check types into a single core `Monitor` entity
with one management surface (/monitors), one result table (monitor_results),
and a single scheduled task. Every type can now watch a free-standing custom
destination (optional host_id) — not just a registered Host.

- models: Monitor + MonitorResult replace PingResult/DnsResult; Host loses its
  ping/dns facet columns (now Monitor rows linked by host_id).
- checks: monitors/{ping,dns,http}.py pure probes + runner.run_monitor
  dispatcher; one monitor_check scheduler with a per-monitor due-filter.
- status: single monitor_status_source replaces the three sources.
- UI: /monitors blueprint (type-aware add/edit/list/widget); host hub shows a
  host's linked monitors + "add monitor for this host"; nav + widget registry
  + alert metric catalog rewired. http plugin folded into core and removed.
- migration 0022 merges the http branch, data-migrates host facets +
  http_monitors + all three result histories, drops the old tables/columns.

Resolves the per-host ping/dns auto-attach issue (#275): monitors are now
explicit, never auto-added to every host.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Jg27rgypiW2efULXJDtMC
2026-06-18 08:56:13 -04:00

75 lines
3.4 KiB
HTML

{# HTMX fragment — monitor rows (list page). #}
{% if monitor_data %}
{% for d in monitor_data %}
{% set m = d.monitor %}
{% set latest = d.latest %}
<div class="ping-row" {% if not m.enabled %}style="opacity:0.5;"{% endif %}>
<div class="ping-meta" style="min-width:160px;max-width:240px;">
<span class="badge" style="font-size:0.62rem;vertical-align:middle;">{{ m.type | upper }}</span>
{% if m.host_id %}
<a class="ping-name" href="/hosts/{{ m.host_id }}" title="{{ m.name }}"
style="color:inherit;text-decoration:none;">{{ m.name }}</a>
{% else %}
<span class="ping-name" title="{{ m.name }}">{{ m.name }}</span>
{% endif %}
<span class="ping-addr" title="{{ m.target }}">{{ m.target }}</span>
</div>
<div class="ping-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" title="{{ h.title }}"
style="background:{{ '#1a6632' if h.state == 'up' else '#6a1515' }};"></span>
{% endfor %}
</div>
<div class="ping-cur">
{% if latest is none %}
<span class="ping-cur-nd"></span>
{% elif not latest.is_up %}
<span class="ping-cur-down">DOWN</span>
{% elif latest.response_ms is not none %}
<span style="{{ threshold_style(latest.response_ms, 'latency') or 'color:var(--green);' }}">
{{ "%.0f"|format(latest.response_ms) }} ms</span>
{% elif m.type == 'dns' and latest.resolved_ip %}
<span class="ping-cur-good" style="font-size:0.72rem;">{{ latest.resolved_ip }}</span>
{% else %}
<span class="ping-cur-good">UP</span>
{% endif %}
</div>
<div style="min-width:4.5rem;text-align:right;font-size:0.78rem;font-variant-numeric:tabular-nums;{{ threshold_style(d.uptime_pct, 'uptime') if d.uptime_pct is not none else 'color:var(--text-dim);' }}">
{% if d.uptime_pct is not none %}{{ "%.1f"|format(d.uptime_pct) }}%{% else %}—{% endif %}
<span style="color:var(--text-dim);font-size:0.7rem;margin-left:0.15rem;">{{ range_key }}</span>
</div>
{% if d.tls_days is not none %}
<div title="TLS certificate expiry" style="min-width:4rem;text-align:right;font-size:0.72rem;{{ 'color:var(--red);' if d.tls_days < 14 else ('color:var(--yellow);' if d.tls_days < 30 else 'color:var(--text-dim);') }}">
cert {{ d.tls_days|round|int }}d
</div>
{% endif %}
{% if session.user_role in ['operator', 'admin'] %}
<div style="display:flex;gap:0.35rem;flex-shrink:0;">
<a href="/monitors/{{ m.id }}/edit" class="btn btn-sm" style="padding:0.2rem 0.5rem;font-size:0.72rem;">Edit</a>
<form method="post" action="/monitors/{{ m.id }}/toggle" style="display:inline;">
<button type="submit" class="btn btn-sm" style="padding:0.2rem 0.5rem;font-size:0.72rem;">
{{ "Pause" if m.enabled else "Resume" }}</button>
</form>
<form method="post" action="/monitors/{{ m.id }}/delete" style="display:inline;"
onsubmit="return confirm('Delete monitor {{ m.name }}?');">
<button type="submit" class="btn btn-sm" style="padding:0.2rem 0.5rem;font-size:0.72rem;color:var(--red);"></button>
</form>
</div>
{% endif %}
</div>
{% endfor %}
{% else %}
<p style="color:var(--text-muted);font-size:0.9rem;padding:1rem 0;">
No monitors yet. Add one above, or attach checks to a host from the
<a href="/hosts/" style="color:var(--accent);">Hosts</a> page.
</p>
{% endif %}