Files
FabledSteward/steward/templates/hosts/list.html
T
bvandeusenandClaude Opus 5.5 bfe3618e88
CI / lint (push) Successful in 3s
CI / integration (push) Successful in 53s
CI / unit (push) Successful in 1m16s
CI / publish (push) Successful in 1m23s
feat(hosts): the Hosts list labels its figures and says how long; SLA tab gone; one place to add a host (#5279–#5283)
Hosts list:
- CPU and Memory are their own labelled columns (#5279); a silent
  agent's last readings are greyed, and Status says "agent silent for 7m".
- One uptime figure, 30 days, with 24h and 7d on hover. The SLA tab
  (/hosts/uptime) repeated it and is removed (#5280; one home per fact).
- Status is a word with how long ("Down for 12m"); host_status now carries
  down_since. Edit and Delete live on the host's page only (#5281).
- Figures are right-aligned with tabular digits (.table .num), not a
  monospace face.

Agent fleet (#5282): its add-host form is gone, so hosts are added only on
Hosts -> Add host. That form was also the only way to get a token for a
manual curl install, so that moves to the host's Agent card as "Install
manually" (POST /fleet/<host_id>/register), shown on the fleet page as
before. "Delete" there reads "Unregister".

Add host (#5283): laid out like the add-monitor form; a name clash comes
back inline with what was typed instead of a bare text 400; the address
is optional, as the host page already allowed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 17:12:17 -04:00

84 lines
4.8 KiB
HTML

{% extends "base.html" %}
{% from "_macros.html" import status_badge %}
{# Hosts, problems first (host_svc.host_rank): a down monitor, then an agent
gone quiet, then monitors not yet checked, then the healthy. Status says
what is wrong and for how long; editing and deleting a host happen on its
own page (#5281). Uptime is one figure, 30 days, with the shorter windows
on hover: Monitors is where uptime is read in full (#5280). #}
{% macro pct(v, kind, stale) -%}
{%- if v is none %}<span class="tone-dim">—</span>
{%- else %}<span class="tone-{{ 'dim' if stale else status_class(v, kind) }}">{{ '%.0f'|format(v) }}%</span>{% endif -%}
{%- endmacro %}
{% block title %}Hosts — Steward{% endblock %}
{% block content %}
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1.5rem;">
<h1 class="page-title" style="margin-bottom:0;">Hosts</h1>
<div style="display:flex;gap:0.5rem;">
{% if session.user_role == 'admin' %}
<a class="btn btn-ghost" href="/plugins/host_agent/fleet/">Agent fleet</a>
{% endif %}
<a class="btn" href="/hosts/new">Add host</a>
</div>
</div>
{% if hosts %}
<div class="card-flush">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Address</th>
<th title="The worst latest result across this host's monitors, and the agent if it has gone quiet">Status</th>
<th class="num" title="Fastest latest response among this host's monitors">Latency</th>
<th class="num" title="Monitors with a result">Monitors</th>
<th class="num" title="Uptime over 30 days across this host's monitors; hover for 24h and 7d">30d uptime</th>
<th class="num" title="Agent: CPU in use, latest reading">CPU</th>
<th class="num" title="Agent: memory in use, latest reading">Memory</th>
</tr>
</thead>
<tbody>
{% for host in hosts %}
{% set hs = host_status.get(host.id) %}
{% set ut = uptime.get(host.id, {}) %}
{% set am = agent.get(host.name) %}
{% set silent = am and not am.fresh %}
<tr>
<td><a href="/hosts/{{ host.id }}" style="font-weight:500;">{{ host.name }}</a></td>
<td class="tone-muted">{{ host.address or "—" }}</td>
<td>
{% if hs %}
{{ status_badge(hs.state) }}
{%- if hs.state == 'down' and hs.down_since %} <span class="tone-dim">for {{ hs.down_since|ago|striptags|replace(" ago", "") }}</span>{% endif %}
{% else %}
<span class="tone-dim" title="No monitors with a result">—</span>
{% endif %}
{% if silent %}
<div style="margin-top:0.15rem;">
{{ status_badge('stale', label="agent silent", title="No agent report since " ~ (am.last_at.strftime('%Y-%m-%d %H:%M UTC') if am.last_at else 'it started')) }}
{%- if am.last_at %} <span class="tone-dim">for {{ am.last_at|ago|striptags|replace(" ago", "") }}</span>{% endif %}
</div>
{% endif %}
</td>
<td class="num">
{% if hs and hs.latency_ms is not none %}
<span class="tone-{{ status_class(hs.latency_ms, 'latency') }}">{{ hs.latency_ms | round(1) }} ms</span>
{% else %}<span class="tone-dim">—</span>{% endif %}
</td>
<td class="num tone-muted">{{ hs.count if hs else "—" }}</td>
<td class="num" title="24h {{ ('%.2f%%'|format(ut['24h'])) if ut.get('24h') is not none else '—' }} · 7d {{ ('%.2f%%'|format(ut['7d'])) if ut.get('7d') is not none else '—' }}">
{% if ut.get('30d') is none %}<span class="tone-dim">—</span>
{% else %}<span class="tone-{{ status_class(ut['30d'], 'uptime') }}">{{ "%.2f"|format(ut['30d']) }}%</span>{% endif %}
</td>
<td class="num"{% if silent %} title="Last reading before the agent went quiet"{% endif %}>{% if am %}{{ pct(am.cpu_pct, 'cpu', silent) }}{% else %}<span class="tone-dim" title="No agent on this host">—</span>{% endif %}</td>
<td class="num"{% if silent %} title="Last reading before the agent went quiet"{% endif %}>{% if am %}{{ pct(am.mem_used_pct, 'mem', silent) }}{% else %}<span class="tone-dim" title="No agent on this host">—</span>{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="card">
<p style="color:var(--text-muted);">No hosts yet. <a href="/hosts/new">Summon one to the table.</a></p>
</div>
{% endif %}
{% endblock %}