Phase 3: env masking, image updates, port conflicts, resources, templates (0.3.0)
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>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
b553c1b861
commit
22d9864436
@@ -0,0 +1,64 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user