feat(hosts): the Hosts list labels its figures and says how long; SLA tab gone; one place to add a host (#5279–#5283)
CI / lint (push) Successful in 3s
CI / integration (push) Successful in 53s
CI / unit (push) Successful in 1m16s
CI / publish (push) Successful in 1m23s

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:
2026-10-07 17:12:17 -04:00
co-authored by Claude Opus 5.5
parent d4974ddf6c
commit bfe3618e88
13 changed files with 178 additions and 252 deletions
+8 -19
View File
@@ -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}")