From 429629be617c01c6ba1259201cd61ccb0206ca28 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 7 Oct 2026 20:38:04 -0400 Subject: [PATCH] fix(docker): the overview's failed count sits in the heading of the list it counts, no tile row beside it; stale moves into the verdict (#5214) Co-Authored-By: Claude Opus 5.5 --- plugins/docker/templates/docker/rows.html | 19 +++++++------------ steward/static/steward.css | 3 +++ tests/plugins/docker/test_docker_page.py | 13 ++++++++----- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/plugins/docker/templates/docker/rows.html b/plugins/docker/templates/docker/rows.html index e65e4b4..9a9b37d 100644 --- a/plugins/docker/templates/docker/rows.html +++ b/plugins/docker/templates/docker/rows.html @@ -1,10 +1,10 @@ {# docker/rows.html — HTMX fragment for the Docker page (#5298–#5301): - a verdict line with only the problem counts that aren't zero; what needs + a verdict line; what needs attention (service.needs_attention, the ranking the MCP tools use); hosts whose Docker is not answering; what failed in 24h, each with its logs; then the swarm services and the standalone containers as two tables that share the filter bar's search, filters and sort (overview.py). #} -{% from "_macros.html" import stat_tile, status_badge, status_dot, sort_th %} +{% from "_macros.html" import status_badge, status_dot, sort_th %} {% from "docker/_usage.html" import cpu, mem, cores %} {% macro th(key, label, cls=none, title=none) -%} {{ sort_th(label, sort_url(key), active=f.sort == key, desc=f.dir == 'desc', cls=cls, title=title) }} @@ -22,20 +22,15 @@ {%- if services_total %} · {{ services_healthy }}/{{ services_total }} services healthy{% endif %} {%- if containers_total %} · {{ running }} container{{ "s" if running != 1 }} running{% if stopped %}, {{ stopped }} stopped{% endif %}{% if services_total %} outside services{% endif %}{% endif %} {%- if docker_hosts %} · {{ docker_hosts }} Docker host{{ "s" if docker_hosts != 1 }}{% endif %} + {%- if stale %} · {{ stale }} stale{% endif %}

-{% if unhealthy or restarting or failures or stale %} -
- {% if unhealthy %}{{ stat_tile("Unhealthy", unhealthy, state="crit") }}{% endif %} - {% if restarting %}{{ stat_tile("Restarting", restarting, state="crit", hint="restarting or crash-looping") }}{% endif %} - {% if failures %}{{ stat_tile("Failed (24h)", failures|length, state="warn", hint="listed below, with their logs", href="#failures") }}{% endif %} - {% if stale %}{{ stat_tile("Stale", stale, state="stale", hint="no recent report") }}{% endif %} -
-{% endif %} {# ── Needs attention ──────────────────────────────────────────────────────── #} {% if attention or failing_services %}
-

Needs attention

+

Needs attention {{ problems }} + {%- if unhealthy %} · {{ unhealthy }} unhealthy{% endif %} + {%- if restarting %} · {{ restarting }} restarting{% endif %}

    {# A swarm service in trouble: Swarm replaces its failed tasks, so the service, not any one container, is what is failing. #} @@ -96,7 +91,7 @@ what died: a service's to its failed task's lines (#5300). ───────────── #} {% if failures %}
    -

    Failed in the last 24h

    +

    Failed in the last 24h {{ failures|length }}

      {% for x in failures %}
    • diff --git a/steward/static/steward.css b/steward/static/steward.css index a295c4b..5fc0d1b 100644 --- a/steward/static/steward.css +++ b/steward/static/steward.css @@ -209,6 +209,9 @@ strong, b, th { font-weight: 500; } .table td.cell-image { max-width: 14rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.8rem; color: var(--text-muted); } /* The Docker page's one-line answer, above its tiles (#5298). */ .docker-verdict { margin: 0 0 0.75rem; font-size: 0.95rem; } +/* A problem section's heading carries its own count, so no tile repeats it (#5214). */ +.section-count { margin-bottom: 0.6rem; } +.section-count > span:first-child { margin-left: 0.25rem; } /* A service's resource history: CPU and memory side by side, each a figure over a line that spans its half (docker/_service_history.html). */ .history-pair { display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); gap: 1.25rem; } diff --git a/tests/plugins/docker/test_docker_page.py b/tests/plugins/docker/test_docker_page.py index 94affe1..3f908de 100644 --- a/tests/plugins/docker/test_docker_page.py +++ b/tests/plugins/docker/test_docker_page.py @@ -72,10 +72,10 @@ async def test_a_crash_loop_comes_first_and_a_stale_row_says_so(app): page = await _rows(app, attention=attention, restarting=1, stale=1, running=1, stopped=1, docker_hosts=1, containers=[_crow(loop, exit_failed=True), _crow(old, stale=True)], containers_total=2) - # #5298: the verdict first; only the tiles that aren't zero. + # #5298: the verdict first; each problem section carries its own count (#5214). assert page.index('class="docker-verdict"') < page.index("Needs attention") assert "1 needs attention" in page and "1 container running, 1 stopped" in page - assert "Unhealthy" not in page and "Failed (24h)" not in page + assert "unhealthy" not in page and "Failed in the last 24h" not in page and "stat-strip" not in page card = page[page.index("Needs attention"):page.index("
    ")] assert 'Restarting' in card assert 'href="/plugins/docker/container/h1/api" title="api">api' in card @@ -84,8 +84,9 @@ async def test_a_crash_loop_comes_first_and_a_stale_row_says_so(app): assert "cache" not in card assert page.index("Needs attention") < page.index("Restarting1' in page - assert 'Stale1' in page + assert ('Needs attention 1 ' + '· 1 restarting') in card + assert '">1 stale' in page[:page.index("

    ")] # stale sits in the verdict assert 'class="badge badge-warn" title="Last report' in page # the stale row # CPU in cores, toned by its share of the host's CPUs through Settings → Thresholds. assert ('Failed (24h)2' in page + # The count sits in the heading of the list it counts: one widget, not a tile beside it. + assert 'Failed in the last 24h 2' in page + assert "stat-tile" not in page async def test_services_and_containers_are_tables_that_sort_and_filter(app):