feat(hosts): a host's health tiles span the card and the gaps match; Add host is a dialog on the list (#5290, #5291)
#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:
@@ -51,10 +51,10 @@
|
||||
hx-swap="innerHTML"></div>
|
||||
|
||||
{# ── Monitors + Agent, side by side on wide screens, stacked when narrow ────── #}
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(420px,1fr));gap:1rem;align-items:start;margin-bottom:1rem;">
|
||||
<div class="host-pair">
|
||||
|
||||
{# ── Monitors ─────────────────────────────────────────────────────────────── #}
|
||||
<div class="card" style="margin-bottom:0;">
|
||||
<div class="card">
|
||||
<div style="display:flex;align-items:baseline;justify-content:space-between;margin-bottom:0.5rem;">
|
||||
<h3 class="section-title" style="margin-bottom:0;">Monitors</h3>
|
||||
{% if session.user_role in ['operator', 'admin'] %}
|
||||
@@ -108,7 +108,7 @@
|
||||
|
||||
{# ── Agent (host_agent management panel) ──────────────────────────────────── #}
|
||||
<div hx-get="/plugins/host_agent/panel/{{ host.id }}" hx-trigger="load" hx-swap="innerHTML">
|
||||
<div class="card" style="margin-bottom:0;"><span style="color:var(--text-muted);font-size:0.85rem;">Loading agent…</span></div>
|
||||
<div class="card"><span style="color:var(--text-muted);font-size:0.85rem;">Loading agent…</span></div>
|
||||
</div>
|
||||
</div>{# Monitors + Agent grid #}
|
||||
|
||||
|
||||
@@ -1,41 +1,34 @@
|
||||
{% extends "base.html" %}
|
||||
{% from "_macros.html" import crumbs, page_header %}
|
||||
{# Add a host (host is none) or edit one; `values` holds what was typed when
|
||||
an error sends the form back. Laid out like the add-monitor form (#5283). #}
|
||||
{% block title %}{{ "Edit " ~ host.name if host else "Add host" }} — Steward{% endblock %}
|
||||
{% block breadcrumb %}
|
||||
{%- if host -%}
|
||||
{{ crumbs([("Hosts", "/hosts/"), (host.name, "/hosts/" ~ host.id), ("Edit", "")]) }}
|
||||
{%- else -%}
|
||||
{{ crumbs([("Hosts", "/hosts/"), ("Add host", "")]) }}
|
||||
{%- endif -%}
|
||||
{% endblock %}
|
||||
{# Edit a host; `values` holds what was typed when a clash sends the form back.
|
||||
Adding one is a dialog on the Hosts list (#5291). #}
|
||||
{% block title %}Edit {{ host.name }} — Steward{% endblock %}
|
||||
{% block breadcrumb %}{{ crumbs([("Hosts", "/hosts/"), (host.name, "/hosts/" ~ host.id), ("Edit", "")]) }}{% endblock %}
|
||||
{% block content %}
|
||||
{% set v = values or {} %}
|
||||
{{ page_header("Edit host" if host else "Add host") }}
|
||||
{{ page_header("Edit host") }}
|
||||
|
||||
<div class="card" style="max-width:760px;">
|
||||
{% if error %}<p class="field-error" role="alert">{{ error }}</p>{% endif %}
|
||||
<form method="post" action="{{ '/hosts/' ~ host.id if host else '/hosts/' }}" style="display:grid;gap:0.6rem;">{{ csrf_field() }}
|
||||
<form method="post" action="/hosts/{{ host.id }}" style="display:grid;gap:0.6rem;">{{ csrf_field() }}
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:0.6rem;align-items:end;">
|
||||
<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 if v.name is defined else (host.name if host else '') }}"
|
||||
<input type="text" id="h-name" name="name" value="{{ v.name if v.name is defined else host.name }}"
|
||||
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 if v.address is defined else (host.address if host else '') }}"
|
||||
<input type="text" id="h-address" name="address" value="{{ v.address if v.address is defined else host.address }}"
|
||||
placeholder="hostname or IP; empty resolves by name" autocomplete="off" class="input">
|
||||
</div>
|
||||
</div>
|
||||
<p class="field-hint" style="margin:0;">
|
||||
{% if host %}Renaming carries the host's agent and container history, and the alert rules on it, to the new name.
|
||||
{% else %}Next, on the host's page: add monitors, install its agent, and link an Ansible target.{% endif %}
|
||||
Renaming carries the host's agent and container history, and the alert rules on it, to the new name.
|
||||
</p>
|
||||
<div style="display:flex;gap:0.5rem;">
|
||||
<button type="submit" class="btn btn-sm">{{ "Save" if host else "Add host" }}</button>
|
||||
<a href="{{ '/hosts/' ~ host.id if host else '/hosts/' }}" class="btn btn-sm btn-ghost">Cancel</a>
|
||||
<button type="submit" class="btn btn-sm">Save</button>
|
||||
<a href="/hosts/{{ host.id }}" class="btn btn-sm btn-ghost">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{% from "_macros.html" import status_dot %}
|
||||
{% if not rows %}
|
||||
<div class="w-verdict" style="color:var(--text-muted);">
|
||||
No hosts yet.{% if session.user_id %} <a href="/hosts/new">Add one →</a>{% endif %}
|
||||
No hosts yet.{% if session.user_id %} <a href="/hosts/?add=1">Add one →</a>{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
{% set problems = rows | selectattr("state", "in", ["down", "crit", "stale", "warn"]) | list %}
|
||||
|
||||
Reference in New Issue
Block a user