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):