feat(docker): the Swarm page is laid out by node: role, what its containers use, and the services placed on each (#5303)
#5303 "Swarm page laid out by node: role, resources and the tasks placed on each" A verdict line first, then the nodes that are down or drained and the services short of replicas, then one card per node. A node with a Steward agent shows what its swarm tasks use against the machine's cores and memory, service by service. A node without one lists Swarm's placement, by count. The services table moved to the Docker overview, where it can be filtered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@ from .swarm_view import (
|
||||
build_swarm_services, failing_services, parse_tasks, replaced_tasks, services_problems_first,
|
||||
task_summary, trouble_since,
|
||||
)
|
||||
from .swarm_nodes import node_cards
|
||||
from .overview import (
|
||||
DOCKER_SORTS, DOCKER_STATES, NO_STACK, container_row, filter_rows, service_row, sort_rows,
|
||||
stack_of,
|
||||
@@ -497,7 +498,9 @@ async def container_detail(host_id: str, name: str):
|
||||
@docker_bp.get("/swarm")
|
||||
@require_role(UserRole.viewer)
|
||||
async def swarm():
|
||||
"""Swarm topology page: services with replica health, nodes, placement.
|
||||
"""The Swarm page, laid out by node (#5303): each node's role, whether it
|
||||
takes work, what its containers use against what the machine has, and the
|
||||
services placed on it; services short of replicas first.
|
||||
|
||||
Swarm services/nodes are cluster-global — every manager's API returns the
|
||||
same list — but each manager reports them independently (rows keyed by
|
||||
@@ -515,6 +518,20 @@ async def swarm():
|
||||
DockerSwarmNode.role, DockerSwarmNode.hostname)
|
||||
)).scalars())
|
||||
hosts = await docker_svc.host_map(db, {s.host_id for s in services} | {n.host_id for n in nodes})
|
||||
containers = await docker_svc.list_containers(db)
|
||||
# The Steward host on each node: the one whose agent reports its
|
||||
# containers, else the host named as the node is.
|
||||
node_host = {c.node_id: c.host_id for c in containers if c.node_id}
|
||||
unmatched = {(n.hostname or "").lower() for n in nodes if n.node_id not in node_host} - {""}
|
||||
if unmatched:
|
||||
by_name = {h.name.lower(): h for h in (await db.execute(
|
||||
select(Host).where(func.lower(Host.name).in_(unmatched)))).scalars()}
|
||||
for n in nodes:
|
||||
h = by_name.get((n.hostname or "").lower())
|
||||
if n.node_id not in node_host and h is not None:
|
||||
node_host[n.node_id] = h.id
|
||||
hosts.update(await docker_svc.host_map(db, set(node_host.values()) - set(hosts)))
|
||||
capacity = await docker_svc.host_capacity(db, set(node_host.values()))
|
||||
|
||||
# ── Group reporting managers into swarms by shared node membership ──────────
|
||||
manager_ids = {s.host_id for s in services} | {n.host_id for n in nodes}
|
||||
@@ -586,17 +603,16 @@ async def swarm():
|
||||
**summary,
|
||||
"trouble_since": trouble_since(tasks, state, summary["crash_looping"], now),
|
||||
})
|
||||
# Problems first: a node that is down or not taking work, then by role.
|
||||
node_rows = sorted(
|
||||
node_by_id.values(), key=lambda n: (
|
||||
n.status == "ready" and n.availability == "active",
|
||||
n.role, (n.hostname or n.node_id).lower())
|
||||
)
|
||||
manager_names = sorted(hosts[m].name if m in hosts else m for m in members)
|
||||
swarms.append({
|
||||
"managers": manager_names,
|
||||
"services": services_problems_first(svc_dicts),
|
||||
"nodes": node_rows,
|
||||
"services_total": len(svc_dicts),
|
||||
"services_healthy": sum(d["state"] == "healthy" for d in svc_dicts),
|
||||
"troubled": failing_services(svc_dicts),
|
||||
"nodes": node_cards(
|
||||
node_by_id.values(), svc_by_name.values(), containers, node_host=node_host,
|
||||
host_names={h.id: h.name for h in hosts.values()}, capacity=capacity,
|
||||
service_state={d["name"]: d["state"] for d in svc_dicts}),
|
||||
})
|
||||
|
||||
swarms.sort(key=lambda g: g["managers"][0].lower() if g["managers"] else "")
|
||||
|
||||
Reference in New Issue
Block a user