Backend:
- update_service: registry manifest digest check (Docker Hub/ghcr/lscr/private
v2 token auth) vs local RepoDigests; in-memory cache + background loop
- port_service: parse compose ports, check /proc/net/tcp[6] + docker bindings
- template_service + bundled templates (jellyfin/vaultwarden/uptime-kuma/
paperless-ngx/gitea) with {{VAR}} placeholders; custom templates in DB
- compose_edit set_resources (deploy.resources.limits/reservations)
- routers: images, ports, templates, editor/set-resources
- Template model; background update task wired into lifespan
Frontend:
- EnvEditor (table + raw, sensitive masking, quick-insert)
- Images page + UpdateBadge + dashboard 'updates available' banner
- PortConflictDialog pre-deploy check on Deploy
- ResourcePanel (CPU/RAM sliders) as editor Limits tab
- Templates page with per-variable instantiate form
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
65 lines
1.9 KiB
Python
65 lines
1.9 KiB
Python
"""Image listing + update-check endpoints."""
|
|
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter, Depends
|
|
|
|
from auth import get_current_user, require_admin
|
|
from docker_client import DockerError, get_client, safe_call
|
|
from models.user import User
|
|
from services import update_service
|
|
|
|
router = APIRouter(prefix="/api/images", tags=["images"])
|
|
|
|
COMPOSE_PROJECT_LABEL = "com.docker.compose.project"
|
|
|
|
|
|
@router.get("")
|
|
def list_images(_user: User = Depends(get_current_user)) -> list[dict]:
|
|
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:
|
|
upd = cache.get(tag)
|
|
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": upd,
|
|
}
|
|
)
|
|
result.sort(key=lambda r: r["tag"])
|
|
return result
|
|
|
|
|
|
@router.get("/updates")
|
|
def updates(_user: User = Depends(get_current_user)) -> dict:
|
|
return update_service.get_cache()
|
|
|
|
|
|
@router.post("/check")
|
|
async def check(_user: User = Depends(require_admin)) -> dict:
|
|
return await update_service.check_all()
|