diff --git a/plugins/docker/routes.py b/plugins/docker/routes.py
index 5cb6d78..3b636f0 100644
--- a/plugins/docker/routes.py
+++ b/plugins/docker/routes.py
@@ -237,6 +237,10 @@ async def widget():
{c.host_id for c in all_containers}
| {n.host_id for n in nodes} | {s.host_id for s in services},
)
+ deaths = await docker_svc.recent_failures_by_container(db, now - timedelta(hours=24))
+ sick = await docker_svc.unhealthy_since(
+ db, [(c.host_id, c.name) for c in all_containers
+ if not c.service_name and (c.health or "") == "unhealthy"])
# Swarm tasks collapse into per-service rows (each replica tagged with its
# host); non-swarm containers stay grouped by host.
@@ -260,7 +264,19 @@ async def widget():
# The widget-shape order (UI & Design ruling): what is wrong first, then
# the healthy rest one line each, collapsing past HEALTHY_SHOWN.
- bad_containers = sorted((c for c in standalone if troubled(c)), key=lambda c: c.name)
+ # Worst problem first, each with since when, as the Docker page has them.
+ hour_ago, crash = now - timedelta(hours=1), docker_svc.crash_loop_deaths(current_app.config)
+ trouble = {}
+ for c in standalone:
+ if troubled(c):
+ died = deaths.get((c.host_id, c.name), [])
+ problems = docker_svc.container_problems(
+ c, sum(1 for e in died if e.at >= hour_ago), bool(died), crash)
+ trouble[(c.host_id, c.name)] = (
+ docker_svc.PROBLEMS.index(problems[0]) if problems else len(docker_svc.PROBLEMS),
+ docker_svc.problem_since(problems, died, hour_ago, sick.get((c.host_id, c.name))))
+ bad_containers = sorted((c for c in standalone if troubled(c)),
+ key=lambda c: (trouble[(c.host_id, c.name)][0], c.name))
healthy = ([{"kind": "service", "s": s} for s in swarm_services if s["state"] == "healthy"]
+ [{"kind": "container", "c": c} for c in sorted(running, key=lambda c: c.name)
if not troubled(c)]
@@ -271,6 +287,7 @@ async def widget():
"docker/widget.html",
bad_services=[s for s in swarm_services if s["state"] != "healthy"],
bad_containers=bad_containers,
+ bad_since={key: since for key, (_rank, since) in trouble.items()},
healthy=healthy[:HEALTHY_SHOWN], healthy_more=max(0, len(healthy) - HEALTHY_SHOWN),
services_total=len(swarm_services),
services_healthy=sum(s["state"] == "healthy" for s in swarm_services),
diff --git a/plugins/docker/templates/docker/widget.html b/plugins/docker/templates/docker/widget.html
index 395ca89..93a9e5e 100644
--- a/plugins/docker/templates/docker/widget.html
+++ b/plugins/docker/templates/docker/widget.html
@@ -35,7 +35,8 @@
{%- if session.user_id %}{{ c.name }}{% else %}{{ c.name }}{% endif %}
- {%- if c.health == 'unhealthy' %}unhealthy{% elif c.status == 'restarting' %}restarting{% else %}exited {{ c.exit_code }}{% if c.oom_killed %}, out of memory{% endif %}{% endif %} · {{ host_name.get(c.host_id, c.host_id) }}
+ {%- if c.health == 'unhealthy' %}unhealthy{% elif c.status == 'restarting' %}restarting{% else %}exited {{ c.exit_code }}{% if c.oom_killed %}, out of memory{% endif %}{% endif %}
+ {%- set since = (bad_since or {}).get((c.host_id, c.name)) %}{% if since %} since {{ since|ago }}{% endif %} · {{ host_name.get(c.host_id, c.host_id) }}
diff --git a/tests/plugins/docker/test_docker_page.py b/tests/plugins/docker/test_docker_page.py
index ab59639..9b5b1ab 100644
--- a/tests/plugins/docker/test_docker_page.py
+++ b/tests/plugins/docker/test_docker_page.py
@@ -299,6 +299,16 @@ async def test_the_services_widget_lists_what_is_wrong_then_packs_the_healthy(ap
assert not hex_colours(page), hex_colours(page)
+async def test_a_troubled_container_says_since_when_on_the_widget(app):
+ """#5810: a container in trouble carries its since, as the Docker page does."""
+ sick = _c("backup_cron", host_id="h1", status="running", health="unhealthy")
+ page = await _services_widget(
+ app, bad_containers=[sick],
+ bad_since={("h1", "backup_cron"): datetime.now(timezone.utc) - timedelta(minutes=40)})
+ page = " ".join(page.split())
+ assert "unhealthy since