nazev u sluzby + kontrola u formulare nove

This commit is contained in:
JiriUhlir
2026-07-16 12:05:02 +02:00
parent 3dc0c5b146
commit ec9a643291
2 changed files with 65 additions and 2 deletions
+51 -2
View File
@@ -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(
<div class="service-title">
{health_dot}
<div>
<strong>{app_id}</strong><br>
<strong>{service_label}</strong><br>
<span class="muted">/apps/{app_id}</span>
</div>
</div>
@@ -1334,7 +1365,11 @@ def new_app_form(request: Request, user=Depends(require_developer)):
<form method="post" action="/portal/new-app" id="new-app-form" data-ajax-form="new-app">
<p>
<label>ID služby</label><br>
<input name="app_id" placeholder="gmail-service" required>
<input name="app_id" placeholder="gmail-service" required
pattern="[a-z0-9]([a-z0-9\\-]*[a-z0-9])?" maxlength="{APP_ID_MAX_LENGTH}"
autocomplete="off" spellcheck="false"
title="Jen malá písmena a-z, číslice a pomlčky; musí začínat i končit písmenem nebo číslicí."><br>
<span class="muted">Jen malá písmena, číslice a pomlčky (např. gmail-service). ID se použije jako název Docker kontejneru, Gitea repozitáře a v URL — později ho nelze změnit.</span>
</p>
<p>
@@ -1376,6 +1411,20 @@ def create_app(
cpus: str = Form(""),
user=Depends(require_developer),
):
# Normalizace + validace ID: ID musí být vždy lowercase a splňovat omezení Dockeru,
# DNS a Gitea (viz validate_new_app_id). Bez toho by create script vytvořil kontejner
# nebo repozitář, který později nejde nasadit či adresovat.
app_id = clean_optional(app_id).lower()
app_name = clean_optional(app_name)
id_error = validate_new_app_id(app_id)
if id_error:
return HTMLResponse(id_error, status_code=400)
if not app_name:
return HTMLResponse("Název služby nesmí být prázdný.", status_code=400)
if get_app(app_id):
return HTMLResponse(f"Služba s ID '{app_id}' už existuje.", status_code=400)
selected_template = get_app_template(template, create_enabled=True)
if not selected_template:
return HTMLResponse("Nepodporovaná šablona", status_code=400)
+14
View File
@@ -149,6 +149,20 @@ document.addEventListener("DOMContentLoaded", () => {
const status = document.getElementById("new-app-status");
const result = document.getElementById("new-app-result");
// ID služby musí být vždy lowercase (Docker image/container, Gitea repo, URL).
// Přepisujeme rovnou při psaní, ať uživatel vidí skutečnou hodnotu, která se odešle.
const appIdInput = form.querySelector('input[name="app_id"]');
if (appIdInput) {
appIdInput.addEventListener("input", () => {
const lowered = appIdInput.value.toLowerCase();
if (appIdInput.value !== lowered) {
const cursor = appIdInput.selectionStart;
appIdInput.value = lowered;
appIdInput.setSelectionRange(cursor, cursor);
}
});
}
form.addEventListener("submit", async (event) => {
event.preventDefault();