Traefik: routers are graded from their latest scrape (router_state: crit on any real 5xx share, warn on p95 past the latency critical cutoff or a high 4xx share). Failing and slow routers lead as cards that say why; healthy ones fold into one table that stays open or shut across polls. Proxy counts are stat tiles; certificates are graded by Settings -> Thresholds (tls_days). UniFi: WAN health is a stat strip (latency graded by the thresholds), devices carry status dots, event categories and firewall actions are badges, the alarm count reuses .count-badge (renamed from .nav-count). SNMP: each device wears a status badge and its readings are stat tiles. Agent fleet: the table comes first, with a Status column (Reporting / Silent / Never reported, by the host page's stale_after_seconds), silent agents first, hosts linked, last seen as |ago. Audit: entries name their entity (one query per type, steward/audit/ service.py) and link it; something deleted since keeps the name its entry recorded and says "(deleted)". Tokens: charts read colours through chart-colors.js (stewardColor resolves a token to the rgb the browser computed; Chart.js defaults take the page's text and border colours), so host, history, Traefik and SNMP charts drop their hex. Template hex colours reach 0 (HEX_CEILING = 0), the last rgba pills go, and heavy weights (600/700/bold, 60 of them) become 500, the house pair being 400/500; a guard keeps templates there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
69 lines
4.3 KiB
HTML
69 lines
4.3 KiB
HTML
{# docker/widget.html — dashboard widget: container status overview, by host #}
|
||
{% if total_count == 0 %}
|
||
<p class="empty" style="padding:0.5rem 0;font-size:0.85rem;">No containers reported yet.</p>
|
||
{% else %}
|
||
|
||
<div style="display:flex;gap:1.25rem;margin-bottom:0.65rem;">
|
||
<div>
|
||
<span style="font-size:1.4rem;font-weight:500;line-height:1;color:var(--green);">{{ running_count }}</span>
|
||
<span style="font-size:0.78rem;color:var(--text-muted);margin-left:0.25rem;">running</span>
|
||
</div>
|
||
<div title="Containers that died or were OOM-killed in the last 24h, plus swarm tasks that failed (a task Swarm replaced is not a failure)">
|
||
<span style="font-size:1.4rem;font-weight:500;line-height:1;color:{% if failed_24h %}var(--red){% else %}var(--text-muted){% endif %};">{{ failed_24h }}</span>
|
||
<span style="font-size:0.78rem;color:var(--text-muted);margin-left:0.25rem;">failed (24h)</span>
|
||
</div>
|
||
</div>
|
||
|
||
{# Swarm services — collapsed, each with its replicas' host chips (dashed = a
|
||
node with no Steward agent, count-only from placement). #}
|
||
{% if swarm_services %}
|
||
<div style="font-size:0.72rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.05em;margin:0.5rem 0 0.2rem;">Swarm services</div>
|
||
<div style="display:grid;gap:0.4rem;margin-bottom:0.4rem;">
|
||
{% for s in swarm_services %}
|
||
<div>
|
||
<div style="display:flex;align-items:center;gap:0.4rem;font-size:0.82rem;">
|
||
<span class="dot {% if s.state == 'healthy' %}dot-up{% elif s.state == 'degraded' %}dot-warn{% else %}dot-down{% endif %}"></span>
|
||
<span style="font-weight:500;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">{{ s.name }}</span>
|
||
{% if s.crash_looping %}<span style="color:var(--red);font-size:0.7rem;font-weight:500;flex-shrink:0;" title="{{ s.failures_1h }} tasks failed in the last hour{% if s.last_error %}: {{ s.last_error.error or s.last_error.message or s.last_error.state }}{% endif %}">crash-looping</span>{% endif %}
|
||
<span style="color:var(--text-muted);font-family:ui-monospace,monospace;font-size:0.74rem;margin-left:auto;flex-shrink:0;">{{ s.running }}/{{ s.desired }}</span>
|
||
</div>
|
||
<div style="display:flex;flex-wrap:wrap;gap:0.25rem;margin:0.2rem 0 0 1rem;">
|
||
{% for r in s.replicas %}
|
||
{% if r.ghost %}
|
||
<span title="no Steward agent on this node — count from swarm placement" style="font-size:0.68rem;color:var(--text-dim);background:var(--bg-elevated);border:1px dashed var(--border-mid);border-radius:4px;padding:0.05rem 0.35rem;">{{ r.host }} ×{{ r.count }}</span>
|
||
{% else %}
|
||
<span style="font-size:0.68rem;color:var(--text-muted);background:var(--bg-elevated);border:1px solid var(--border);border-radius:4px;padding:0.05rem 0.35rem;">{{ r.host }}</span>
|
||
{% endif %}
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% endif %}
|
||
|
||
{% for g in host_groups %}
|
||
{% if multi_host %}
|
||
<div style="font-size:0.72rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.05em;margin:0.5rem 0 0.2rem;">{{ g.host_name }}</div>
|
||
{% endif %}
|
||
<div style="display:grid;gap:2px;">
|
||
{% for c in g.containers %}
|
||
<div style="display:flex;align-items:center;gap:0.5rem;font-size:0.82rem;padding:0.15rem 0;">
|
||
<span class="dot {% if c.status == 'running' %}dot-up{% elif c.status == 'paused' %}dot-warn{% else %}dot-down{% endif %}"></span>
|
||
<a href="/plugins/docker/container/{{ c.host_id }}/{{ c.name }}" style="flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:inherit;text-decoration:none;">
|
||
{{ c.name }}{% if c.health == 'unhealthy' %}<span title="unhealthy" style="color:var(--red);"> ●</span>{% elif c.health == 'healthy' %}<span title="healthy" style="color:var(--green);"> ●</span>{% endif %}{% if c.restart_count %}<span title="restarts" style="color:var(--orange);font-size:0.7rem;"> ⟳{{ c.restart_count }}</span>{% endif %}
|
||
</a>
|
||
{% if c.status != 'running' and c.exit_code is not none and c.exit_code != 0 %}
|
||
<span title="last exit code" style="font-size:0.7rem;color:var(--red);flex-shrink:0;">exit {{ c.exit_code }}</span>
|
||
{% endif %}
|
||
{% if c.cpu_pct is not none %}
|
||
<span style="font-size:0.72rem;color:var(--text-muted);font-family:ui-monospace,monospace;flex-shrink:0;">
|
||
{{ "%.1f" | format(c.cpu_pct) }}%
|
||
</span>
|
||
{% endif %}
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% endfor %}
|
||
|
||
{% endif %}
|