|
|
|
@@ -0,0 +1,112 @@
|
|
|
|
|
{# docker/container_detail.html — full detail page for one container #}
|
|
|
|
|
{% extends "base.html" %}
|
|
|
|
|
{% block title %}{{ name }} — Docker — Steward{% endblock %}
|
|
|
|
|
{% block content %}
|
|
|
|
|
|
|
|
|
|
<div style="margin-bottom:1rem;">
|
|
|
|
|
<a href="/plugins/docker/" style="font-size:0.82rem;color:var(--text-muted);text-decoration:none;">← All containers</a>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{% if container is none %}
|
|
|
|
|
<div class="card" style="text-align:center;padding:3rem;">
|
|
|
|
|
<div style="font-weight:600;margin-bottom:0.4rem;">Container not found</div>
|
|
|
|
|
<div style="color:var(--text-muted);font-size:0.9rem;">
|
|
|
|
|
No container named <code>{{ name }}</code> is currently reported for this host.
|
|
|
|
|
It may have been removed, or the host agent hasn't reported recently.
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{% else %}
|
|
|
|
|
|
|
|
|
|
{# ── Header ──────────────────────────────────────────────────────────────── #}
|
|
|
|
|
<div style="display:flex;align-items:center;gap:0.6rem;margin-bottom:0.35rem;flex-wrap:wrap;">
|
|
|
|
|
<span class="dot {% if container.status == 'running' %}dot-up{% elif container.status == 'paused' %}dot-warn{% else %}dot-down{% endif %}"></span>
|
|
|
|
|
<h1 class="page-title" style="margin-bottom:0;">{{ container.name }}</h1>
|
|
|
|
|
{% if container.health == 'healthy' %}<span style="font-size:0.72rem;color:var(--green);border:1px solid var(--green);border-radius:3px;padding:0.05rem 0.4rem;">healthy</span>
|
|
|
|
|
{% elif container.health == 'unhealthy' %}<span style="font-size:0.72rem;color:var(--red);border:1px solid var(--red);border-radius:3px;padding:0.05rem 0.4rem;">unhealthy</span>
|
|
|
|
|
{% elif container.health == 'starting' %}<span style="font-size:0.72rem;color:var(--orange);border:1px solid var(--orange);border-radius:3px;padding:0.05rem 0.4rem;">starting</span>{% endif %}
|
|
|
|
|
</div>
|
|
|
|
|
<div style="font-size:0.82rem;color:var(--text-muted);margin-bottom:1.5rem;">
|
|
|
|
|
{{ container.status }}{% if uptime %} · up {{ uptime }}{% endif %}
|
|
|
|
|
{% if host %} · on <a href="/hosts/{{ host.id }}" style="color:var(--text-muted);">{{ host.name }}</a>{% endif %}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{# ── Facts grid ──────────────────────────────────────────────────────────── #}
|
|
|
|
|
{% macro fact(label, value, colour="") %}
|
|
|
|
|
<div class="card" style="margin-bottom:0;">
|
|
|
|
|
<div class="section-title" style="margin-bottom:0.3rem;">{{ label }}</div>
|
|
|
|
|
<div style="font-size:0.95rem;{% if colour %}color:{{ colour }};{% endif %}font-family:ui-monospace,monospace;">{{ value }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
{% endmacro %}
|
|
|
|
|
|
|
|
|
|
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:0.75rem;margin-bottom:1.5rem;">
|
|
|
|
|
{{ fact("CPU", "%.1f%%" | format(container.cpu_pct) if container.cpu_pct is not none else "—") }}
|
|
|
|
|
{{ fact("Memory", "%.1f%%" | format(container.mem_pct) if container.mem_pct is not none else "—") }}
|
|
|
|
|
{{ fact("Restarts", container.restart_count, "var(--orange)" if container.restart_count else "") }}
|
|
|
|
|
{{ fact("Last exit", (container.exit_code ~ (" (OOM)" if container.oom_killed else "")) if container.exit_code is not none else "—",
|
|
|
|
|
"var(--red)" if (container.exit_code is not none and container.exit_code != 0) else "") }}
|
|
|
|
|
{{ fact("Net in", net_rx) }}
|
|
|
|
|
{{ fact("Net out", net_tx) }}
|
|
|
|
|
{{ fact("Block read", blk_read) }}
|
|
|
|
|
{{ fact("Block write", blk_write) }}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{# ── Image / placement ───────────────────────────────────────────────────── #}
|
|
|
|
|
<div class="card" style="margin-bottom:1.5rem;">
|
|
|
|
|
<div style="display:grid;grid-template-columns:max-content 1fr;gap:0.4rem 1.25rem;font-size:0.86rem;">
|
|
|
|
|
<span style="color:var(--text-muted);">Image</span>
|
|
|
|
|
<span style="font-family:ui-monospace,monospace;word-break:break-all;">{{ container.image or "—" }}</span>
|
|
|
|
|
{% if container.compose_project %}
|
|
|
|
|
<span style="color:var(--text-muted);">Compose project</span><span>{{ container.compose_project }}</span>
|
|
|
|
|
{% endif %}
|
|
|
|
|
{% if container.service_name %}
|
|
|
|
|
<span style="color:var(--text-muted);">Swarm service</span><span>{{ container.service_name }}</span>
|
|
|
|
|
{% endif %}
|
|
|
|
|
{% if container.node_id %}
|
|
|
|
|
<span style="color:var(--text-muted);">Node</span><span style="font-family:ui-monospace,monospace;">{{ container.node_id }}</span>
|
|
|
|
|
{% endif %}
|
|
|
|
|
{% if ports %}
|
|
|
|
|
<span style="color:var(--text-muted);">Ports</span>
|
|
|
|
|
<span style="font-family:ui-monospace,monospace;">{% for p in ports %}{{ p.host_port }}:{{ p.container_port }}/{{ p.protocol }}{% if not loop.last %}, {% endif %}{% endfor %}</span>
|
|
|
|
|
{% endif %}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{# ── Resource history (range-toggled) ────────────────────────────────────── #}
|
|
|
|
|
<div class="card" style="margin-bottom:1.5rem;">
|
|
|
|
|
<div style="display:flex;align-items:center;justify-content:space-between;gap:1rem;margin-bottom:0.75rem;flex-wrap:wrap;">
|
|
|
|
|
<h3 class="section-title" style="margin-bottom:0;">Resource history</h3>
|
|
|
|
|
{% include "_time_range.html" %}
|
|
|
|
|
</div>
|
|
|
|
|
<div id="container-history"
|
|
|
|
|
hx-get="/plugins/docker/container/{{ host_id }}/{{ name }}/history"
|
|
|
|
|
hx-trigger="load, rangeChange from:body"
|
|
|
|
|
hx-include="#time-range"
|
|
|
|
|
hx-swap="innerHTML">
|
|
|
|
|
<div style="color:var(--text-muted);font-size:0.85rem;">Loading…</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{# ── Lifecycle timeline ──────────────────────────────────────────────────── #}
|
|
|
|
|
<div class="card">
|
|
|
|
|
<h3 class="section-title" style="margin-bottom:0.75rem;">Lifecycle</h3>
|
|
|
|
|
{% if events %}
|
|
|
|
|
<div style="display:grid;gap:0.5rem;">
|
|
|
|
|
{% for e in events %}
|
|
|
|
|
<div style="display:flex;align-items:baseline;gap:0.6rem;font-size:0.85rem;">
|
|
|
|
|
<span style="color:{{ e.colour }};width:1rem;text-align:center;flex-shrink:0;">{{ e.glyph }}</span>
|
|
|
|
|
<span style="font-weight:500;width:6.5rem;flex-shrink:0;">{{ e.event }}</span>
|
|
|
|
|
<span style="color:var(--text-muted);flex:1;min-width:0;">{{ e.detail or "" }}</span>
|
|
|
|
|
<span style="color:var(--text-dim);font-size:0.78rem;white-space:nowrap;" title="{{ e.at }}">{{ e.at.strftime("%Y-%m-%d %H:%M") }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
{% endfor %}
|
|
|
|
|
</div>
|
|
|
|
|
{% else %}
|
|
|
|
|
<div style="color:var(--text-muted);font-size:0.85rem;">
|
|
|
|
|
No lifecycle events recorded yet. Start/stop/health changes appear here as the
|
|
|
|
|
agent reports them over time.
|
|
|
|
|
</div>
|
|
|
|
|
{% endif %}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{% endif %}
|
|
|
|
|
{% endblock %}
|