feat(hosts): Phase 2+3 — agent column, fold fleet + management into Hosts
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 9s
CI / integration (push) Successful in 2m16s
CI / publish (push) Successful in 54s

- Hosts list: new Agent column (latest CPU/mem read from the generic
  PluginMetric table — no host_agent import) + an admin "Agent fleet" button.
- /plugins/host_agent/ (old fleet page) now redirects to /hosts/ (folded into
  the hub; kept as a redirect so widgets/links don't 404).
- Agent management moved off the "settings" URL: the management page is now
  /plugins/host_agent/fleet/ ("Agent fleet" — bulk provision/install/update +
  registrations + curl install), reachable from the Hosts list. Old
  /plugins/host_agent/settings/ redirects there. Per-host management lives on
  the host detail page; this page is now explicitly the bulk/fleet view.

Milestone 70 phases 2-3. Phase 4 (plugins capability/integration split + nav
cleanup) next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-16 20:51:33 -04:00
parent 8bdf07f709
commit f29255039d
4 changed files with 77 additions and 12 deletions
+14 -8
View File
@@ -375,10 +375,9 @@ async def _fleet_rows(session) -> list[dict]:
@host_agent_bp.get("/")
@require_role(UserRole.viewer)
async def index():
"""Fleet overview page — cards per host linking to the detail view."""
async with current_app.db_sessionmaker() as session:
rows = await _fleet_rows(session)
return await render_template("host_list.html", rows=rows)
"""The fleet view folded into the Hosts hub — kept as a redirect so old
links/widgets don't 404."""
return redirect("/hosts/")
@host_agent_bp.get("/widget")
@@ -571,7 +570,14 @@ async def host_panel(host_id: str):
@host_agent_bp.get("/settings/")
@require_role(UserRole.admin)
async def settings_list():
async def settings_redirect():
"""Old management URL — agent management moved into the Hosts section."""
return redirect(url_for("host_agent.fleet"))
@host_agent_bp.get("/fleet/")
@require_role(UserRole.admin)
async def fleet():
from steward.core.capabilities import has_capability
from steward.models.ansible_inventory import AnsibleTarget, AnsibleGroup
@@ -641,7 +647,7 @@ async def add_host():
await session.commit()
host_id = host.id
return redirect(url_for("host_agent.settings_list") + f"?new_token={raw}&host_id={host_id}")
return redirect(url_for("host_agent.fleet") + f"?new_token={raw}&host_id={host_id}")
@host_agent_bp.post("/settings/<host_id>/rotate-token")
@@ -657,7 +663,7 @@ async def rotate_token(host_id: str):
reg.token_hash = hashed
reg.token_created_at = datetime.now(timezone.utc)
await session.commit()
return redirect(url_for("host_agent.settings_list") + f"?new_token={raw}&host_id={host_id}")
return redirect(url_for("host_agent.fleet") + f"?new_token={raw}&host_id={host_id}")
@host_agent_bp.post("/settings/<host_id>/delete")
@@ -670,7 +676,7 @@ async def delete_registration(host_id: str):
if reg is not None:
await session.delete(reg)
await session.commit()
return redirect(url_for("host_agent.settings_list"))
return redirect(url_for("host_agent.fleet"))
# ── Deploy via Ansible (plugin↔core synergy via the capability registry) ──────