Networks and Images are now per-host, rendered as a section for the local host
plus one per registered agent (like the Stacks page).
- agent_app.py: new /agent/networks (list/inspect/containers/connect/disconnect/
create/delete/prune) and /agent/images (list/updates/check), reusing
network_service and a new image_service; DockerError mapped to HTTP status
(forbidden -> 400 so the proxy doesn't treat it as a token failure).
- routers/agents.py: proxy routes at /api/agents/{id}/networks/* and
/api/agents/{id}/images/*, audit-logging mutations.
- services/image_service.py: extracted the image-listing logic so the central
router and the agent share it.
- Frontend: networksApi/imagesApi take an optional agentId; Networks/Images
pages render NetworksSection/ImagesSection per host with a shared HostHeader.
Remote "Prune unused" networks resolves the address-pool-exhaustion deploy
error from the UI.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
48 lines
1.5 KiB
Python
48 lines
1.5 KiB
Python
"""Image listing — shared by the central images router and the agent."""
|
|
from __future__ import annotations
|
|
|
|
from docker_client import DockerError, get_client, safe_call
|
|
from services import update_service
|
|
|
|
COMPOSE_PROJECT_LABEL = "com.docker.compose.project"
|
|
|
|
|
|
def list_images() -> list[dict]:
|
|
"""Return one row per image tag, annotated with using-stacks + update status."""
|
|
try:
|
|
client = get_client()
|
|
images = safe_call(client.images.list)
|
|
containers = safe_call(client.containers.list, all=True)
|
|
except DockerError:
|
|
return []
|
|
|
|
# Map image ref -> stacks using it.
|
|
usage: dict[str, set[str]] = {}
|
|
for c in containers:
|
|
ref = c.attrs.get("Config", {}).get("Image")
|
|
stack = c.labels.get(COMPOSE_PROJECT_LABEL)
|
|
if ref:
|
|
usage.setdefault(ref, set())
|
|
if stack:
|
|
usage[ref].add(stack)
|
|
|
|
cache = update_service.get_cache()
|
|
result = []
|
|
for img in images:
|
|
tags = img.tags or []
|
|
if not tags:
|
|
continue
|
|
for tag in tags:
|
|
result.append(
|
|
{
|
|
"id": img.short_id,
|
|
"tag": tag,
|
|
"size": img.attrs.get("Size", 0),
|
|
"created": img.attrs.get("Created"),
|
|
"stacks": sorted(usage.get(tag, set())),
|
|
"update": cache.get(tag),
|
|
}
|
|
)
|
|
result.sort(key=lambda r: r["tag"])
|
|
return result
|