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>
This commit is contained in:
@@ -796,35 +796,24 @@ async def fleet():
|
||||
)
|
||||
|
||||
|
||||
@host_agent_bp.post("/fleet/add-host")
|
||||
@host_agent_bp.post("/fleet/<host_id>/register")
|
||||
@require_role(UserRole.admin)
|
||||
async def add_host():
|
||||
form = await request.form
|
||||
name = (form.get("name") or "").strip()
|
||||
address = (form.get("address") or "").strip()
|
||||
if not name:
|
||||
return _error(400, "missing_name")
|
||||
|
||||
async def register_host(host_id: str):
|
||||
"""Issue an agent token for an existing host, for a manual install: the
|
||||
fleet page then shows the one-line install command, once. Hosts are added
|
||||
on Hosts → Add host; this is only the agent half (#5282)."""
|
||||
async with current_app.db_sessionmaker() as session:
|
||||
host = (await session.execute(
|
||||
select(Host).where(Host.name == name))).scalar_one_or_none()
|
||||
host = await session.get(Host, host_id)
|
||||
if host is None:
|
||||
host = Host(name=name, address=address)
|
||||
session.add(host)
|
||||
await session.flush()
|
||||
return _error(404, "not_found")
|
||||
existing = (await session.execute(
|
||||
select(HostAgentRegistration).where(
|
||||
HostAgentRegistration.host_id == host.id))).scalar_one_or_none()
|
||||
if existing is not None:
|
||||
await session.rollback()
|
||||
return _error(400, "already_registered")
|
||||
|
||||
raw, hashed = _new_token_pair()
|
||||
reg = HostAgentRegistration(host_id=host.id, token_hash=hashed)
|
||||
session.add(reg)
|
||||
session.add(HostAgentRegistration(host_id=host.id, token_hash=hashed))
|
||||
await session.commit()
|
||||
host_id = host.id
|
||||
|
||||
return redirect(url_for("host_agent.fleet") + f"?new_token={raw}&host_id={host_id}")
|
||||
|
||||
|
||||
|
||||
@@ -91,8 +91,7 @@
|
||||
|
||||
{% elif not ansible_available %}
|
||||
<p style="color:var(--text-dim);font-size:0.85rem;">
|
||||
Ansible isn't available, so the agent can't be deployed from here. Install it manually with the
|
||||
<a href="/plugins/host_agent/fleet/">curl install command</a>.
|
||||
Ansible isn't available, so the agent can't be deployed from here. Install it manually instead (below).
|
||||
</p>
|
||||
|
||||
{% elif not managed_key_set %}
|
||||
@@ -141,6 +140,16 @@
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if not reg and session.user_role == 'admin' %}
|
||||
{# A manual install: no Ansible, no SSH. Issues this host's token and shows
|
||||
the one-line install command on the fleet page, once (#5282). #}
|
||||
<form method="post" action="/plugins/host_agent/fleet/{{ host.id }}/register"
|
||||
style="display:flex;gap:0.6rem;align-items:center;flex-wrap:wrap;margin:0.75rem 0 0;">{{ csrf_field() }}
|
||||
<span style="font-size:0.78rem;color:var(--text-muted);">Or by hand on the host:</span>
|
||||
<button type="submit" class="btn btn-sm btn-ghost">Install manually</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if reg and session.user_role == 'admin' %}
|
||||
<form method="post" action="/plugins/host_agent/fleet/{{ host.id }}/delete" style="margin:0.75rem 0 0;"
|
||||
data-confirm="Clear this pending registration? Its token is discarded.">{{ csrf_field() }}
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
</div>
|
||||
<p style="color:var(--text-muted);font-size:0.84rem;margin:-0.4rem 0 1.25rem;">
|
||||
Bulk agent operations across your inventory. For a single host, use its
|
||||
<a href="/hosts/">host page</a> — provisioning and metrics live there.
|
||||
<a href="/hosts/">host page</a>: provisioning, a manual install command and
|
||||
metrics live there. New hosts are added with <a href="/hosts/new">Add host</a>.
|
||||
</p>
|
||||
|
||||
{% if new_token %}
|
||||
@@ -47,34 +48,19 @@
|
||||
</form>
|
||||
<form method="post" action="/plugins/host_agent/fleet/{{ item.reg.host_id }}/delete"
|
||||
style="display:inline;"
|
||||
data-confirm="Delete the agent registration for {{ item.name }}? The agent will be unable to report metrics until re-registered.">{{ csrf_field() }}
|
||||
<button type="submit" class="btn btn-danger btn-sm">Delete</button>
|
||||
data-confirm="Unregister the agent for {{ item.name }}? Its token stops working and metrics stop until it is registered again. The agent itself stays installed on the host.">{{ csrf_field() }}
|
||||
<button type="submit" class="btn btn-danger btn-sm">Unregister</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="7" class="empty">No hosts registered. Add one below.</td></tr>
|
||||
<tr><td colspan="7" class="empty">No agents registered yet. <a href="/hosts/new">Add a host</a>, then install its agent from the host's page.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<form method="post" action="/plugins/host_agent/fleet/add-host"
|
||||
style="display:flex;gap:0.75rem;align-items:flex-end;flex-wrap:wrap;">{{ csrf_field() }}
|
||||
<div class="form-group" style="margin-bottom:0;">
|
||||
<label>Host name</label>
|
||||
<input type="text" name="name" required>
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:0;">
|
||||
<label>Address (optional)</label>
|
||||
<input type="text" name="address">
|
||||
</div>
|
||||
<button type="submit" class="btn">Add host</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% if ansible_available %}
|
||||
{% set scope_select %}
|
||||
<label>Target</label>
|
||||
|
||||
Reference in New Issue
Block a user