feat(hosts): a host's health tiles span the card and the gaps match; Add host is a dialog on the list (#5290, #5291)
CI / lint (push) Successful in 3s
CI / integration (push) Successful in 54s
CI / unit (push) Successful in 1m18s
CI / publish (push) Successful in 1m23s

#5290 "Host page: the health tiles span the card's width; even spacing above Ansible":
the four tiles take equal quarters (halves on a phone) and their sparklines stretch
across them; the Monitors + Agent row drops the Agent card's own margin, so the gap
above Ansible is 1rem like every other.

#5291 "Add host opens as a dialog over the Hosts list": a native <dialog> on /hosts/,
opened by Add host or /hosts/?add=1. A name clash re-renders the list with it open,
the error and what was typed. GET /hosts/new is gone; hosts/form.html is Edit only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 18:22:48 -04:00
co-authored by Claude Opus 5.5
parent bfe3618e88
commit fb20415a38
13 changed files with 136 additions and 52 deletions
+33 -3
View File
@@ -4,20 +4,24 @@
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). #}
on hover: Monitors is where uptime is read in full (#5280). Add host is a
dialog over this list (#5291); `add` opens it, carrying a clash's error. #}
{% 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 %}
{% set can_add = session.user_role in ['operator', 'admin'] %}
<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>
{% if can_add %}
<button type="button" class="btn" onclick="document.getElementById('add-host').showModal()">Add host</button>
{% endif %}
</div>
</div>
{% if hosts %}
@@ -77,7 +81,33 @@
</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>
<p style="color:var(--text-muted);">No hosts yet.{% if can_add %} <a href="/hosts/?add=1" onclick="document.getElementById('add-host').showModal();return false;">Summon one to the table.</a>{% endif %}</p>
</div>
{% endif %}
{% if can_add %}
{% set v = (add or {}).get('values') or {} %}
<dialog id="add-host" class="modal" aria-labelledby="add-host-title">
<h2 id="add-host-title" class="modal-title">Add host</h2>
{% if add and add.error %}<p class="field-error" role="alert">{{ add.error }}</p>{% endif %}
<form method="post" action="/hosts/" style="display:grid;gap:0.75rem;">{{ csrf_field() }}
<div>
<label class="field-label" for="h-name">Name <span class="tone-crit" aria-hidden="true">*</span></label>
<input type="text" id="h-name" name="name" value="{{ v.name or '' }}"
placeholder="nas, web-1, …" autocomplete="off" required autofocus class="input">
</div>
<div>
<label class="field-label" for="h-address">Address</label>
<input type="text" id="h-address" name="address" value="{{ v.address or '' }}"
placeholder="hostname or IP; empty resolves by name" autocomplete="off" class="input">
</div>
<p class="field-hint" style="margin:0;">Next, on the host's page: add monitors, install its agent, and link an Ansible target.</p>
<div style="display:flex;gap:0.5rem;justify-content:flex-end;">
<button type="button" class="btn btn-sm btn-ghost" onclick="this.closest('dialog').close()">Cancel</button>
<button type="submit" class="btn btn-sm">Add host</button>
</div>
</form>
</dialog>
{% if add is not none %}<script>document.getElementById('add-host').showModal();</script>{% endif %}
{% endif %}
{% endblock %}