diff --git a/plugins/host_agent/routes.py b/plugins/host_agent/routes.py index 1863141..c15f177 100644 --- a/plugins/host_agent/routes.py +++ b/plugins/host_agent/routes.py @@ -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//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//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) ────── diff --git a/plugins/host_agent/templates/settings_list.html b/plugins/host_agent/templates/settings_list.html index 7cbff8f..866cad4 100644 --- a/plugins/host_agent/templates/settings_list.html +++ b/plugins/host_agent/templates/settings_list.html @@ -1,9 +1,16 @@ {% extends "base.html" %} {% from "_macros.html" import crumbs %} -{% block title %}Host Agent — Steward{% endblock %} -{% block breadcrumb %}{{ crumbs([("Host Agents", "/plugins/host_agent/"), ("Settings", "")]) }}{% endblock %} +{% block title %}Agent fleet — Steward{% endblock %} +{% block breadcrumb %}{{ crumbs([("Hosts", "/hosts/"), ("Agent fleet", "")]) }}{% endblock %} {% block content %} -

Host Agent — Registered Hosts

+
+

Agent fleet

+ ← Hosts +
+

+ Bulk agent operations across your inventory. For a single host, use its + host page — provisioning and metrics live there. +

{% if new_token %}
@@ -58,7 +65,7 @@ No managed SSH key yet — Steward needs one to log into hosts as steward.
- +
diff --git a/steward/hosts/routes.py b/steward/hosts/routes.py index c9a903e..95e0ab2 100644 --- a/steward/hosts/routes.py +++ b/steward/hosts/routes.py @@ -68,6 +68,42 @@ async def _compute_uptime(db) -> dict[str, dict]: return stats +async def _agent_metrics_by_host(db, host_names: list[str]) -> dict[str, dict]: + """Latest agent cpu/mem per host name from the generic PluginMetric table. + + Read directly (PluginMetric is a core model) so the Hosts list can show an + at-a-glance agent column without importing the host_agent plugin. + """ + from steward.models.metrics import PluginMetric + if not host_names: + return {} + wanted = ("cpu_pct", "mem_used_pct") + subq = ( + select( + PluginMetric.resource_name, PluginMetric.metric_name, + func.max(PluginMetric.recorded_at).label("m"), + ) + .where( + PluginMetric.source_module == "host_agent", + PluginMetric.metric_name.in_(wanted), + PluginMetric.resource_name.in_(host_names), + ) + .group_by(PluginMetric.resource_name, PluginMetric.metric_name) + ).subquery() + rows = (await db.execute( + select(PluginMetric).join( + subq, + (PluginMetric.resource_name == subq.c.resource_name) + & (PluginMetric.metric_name == subq.c.metric_name) + & (PluginMetric.recorded_at == subq.c.m), + ) + )).scalars().all() + out: dict[str, dict] = {} + for r in rows: + out.setdefault(r.resource_name, {})[r.metric_name] = r.value + return out + + @hosts_bp.get("/") @require_role(UserRole.viewer) async def list_hosts(): @@ -105,6 +141,7 @@ async def list_hosts(): ) latest_dns = {r.host_id: r for r in dr.scalars()} uptime = await _compute_uptime(db) + agent_metrics = await _agent_metrics_by_host(db, [h.name for h in hosts]) return await render_template( "hosts/list.html", @@ -112,6 +149,7 @@ async def list_hosts(): latest_pings=latest_pings, latest_dns=latest_dns, uptime=uptime, + agent_metrics=agent_metrics, ) diff --git a/steward/templates/hosts/list.html b/steward/templates/hosts/list.html index 5c3a2a8..dde6693 100644 --- a/steward/templates/hosts/list.html +++ b/steward/templates/hosts/list.html @@ -5,6 +5,9 @@

Hosts

SLA + {% if session.user_role == 'admin' %} + Agent fleet + {% endif %} Add Host
@@ -22,6 +25,7 @@ 24h 7d 30d + Agent @@ -89,6 +93,16 @@ {% endif %} {% endfor %} + {% set am = agent_metrics.get(host.name) %} + + {% if am %} + + {{ '%.0f'|format(am.get('cpu_pct', 0)) }}% / {{ '%.0f'|format(am.get('mem_used_pct', 0)) }}% + + {% else %} + + {% endif %} + Edit