389002fc6f
Adds a breadcrumb trail to nested views for orientation. Mechanism: a
{% block breadcrumb %} slot in base.html (+ styling) and a shared crumbs()
macro in templates/_macros.html; each nested page fills the block with its
trail (root→current, last item is the current page). Pages without the block
render no bar, so top-level nav roots stay clean.
Applied to: Ansible (browse, schedules, playbook editor, run detail) +
inventory (targets/groups + detail), host_agent (fleet, host detail,
settings), hosts form, settings tabs (ansible/auth/notifications/plugins/
reports + plugin detail), dashboard (list, edit), and alerts (rule form,
maintenance + new). Dynamic labels (host/target/run/dashboard names) come
from the page context — no route changes. All 60 templates Jinja-compile.
Task #873.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
46 lines
2.7 KiB
HTML
46 lines
2.7 KiB
HTML
{% extends "base.html" %}
|
|
{% from "_macros.html" import crumbs %}
|
|
{% block title %}Host Agents — Steward{% endblock %}
|
|
{% block breadcrumb %}{{ crumbs([("Dashboard", "/"), ("Host Agents", "")]) }}{% endblock %}
|
|
{% block content %}
|
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1.25rem;gap:1rem;flex-wrap:wrap;">
|
|
<h1 class="page-title" style="margin-bottom:0;">Host Agents</h1>
|
|
{% if session.user_role == 'admin' %}
|
|
<a class="btn btn-ghost" href="/plugins/host_agent/settings/">Manage agents →</a>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if not rows %}
|
|
<div class="card empty">
|
|
No hosts are reporting agent metrics yet.
|
|
{% if session.user_role == 'admin' %}
|
|
Add one from <a href="/plugins/host_agent/settings/">Host Agent settings</a> and run the install command on the target.
|
|
{% else %}
|
|
Ask an admin to install the agent on a host.
|
|
{% endif %}
|
|
</div>
|
|
{% else %}
|
|
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:1rem;">
|
|
{% for r in rows %}
|
|
<a href="/plugins/host_agent/{{ r.host.id }}/" class="card" style="display:block;margin-bottom:0;color:inherit;">
|
|
<div style="display:flex;align-items:center;gap:0.5rem;margin-bottom:0.7rem;">
|
|
<span class="dot {% if r.stale %}dot-dim{% elif (r.cpu_pct or 0) >= 90 or (r.mem_used_pct or 0) >= 90 or (r.disk_worst or 0) >= 90 %}dot-warn{% else %}dot-up{% endif %}"></span>
|
|
<span style="font-weight:600;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">{{ r.host.name }}</span>
|
|
{% if r.stale %}<span class="badge badge-dim">stale</span>{% endif %}
|
|
</div>
|
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:0.45rem 0.9rem;font-size:0.84rem;font-variant-numeric:tabular-nums;">
|
|
<span><span style="color:var(--text-dim);">CPU</span> {% if r.cpu_pct is not none %}{{ "%.0f"|format(r.cpu_pct) }}%{% else %}—{% endif %}</span>
|
|
<span><span style="color:var(--text-dim);">Mem</span> {% if r.mem_used_pct is not none %}{{ "%.0f"|format(r.mem_used_pct) }}%{% else %}—{% endif %}</span>
|
|
<span><span style="color:var(--text-dim);">Disk</span> {% if r.disk_worst is not none %}{{ "%.0f"|format(r.disk_worst) }}%{% else %}—{% endif %}</span>
|
|
<span><span style="color:var(--text-dim);">Load</span> {% if r.load_1m is not none %}{{ "%.2f"|format(r.load_1m) }}{% else %}—{% endif %}</span>
|
|
{% if r.temp_max is not none %}<span><span style="color:var(--text-dim);">Temp</span> {{ "%.0f"|format(r.temp_max) }}°C</span>{% endif %}
|
|
</div>
|
|
<div style="margin-top:0.6rem;font-size:0.72rem;color:var(--text-dim);">
|
|
last seen {{ r.reg.last_seen_at.strftime("%Y-%m-%d %H:%M:%S") ~ " UTC" if r.reg.last_seen_at else "never" }}
|
|
</div>
|
|
</a>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
{% endblock %}
|