feat(widgets): a troubled container on the Docker widget says since when (#5810)
Its problems and since come from the same container_problems and problem_since the Docker page uses, read from the day's deaths and health changes; troubled containers sort worst problem first, then name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -35,7 +35,8 @@
|
||||
<span class="w-label" title="{{ c.name }}">
|
||||
{%- if session.user_id %}<a href="/plugins/docker/container/{{ c.host_id }}/{{ c.name }}">{{ c.name }}</a>{% else %}{{ c.name }}{% endif %}
|
||||
<span class="w-sub tone-crit">
|
||||
{%- 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) }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -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 <time" in page and "40m ago" in page and "· VDNT-Docker02" in page
|
||||
|
||||
|
||||
async def test_the_services_widget_says_when_all_is_well(app):
|
||||
page = await _services_widget(app, services_total=2, services_healthy=2, running_count=1,
|
||||
healthy=[{"kind": "service", "s": _widget_svc("a")}])
|
||||
|
||||
Reference in New Issue
Block a user