9615f9abcd
Container list now sub-groups each host's containers by compose project (or swarm service) with a small subheading, preserving the running-first order; hosts with no such labels render flat as before. The dashboard status widget links each container to its detail page and surfaces enriched state inline — health dot (healthy/unhealthy), restart count, and last non-zero exit code for stopped containers. Completes the #942 UI surface. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Jg27rgypiW2efULXJDtMC
126 lines
6.2 KiB
HTML
126 lines
6.2 KiB
HTML
{# docker/rows.html — HTMX fragment for the Docker main page, grouped by host #}
|
|
|
|
{# ── Summary strip ─────────────────────────────────────────────────────────── #}
|
|
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:0.75rem;margin-bottom:1.5rem;">
|
|
<div class="card" style="margin-bottom:0;">
|
|
<div class="section-title" style="margin-bottom:0.35rem;">Running</div>
|
|
<span class="stat-val" style="color:var(--green);">{{ running }}</span>
|
|
</div>
|
|
<div class="card" style="margin-bottom:0;">
|
|
<div class="section-title" style="margin-bottom:0.35rem;">Stopped</div>
|
|
<span class="stat-val" style="{% if stopped %}color:var(--text-muted){% endif %};">{{ stopped }}</span>
|
|
</div>
|
|
<div class="card" style="margin-bottom:0;">
|
|
<div class="section-title" style="margin-bottom:0.35rem;">Total</div>
|
|
<span class="stat-val">{{ total }}</span>
|
|
</div>
|
|
<div class="card" style="margin-bottom:0;">
|
|
<div class="section-title" style="margin-bottom:0.35rem;">Hosts</div>
|
|
<span class="stat-val">{{ host_groups | length }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
{# ── Container tables, one per host ───────────────────────────────────────── #}
|
|
{% if host_groups %}
|
|
{% for g in host_groups %}
|
|
<div class="card-flush" style="margin-bottom:1.5rem;">
|
|
<div style="display:flex;align-items:baseline;gap:0.6rem;padding:0.5rem 0.75rem;border-bottom:1px solid var(--border);">
|
|
{% if g.host %}
|
|
<a href="/hosts/{{ g.host.id }}" style="font-weight:600;font-size:0.95rem;color:inherit;text-decoration:none;">{{ g.host.name }}</a>
|
|
{% else %}
|
|
<span style="font-weight:600;font-size:0.95rem;">{{ g.host_name }}</span>
|
|
{% endif %}
|
|
<span style="font-size:0.78rem;color:var(--text-muted);">
|
|
{{ g.running }} running{% if g.stopped %} · {{ g.stopped }} stopped{% endif %}
|
|
</span>
|
|
</div>
|
|
<table class="table">
|
|
<thead>
|
|
<tr>
|
|
<th>Container</th>
|
|
<th>Image</th>
|
|
<th>Ports</th>
|
|
<th>CPU %</th>
|
|
<th style="min-width:100px;">CPU history</th>
|
|
<th>Mem %</th>
|
|
<th style="min-width:100px;">Mem history</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for sub in g.subgroups %}
|
|
{% if g.grouped and sub.label %}
|
|
<tr><td colspan="7" style="padding:0.4rem 0.75rem 0.2rem;font-size:0.72rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.05em;font-weight:600;">{{ sub.label }}</td></tr>
|
|
{% endif %}
|
|
{% for item in sub.containers %}
|
|
{% set c = item.container %}
|
|
<tr>
|
|
<td>
|
|
<div style="display:flex;align-items:center;gap:0.5rem;">
|
|
<span class="dot {% if c.status == 'running' %}dot-up{% elif c.status == 'paused' %}dot-warn{% else %}dot-down{% endif %}"></span>
|
|
<div>
|
|
<div style="font-weight:500;font-size:0.9rem;">
|
|
<a href="/plugins/docker/container/{{ c.host_id }}/{{ c.name }}" style="color:inherit;text-decoration:none;border-bottom:1px dotted var(--border-mid);">{{ c.name }}</a>
|
|
{% if c.health == 'healthy' %}<span title="healthy" style="color:var(--green);font-size:0.7rem;">●</span>
|
|
{% elif c.health == 'unhealthy' %}<span title="unhealthy" style="color:var(--red);font-size:0.7rem;">●</span>
|
|
{% elif c.health == 'starting' %}<span title="health: starting" style="color:var(--orange);font-size:0.7rem;">◐</span>{% endif %}
|
|
</div>
|
|
<div style="font-size:0.73rem;color:var(--text-muted);">
|
|
{{ c.status }}{% if item.uptime %} · up {{ item.uptime }}{% endif %}
|
|
{% if c.status != 'running' and c.exit_code is not none and c.exit_code != 0 %}
|
|
· <span style="color:var(--red);">exit {{ c.exit_code }}{% if c.oom_killed %} (OOM){% endif %}</span>
|
|
{% endif %}
|
|
{% if c.restart_count %} · <span title="restart count" style="color:var(--orange);">⟳{{ c.restart_count }}</span>{% endif %}
|
|
</div>
|
|
{% if c.service_name or c.compose_project %}
|
|
<div style="font-size:0.68rem;color:var(--text-dim);margin-top:0.1rem;">
|
|
{{ c.service_name or c.compose_project }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td style="font-size:0.82rem;color:var(--text-muted);max-width:200px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">
|
|
{{ c.image }}
|
|
</td>
|
|
<td style="font-size:0.78rem;font-family:ui-monospace,monospace;">
|
|
{% for p in item.ports %}
|
|
<div style="color:var(--text-muted);">{{ p.host_port }}:{{ p.container_port }}/{{ p.protocol }}</div>
|
|
{% endfor %}
|
|
</td>
|
|
<td style="font-family:ui-monospace,monospace;font-size:0.88rem;">
|
|
{% if c.cpu_pct is not none %}
|
|
<span style="color:{% if c.cpu_pct > 80 %}var(--red){% elif c.cpu_pct > 50 %}var(--orange){% else %}var(--text){% endif %};">
|
|
{{ "%.1f" | format(c.cpu_pct) }}%
|
|
</span>
|
|
{% else %}
|
|
<span style="color:var(--text-dim);">—</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ item.sparkline_cpu | safe }}</td>
|
|
<td style="font-family:ui-monospace,monospace;font-size:0.88rem;">
|
|
{% if c.mem_pct is not none %}
|
|
<span style="color:{% if c.mem_pct > 90 %}var(--red){% elif c.mem_pct > 70 %}var(--orange){% else %}var(--text){% endif %};">
|
|
{{ "%.1f" | format(c.mem_pct) }}%
|
|
</span>
|
|
{% else %}
|
|
<span style="color:var(--text-dim);">—</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ item.sparkline_mem | safe }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% endfor %}
|
|
{% else %}
|
|
<div class="card" style="text-align:center;padding:3rem;">
|
|
<div style="color:var(--text-muted);font-size:0.9rem;">
|
|
No containers reported yet. Containers are collected by the Steward host agent —
|
|
deploy the agent to a host running Docker (Hosts → the host → agent panel) and
|
|
its containers will appear here under that host.
|
|
</div>
|
|
</div>
|
|
{% endif %}
|