diff --git a/app/routes/apps.py b/app/routes/apps.py index fd23872..2637304 100644 --- a/app/routes/apps.py +++ b/app/routes/apps.py @@ -1,5 +1,6 @@ import html import math +import re from urllib.parse import quote, urlencode from fastapi import APIRouter, Depends, Form, HTTPException, Query, Request @@ -78,6 +79,32 @@ METADATA_FIELDS = ( ) +# ID služby se používá jako Docker container/image name, Compose service name, Gitea repo, +# adresář workspace a část URL/domény. Nejpřísnější je Docker image name (jen lowercase) +# a DNS label (max 63 znaků, alfanumerické na začátku i konci). Proto povolujeme pouze +# malá písmena a-z, číslice a pomlčky uvnitř, bez diakritiky, podtržítek a teček. +APP_ID_MAX_LENGTH = 63 +APP_ID_RE = re.compile(r"^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$") + + +def validate_new_app_id(app_id: str) -> str | None: + """Vrátí chybovou hlášku, pokud ID služby nesplňuje omezení, jinak None.""" + if not app_id: + return "ID služby nesmí být prázdné." + if app_id != app_id.lower(): + return "ID služby musí být malými písmeny (lowercase)." + if len(app_id) > APP_ID_MAX_LENGTH: + return f"ID služby může mít nejvýše {APP_ID_MAX_LENGTH} znaků (limit DNS/Docker)." + if not APP_ID_RE.match(app_id): + return ( + "Neplatné ID služby. Povolena jsou jen malá písmena a-z, číslice a pomlčky; " + "musí začínat i končit písmenem nebo číslicí (např. gmail-service). " + "Bez diakritiky, mezer, podtržítek a teček — ID se používá jako název " + "Docker kontejneru, image a Gitea repozitáře." + ) + return None + + def clean_optional(value: str | None) -> str: return (value or "").strip() @@ -330,6 +357,10 @@ def apps_page( for index, item in enumerate(apps, start=1): app_id = html.escape(item.get("id", "")) app_url_id = quote(item.get("id", ""), safe="") + app_name = html.escape(item.get("name", "") or "") + # Ve sloupci Služba zobrazujeme "Název (id)"; když název chybí nebo je shodný s ID, + # zůstane jen ID bez zdvojení. + service_label = f"{app_name} ({app_id})" if app_name and app_name != app_id else app_id resource_modal_id = f"resources-{page_number}-{index}" status = html.escape(item.get("status", "")) health = latest_health.get(item.get("id", "")) or {} @@ -422,7 +453,7 @@ def apps_page(