feat(hosts): Phase 2+3 — agent column, fold fleet + management into Hosts
- 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:
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user