This commit is contained in:
JiriUhlir
2026-05-27 13:09:34 +02:00
parent dcc1a2f3ad
commit 3e1d1f5360
4 changed files with 79 additions and 79 deletions
+54 -54
View File
@@ -41,22 +41,22 @@ def index():
memory_options = ""
for value, label in [
("", "Default"),
("256m", "256 MB - small service"),
("512m", "512 MB - normal service"),
("1g", "1 GB - larger service"),
("2g", "2 GB - heavy service"),
("", "Výchozí"),
("256m", "256 MB - malá služba"),
("512m", "512 MB - běžná služba"),
("1g", "1 GB - větší služba"),
("2g", "2 GB - náročná služba"),
]:
selected = "selected" if memory == value else ""
memory_options += f'<option value="{value}" {selected}>{label}</option>'
cpu_options = ""
for value, label in [
("", "Default"),
("0.25", "0.25 CPU - very small"),
("0.50", "0.50 CPU - normal"),
("1.00", "1 CPU - full core"),
("2.00", "2 CPU - heavy"),
("", "Výchozí"),
("0.25", "0,25 CPU - velmi malá služba"),
("0.50", "0,50 CPU - běžná služba"),
("1.00", "1 CPU - celé jádro"),
("2.00", "2 CPU - náročná služba"),
]:
selected = "selected" if cpus == value else ""
cpu_options += f'<option value="{value}" {selected}>{label}</option>'
@@ -75,68 +75,68 @@ def index():
<div class="inline-form">
<select name="memory">{memory_options}</select>
<select name="cpus">{cpu_options}</select>
<button type="submit">Apply</button>
<button type="submit">Použít</button>
</div>
<div class="resource-help">
Memory is RAM limit. CPU is max compute share.
Example: 0.50 = half CPU core, 1.00 = one full core.
Paměť je limit RAM. CPU určuje maximální podíl výpočetního výkonu.
Příklad: 0,50 = polovina jádra, 1,00 = celé jádro.
</div>
</form>
</td>
<td>
<details>
<summary>Clone commands</summary>
<summary>Příkazy pro klonování</summary>
<label class="muted">HTTP</label>
<div class="cmd-row">
<input readonly value="{http_clone}" id="http-{app_id}">
<button onclick="copyValue('http-{app_id}')">Copy</button>
<button onclick="copyValue('http-{app_id}')">Kopírovat</button>
</div>
<label class="muted">SSH</label>
<div class="cmd-row">
<input readonly value="{ssh_clone}" id="ssh-{app_id}">
<button onclick="copyValue('ssh-{app_id}')">Copy</button>
<button onclick="copyValue('ssh-{app_id}')">Kopírovat</button>
</div>
</details>
</td>
<td>
<form method="post" action="/portal/delete-app" onsubmit="return confirm('Delete {app_id}? This removes container, image, workspace, catalog entry and Gitea repo.');">
<form method="post" action="/portal/delete-app" onsubmit="return confirm('Smazat {app_id}? Tím se odstraní kontejner, image, workspace, záznam v katalogu a Gitea repozitář.');">
<input type="hidden" name="app_id" value="{app_id}">
<button type="submit" class="danger">Delete</button>
<button type="submit" class="danger">Smazat</button>
</form>
</td>
</tr>
"""
if not rows:
rows = '<tr><td colspan="6">No apps deployed yet.</td></tr>'
rows = '<tr><td colspan="6">Zatím nejsou nasazené žádné aplikace.</td></tr>'
return page(
"Apps",
"Aplikace",
f"""
<div class="grid">
<div class="card">
<h2>Apps</h2>
<p class="muted">Create, deploy, clone, tune resources and delete services.</p>
<a class="btn" href="/portal/new-app">+ New App</a>
<h2>Aplikace</h2>
<p class="muted">Vytváření, nasazení, klonování, nastavení prostředků a mazání služeb.</p>
<a class="btn" href="/portal/new-app">+ Nová aplikace</a>
</div>
<div class="card">
<h2>Backups</h2>
<p class="muted">Create backups and copy restore commands. Restore is intentionally manual and guarded.</p>
<a class="btn" href="/portal/backups">Manage Backups</a>
<h2>Zálohy</h2>
<p class="muted">Vytváření záloh a kopírování příkazů pro obnovu. Obnova je záměrně ruční a chráněná.</p>
<a class="btn" href="/portal/backups">Spravovat zálohy</a>
</div>
</div>
<div class="card">
<h2>Deployed Apps</h2>
<h2>Nasazené aplikace</h2>
<table>
<tr>
<th>App</th>
<th>Aplikace</th>
<th>Status</th>
<th>Docs</th>
<th>Resources</th>
<th>Dokumentace</th>
<th>Prostředky</th>
<th>Git</th>
<th>Actions</th>
<th>Akce</th>
</tr>
{rows}
</table>
@@ -148,34 +148,34 @@ def index():
@router.get("/new-app", response_class=HTMLResponse)
def new_app_form():
return page(
"New App",
"Nová aplikace",
"""
<div class="card">
<h2>Create New App</h2>
<p class="muted">This creates Gitea repo, webhook, local workspace, initial commit and deploys the service.</p>
<h2>Vytvořit novou aplikaci</h2>
<p class="muted">Vytvoří Gitea repozitář, webhook, lokální workspace, první commit a nasadí službu.</p>
<form method="post" action="/portal/new-app">
<p>
<label>App ID</label><br>
<label>ID aplikace</label><br>
<input name="app_id" placeholder="gmail-service" required>
</p>
<p>
<label>App Name</label><br>
<input name="app_name" placeholder="Gmail Service" required>
<label>Název aplikace</label><br>
<input name="app_name" placeholder="Gmail služba" required>
</p>
<p>
<label>Template</label><br>
<label>Šablona</label><br>
<select name="template">
<option value="python-fastapi">Python FastAPI</option>
</select>
</p>
<button type="submit">Create App</button>
<button type="submit">Vytvořit aplikaci</button>
</form>
<p><a href="/portal">&larr; Back</a></p>
<p><a href="/portal">&larr; Zpět</a></p>
</div>
""",
)
@@ -184,7 +184,7 @@ def new_app_form():
@router.post("/new-app", response_class=HTMLResponse)
def create_app(app_id: str = Form(...), app_name: str = Form(...), template: str = Form(...)):
if template != "python-fastapi":
return HTMLResponse("Unsupported template", status_code=400)
return HTMLResponse("Nepodporovaná šablona", status_code=400)
create_result = run_command([NEW_APP_SCRIPT, app_id, app_name])
deploy_result = run_command([DEPLOY_SCRIPT, app_id])
@@ -192,16 +192,16 @@ def create_app(app_id: str = Form(...), app_name: str = Form(...), template: str
status = "OK" if create_result.returncode == 0 and deploy_result.returncode == 0 else "FAILED"
return render_result(
title=f"Create App: {status}",
title=f"Vytvoření aplikace: {status}",
back_url="/portal",
sections=[
("Create Output", create_result.stdout),
("Create Error", create_result.stderr),
("Deploy Output", deploy_result.stdout),
("Deploy Error", deploy_result.stderr),
("Výstup vytvoření", create_result.stdout),
("Chyba vytvoření", create_result.stderr),
("Výstup nasazení", deploy_result.stdout),
("Chyba nasazení", deploy_result.stderr),
],
extra_link=f"/apps/{html.escape(app_id)}/docs",
extra_label="Open Swagger",
extra_label="Otevřít Swagger",
)
@@ -211,9 +211,9 @@ def delete_app(app_id: str = Form(...)):
status = "OK" if result.returncode == 0 else "FAILED"
return render_result(
title=f"Delete App: {status}",
title=f"Smazání aplikace: {status}",
back_url="/portal",
sections=[("Output", result.stdout), ("Error", result.stderr)],
sections=[("Výstup", result.stdout), ("Chyba", result.stderr)],
)
@@ -244,12 +244,12 @@ def update_resources(app_id: str = Form(...), memory: str = Form(""), cpus: str
status = "OK" if compose_result.returncode == 0 and deploy_result.returncode == 0 else "FAILED"
return render_result(
title=f"Update Resources: {status}",
title=f"Úprava prostředků: {status}",
back_url="/portal",
sections=[
("Compose Output", compose_result.stdout),
("Compose Error", compose_result.stderr),
("Deploy Output", deploy_result.stdout),
("Deploy Error", deploy_result.stderr),
("Výstup compose", compose_result.stdout),
("Chyba compose", compose_result.stderr),
("Výstup nasazení", deploy_result.stdout),
("Chyba nasazení", deploy_result.stderr),
],
)
+20 -20
View File
@@ -35,52 +35,52 @@ def backups_page():
<td>
<div class="copy-row">
<input readonly value="{backup_path}">
<button type="button" class="copy-button" data-copy-value="{backup_path}" onclick="copyText(this)">Copy</button>
<button type="button" class="copy-button" data-copy-value="{backup_path}" onclick="copyText(this)">Kopírovat</button>
</div>
</td>
<td>
<div class="copy-row">
<input readonly value="{restore_cmd}">
<button type="button" class="copy-button" data-copy-value="{restore_cmd}" onclick="copyText(this)">Copy</button>
<button type="button" class="copy-button" data-copy-value="{restore_cmd}" onclick="copyText(this)">Kopírovat</button>
</div>
<div class="muted">Restore is dangerous and must be run manually over SSH.</div>
<div class="muted">Obnova je nebezpečná a musí se spustit ručně přes SSH.</div>
</td>
<td class="actions-cell">
<form method="post" action="/portal/backups/delete" onsubmit="return confirm('Delete backup {backup_name}?');">
<form method="post" action="/portal/backups/delete" onsubmit="return confirm('Smazat zálohu {backup_name}?');">
<input type="hidden" name="backup_path" value="{backup_path}">
<button type="submit" class="danger">Delete</button>
<button type="submit" class="danger">Smazat</button>
</form>
</td>
</tr>
"""
if not rows:
rows = '<tr><td colspan="4">No backups found.</td></tr>'
rows = '<tr><td colspan="4">Nebyly nalezeny žádné zálohy.</td></tr>'
return page(
"Backups",
"Zálohy",
f"""
<div class="card">
<h2>Backup Management</h2>
<h2>Správa záloh</h2>
<p class="muted">
Backups do not include the backup directory itself.
Restoring an older backup should not delete newer .tar.gz files.
Restore remains manual to avoid accidental overwrite.
Zálohy neobsahují samotný adresář se zálohami.
Obnova starší zálohy by neměla smazat novější soubory .tar.gz.
Obnova zůstává ruční, aby nedošlo k nechtěnému přepsání dat.
</p>
<form method="post" action="/portal/backups/create">
<button type="submit">Create Backup</button>
<button type="submit">Vytvořit zálohu</button>
</form>
</div>
<div class="card">
<h2>Available Backups</h2>
<h2>Dostupné zálohy</h2>
<table>
<tr>
<th>Backup</th>
<th>Path</th>
<th>Restore Command</th>
<th>Actions</th>
<th>Záloha</th>
<th>Cesta</th>
<th>Příkaz pro obnovu</th>
<th>Akce</th>
</tr>
{rows}
</table>
@@ -95,9 +95,9 @@ def create_backup():
status = "OK" if result.returncode == 0 else "FAILED"
return render_result(
title=f"Create Backup: {status}",
title=f"Vytvoření zálohy: {status}",
back_url="/portal/backups",
sections=[("Output", result.stdout), ("Error", result.stderr)],
sections=[("Výstup", result.stdout), ("Chyba", result.stderr)],
)
@@ -106,7 +106,7 @@ def delete_backup(backup_path: str = Form(...)):
target = Path(backup_path)
if not is_backup_path(target):
return HTMLResponse("Invalid backup path", status_code=400)
return HTMLResponse("Neplatná cesta k záloze", status_code=400)
target = target.resolve()
if target.exists():