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
@@ -144,6 +144,60 @@ def set_privileged(yaml_str: str, service: str, value: bool) -> str:
|
||||
return _dump(data)
|
||||
|
||||
|
||||
def set_resources(
|
||||
yaml_str: str,
|
||||
service: str,
|
||||
*,
|
||||
cpus: float | None = None,
|
||||
memory: str | None = None,
|
||||
cpus_reserve: float | None = None,
|
||||
memory_reserve: str | None = None,
|
||||
) -> str:
|
||||
"""Set deploy.resources limits/reservations for a service.
|
||||
|
||||
Pass None / empty to clear an individual value.
|
||||
"""
|
||||
data = _load(yaml_str)
|
||||
svc = _get_service(data, service)
|
||||
deploy = svc.setdefault("deploy", {})
|
||||
resources = deploy.setdefault("resources", {})
|
||||
|
||||
limits = resources.get("limits", {})
|
||||
if cpus:
|
||||
limits["cpus"] = str(cpus)
|
||||
else:
|
||||
limits.pop("cpus", None)
|
||||
if memory:
|
||||
limits["memory"] = memory
|
||||
else:
|
||||
limits.pop("memory", None)
|
||||
if limits:
|
||||
resources["limits"] = limits
|
||||
else:
|
||||
resources.pop("limits", None)
|
||||
|
||||
reservations = resources.get("reservations", {})
|
||||
if cpus_reserve:
|
||||
reservations["cpus"] = str(cpus_reserve)
|
||||
else:
|
||||
reservations.pop("cpus", None)
|
||||
if memory_reserve:
|
||||
reservations["memory"] = memory_reserve
|
||||
else:
|
||||
reservations.pop("memory", None)
|
||||
# Don't drop reservations entirely — it may hold GPU devices.
|
||||
if reservations:
|
||||
resources["reservations"] = reservations
|
||||
elif "reservations" in resources and not resources["reservations"]:
|
||||
resources.pop("reservations", None)
|
||||
|
||||
if not resources:
|
||||
deploy.pop("resources", None)
|
||||
if not deploy:
|
||||
svc.pop("deploy", None)
|
||||
return _dump(data)
|
||||
|
||||
|
||||
def list_services(yaml_str: str) -> list[str]:
|
||||
data = _load(yaml_str)
|
||||
return list(data["services"].keys())
|
||||
|
||||
Reference in New Issue
Block a user