Co se změnilo:
app_templates je teď jediný source of truth. Detail služby načítá šablony přes is_enabled = 1. Vytvoření služby načítá šablony přes is_enabled = 1 AND create_enabled = 1. Select hodnoty používají app_templates.id, takže fastapi odpovídá existující službě microsoft-365-service. Při vytvoření služby se z vybrané šablony nastaví template, runtime, language, container_port a health_url. Neznámá šablona se zobrazí jako Neznámá šablona a detail nespadne. Hardcoded python-fastapi / seznam šablon byl odstraněn.
This commit is contained in:
+75
-3
@@ -82,14 +82,26 @@ def get_app(app_id: str):
|
|||||||
return dict(row) if row else None
|
return dict(row) if row else None
|
||||||
|
|
||||||
|
|
||||||
def get_app_templates():
|
def get_app_templates(create_enabled: bool = False):
|
||||||
run_migrations()
|
run_migrations()
|
||||||
con = get_connection()
|
con = get_connection()
|
||||||
|
|
||||||
|
create_filter = "AND create_enabled = 1" if create_enabled else ""
|
||||||
rows = con.execute(
|
rows = con.execute(
|
||||||
"""
|
f"""
|
||||||
SELECT name, runtime, description
|
SELECT
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
runtime,
|
||||||
|
language,
|
||||||
|
description,
|
||||||
|
default_port,
|
||||||
|
default_health_path,
|
||||||
|
COALESCE(is_enabled, 1) AS is_enabled,
|
||||||
|
COALESCE(create_enabled, 0) AS create_enabled
|
||||||
FROM app_templates
|
FROM app_templates
|
||||||
|
WHERE COALESCE(is_enabled, 1) = 1
|
||||||
|
{create_filter}
|
||||||
ORDER BY name
|
ORDER BY name
|
||||||
"""
|
"""
|
||||||
).fetchall()
|
).fetchall()
|
||||||
@@ -98,6 +110,66 @@ def get_app_templates():
|
|||||||
return [dict(row) for row in rows]
|
return [dict(row) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
|
def get_app_template(template_id: str, create_enabled: bool = False):
|
||||||
|
run_migrations()
|
||||||
|
con = get_connection()
|
||||||
|
|
||||||
|
create_filter = "AND COALESCE(create_enabled, 0) = 1" if create_enabled else ""
|
||||||
|
row = con.execute(
|
||||||
|
f"""
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
runtime,
|
||||||
|
language,
|
||||||
|
description,
|
||||||
|
default_port,
|
||||||
|
default_health_path,
|
||||||
|
COALESCE(is_enabled, 1) AS is_enabled,
|
||||||
|
COALESCE(create_enabled, 0) AS create_enabled
|
||||||
|
FROM app_templates
|
||||||
|
WHERE id = ?
|
||||||
|
AND COALESCE(is_enabled, 1) = 1
|
||||||
|
{create_filter}
|
||||||
|
""",
|
||||||
|
(template_id,),
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
con.close()
|
||||||
|
return dict(row) if row else None
|
||||||
|
|
||||||
|
|
||||||
|
def update_app_template_metadata(app_id: str, metadata: dict):
|
||||||
|
run_migrations()
|
||||||
|
con = get_connection()
|
||||||
|
|
||||||
|
con.execute(
|
||||||
|
"""
|
||||||
|
UPDATE apps
|
||||||
|
SET name = ?,
|
||||||
|
template = ?,
|
||||||
|
runtime = ?,
|
||||||
|
language = ?,
|
||||||
|
health_url = ?,
|
||||||
|
container_port = ?,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = ?
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
metadata.get("name") or None,
|
||||||
|
metadata.get("template") or None,
|
||||||
|
metadata.get("runtime") or None,
|
||||||
|
metadata.get("language") or None,
|
||||||
|
metadata.get("health_url") or None,
|
||||||
|
metadata.get("container_port"),
|
||||||
|
app_id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
con.commit()
|
||||||
|
con.close()
|
||||||
|
|
||||||
|
|
||||||
def update_app_metadata(app_id: str, metadata: dict):
|
def update_app_metadata(app_id: str, metadata: dict):
|
||||||
run_migrations()
|
run_migrations()
|
||||||
con = get_connection()
|
con = get_connection()
|
||||||
|
|||||||
+18
-1
@@ -88,9 +88,15 @@ def run_migrations():
|
|||||||
con.execute(
|
con.execute(
|
||||||
"""
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS app_templates (
|
CREATE TABLE IF NOT EXISTS app_templates (
|
||||||
name TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
runtime TEXT,
|
runtime TEXT,
|
||||||
|
language TEXT,
|
||||||
description TEXT,
|
description TEXT,
|
||||||
|
default_port INTEGER,
|
||||||
|
default_health_path TEXT,
|
||||||
|
is_enabled INTEGER NOT NULL DEFAULT 1,
|
||||||
|
create_enabled INTEGER NOT NULL DEFAULT 0,
|
||||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
)
|
)
|
||||||
@@ -125,8 +131,15 @@ def run_migrations():
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
for statement in (
|
for statement in (
|
||||||
|
"ALTER TABLE app_templates ADD COLUMN id TEXT",
|
||||||
|
"ALTER TABLE app_templates ADD COLUMN name TEXT",
|
||||||
"ALTER TABLE app_templates ADD COLUMN runtime TEXT",
|
"ALTER TABLE app_templates ADD COLUMN runtime TEXT",
|
||||||
|
"ALTER TABLE app_templates ADD COLUMN language TEXT",
|
||||||
"ALTER TABLE app_templates ADD COLUMN description TEXT",
|
"ALTER TABLE app_templates ADD COLUMN description TEXT",
|
||||||
|
"ALTER TABLE app_templates ADD COLUMN default_port INTEGER",
|
||||||
|
"ALTER TABLE app_templates ADD COLUMN default_health_path TEXT",
|
||||||
|
"ALTER TABLE app_templates ADD COLUMN is_enabled INTEGER NOT NULL DEFAULT 1",
|
||||||
|
"ALTER TABLE app_templates ADD COLUMN create_enabled INTEGER NOT NULL DEFAULT 0",
|
||||||
"ALTER TABLE app_templates ADD COLUMN created_at TEXT",
|
"ALTER TABLE app_templates ADD COLUMN created_at TEXT",
|
||||||
"ALTER TABLE app_templates ADD COLUMN updated_at TEXT",
|
"ALTER TABLE app_templates ADD COLUMN updated_at TEXT",
|
||||||
"ALTER TABLE app_variables ADD COLUMN created_at TEXT",
|
"ALTER TABLE app_variables ADD COLUMN created_at TEXT",
|
||||||
@@ -136,6 +149,10 @@ def run_migrations():
|
|||||||
con.execute(statement)
|
con.execute(statement)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
try:
|
||||||
|
con.execute("UPDATE app_templates SET id = name WHERE id IS NULL OR id = ''")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
con.execute("CREATE INDEX IF NOT EXISTS idx_app_variables_app_id ON app_variables(app_id)")
|
con.execute("CREATE INDEX IF NOT EXISTS idx_app_variables_app_id ON app_variables(app_id)")
|
||||||
con.execute("CREATE INDEX IF NOT EXISTS idx_service_incidents_status_started ON service_incidents(status, started_at)")
|
con.execute("CREATE INDEX IF NOT EXISTS idx_service_incidents_status_started ON service_incidents(status, started_at)")
|
||||||
con.execute("CREATE INDEX IF NOT EXISTS idx_service_incidents_service_started ON service_incidents(service_id, started_at)")
|
con.execute("CREATE INDEX IF NOT EXISTS idx_service_incidents_service_started ON service_incidents(service_id, started_at)")
|
||||||
|
|||||||
+42
-11
@@ -19,12 +19,14 @@ from ..db.apps import (
|
|||||||
create_app_variable,
|
create_app_variable,
|
||||||
delete_app_variable,
|
delete_app_variable,
|
||||||
get_app,
|
get_app,
|
||||||
|
get_app_template,
|
||||||
get_app_deployments,
|
get_app_deployments,
|
||||||
get_app_templates,
|
get_app_templates,
|
||||||
get_app_variables,
|
get_app_variables,
|
||||||
get_apps,
|
get_apps,
|
||||||
update_app_metadata,
|
update_app_metadata,
|
||||||
update_app_resources,
|
update_app_resources,
|
||||||
|
update_app_template_metadata,
|
||||||
update_app_variable,
|
update_app_variable,
|
||||||
)
|
)
|
||||||
from ..db.audit import log_audit_event
|
from ..db.audit import log_audit_event
|
||||||
@@ -73,9 +75,11 @@ def bool_checked(value) -> str:
|
|||||||
return " checked" if value else ""
|
return " checked" if value else ""
|
||||||
|
|
||||||
|
|
||||||
def render_template_options(templates: list[dict], selected_template: str) -> str:
|
def render_template_options(templates: list[dict], selected_template: str, include_blank: bool = True) -> str:
|
||||||
options = ['<option value="">Bez šablony</option>']
|
options = ['<option value="">Bez šablony</option>'] if include_blank else []
|
||||||
|
selected_exists = not selected_template
|
||||||
for template in templates:
|
for template in templates:
|
||||||
|
template_id = template.get("id", "") or ""
|
||||||
name = template.get("name", "") or ""
|
name = template.get("name", "") or ""
|
||||||
runtime = template.get("runtime", "") or ""
|
runtime = template.get("runtime", "") or ""
|
||||||
description = template.get("description", "") or ""
|
description = template.get("description", "") or ""
|
||||||
@@ -84,13 +88,28 @@ def render_template_options(templates: list[dict], selected_template: str) -> st
|
|||||||
label_parts.append(runtime)
|
label_parts.append(runtime)
|
||||||
if description:
|
if description:
|
||||||
label_parts.append(description)
|
label_parts.append(description)
|
||||||
selected = " selected" if selected_template == name else ""
|
selected = " selected" if selected_template == template_id else ""
|
||||||
|
if selected:
|
||||||
|
selected_exists = True
|
||||||
options.append(
|
options.append(
|
||||||
f'<option value="{html.escape(name)}"{selected}>{html.escape(" - ".join(label_parts))}</option>'
|
f'<option value="{html.escape(template_id)}"{selected}>{html.escape(" - ".join(label_parts))}</option>'
|
||||||
|
)
|
||||||
|
if not selected_exists:
|
||||||
|
options.append(
|
||||||
|
f'<option value="{html.escape(selected_template)}" selected>Neznámá šablona</option>'
|
||||||
)
|
)
|
||||||
return "".join(options)
|
return "".join(options)
|
||||||
|
|
||||||
|
|
||||||
|
def render_template_label(templates: list[dict], template_id: str) -> str:
|
||||||
|
if not template_id:
|
||||||
|
return ""
|
||||||
|
for template in templates:
|
||||||
|
if template.get("id") == template_id:
|
||||||
|
return html.escape(template.get("name", "") or template_id)
|
||||||
|
return "Neznámá šablona"
|
||||||
|
|
||||||
|
|
||||||
def diff_metadata(before: dict, after: dict) -> dict:
|
def diff_metadata(before: dict, after: dict) -> dict:
|
||||||
changes = {}
|
changes = {}
|
||||||
for field in METADATA_FIELDS:
|
for field in METADATA_FIELDS:
|
||||||
@@ -404,7 +423,6 @@ def app_detail(app_id: str, request: Request, user=Depends(require_user)):
|
|||||||
updated_at = html.escape(app.get("updated_at", "") or "")
|
updated_at = html.escape(app.get("updated_at", "") or "")
|
||||||
description = html.escape(app.get("description", "") or "")
|
description = html.escape(app.get("description", "") or "")
|
||||||
owner = html.escape(app.get("owner", "") or "")
|
owner = html.escape(app.get("owner", "") or "")
|
||||||
template_value = html.escape(app.get("template", "") or "")
|
|
||||||
runtime = html.escape(app.get("runtime", "") or "")
|
runtime = html.escape(app.get("runtime", "") or "")
|
||||||
repository_url = html.escape(app.get("repository_url", "") or "")
|
repository_url = html.escape(app.get("repository_url", "") or "")
|
||||||
repository_name = html.escape(app.get("repository_name", "") or "")
|
repository_name = html.escape(app.get("repository_name", "") or "")
|
||||||
@@ -416,6 +434,7 @@ def app_detail(app_id: str, request: Request, user=Depends(require_user)):
|
|||||||
is_enabled = bool(app.get("is_enabled"))
|
is_enabled = bool(app.get("is_enabled"))
|
||||||
templates = get_app_templates()
|
templates = get_app_templates()
|
||||||
template_options = render_template_options(templates, app.get("template", "") or "")
|
template_options = render_template_options(templates, app.get("template", "") or "")
|
||||||
|
template_value = render_template_label(templates, app.get("template", "") or "")
|
||||||
variables = get_app_variables(app.get("id", ""))
|
variables = get_app_variables(app.get("id", ""))
|
||||||
incidents = get_service_incidents(app.get("id", ""), limit=20)
|
incidents = get_service_incidents(app.get("id", ""), limit=20)
|
||||||
current_health = get_service_health(app.get("id", ""))
|
current_health = get_service_health(app.get("id", ""))
|
||||||
@@ -867,9 +886,10 @@ def redeploy_app(app_id: str, user=Depends(require_user)):
|
|||||||
|
|
||||||
@router.get("/new-app", response_class=HTMLResponse)
|
@router.get("/new-app", response_class=HTMLResponse)
|
||||||
def new_app_form(request: Request, user=Depends(require_user)):
|
def new_app_form(request: Request, user=Depends(require_user)):
|
||||||
|
template_options = render_template_options(get_app_templates(create_enabled=True), "", include_blank=False)
|
||||||
return page(
|
return page(
|
||||||
"Nová služba",
|
"Nová služba",
|
||||||
"""
|
f"""
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>Vytvořit novou službu</h2>
|
<h2>Vytvořit novou službu</h2>
|
||||||
<p class="muted">Vytvoří Gitea repozitář, webhook, lokální workspace, první commit a nasadí službu.</p>
|
<p class="muted">Vytvoří Gitea repozitář, webhook, lokální workspace, první commit a nasadí službu.</p>
|
||||||
@@ -887,9 +907,7 @@ def new_app_form(request: Request, user=Depends(require_user)):
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
<label>Šablona</label><br>
|
<label>Šablona</label><br>
|
||||||
<select name="template">
|
<select name="template" required>{template_options}</select>
|
||||||
<option value="python-fastapi">Python FastAPI</option>
|
|
||||||
</select>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<button type="submit">Vytvořit službu</button>
|
<button type="submit">Vytvořit službu</button>
|
||||||
@@ -909,10 +927,23 @@ def create_app(
|
|||||||
template: str = Form(...),
|
template: str = Form(...),
|
||||||
user=Depends(require_user),
|
user=Depends(require_user),
|
||||||
):
|
):
|
||||||
if template != "python-fastapi":
|
selected_template = get_app_template(template, create_enabled=True)
|
||||||
|
if not selected_template:
|
||||||
return HTMLResponse("Nepodporovaná šablona", status_code=400)
|
return HTMLResponse("Nepodporovaná šablona", status_code=400)
|
||||||
|
|
||||||
create_result = run_command([NEW_APP_SCRIPT, app_id, app_name])
|
create_result = run_command([NEW_APP_SCRIPT, app_id, app_name])
|
||||||
|
if create_result.returncode == 0:
|
||||||
|
update_app_template_metadata(
|
||||||
|
app_id,
|
||||||
|
{
|
||||||
|
"name": app_name,
|
||||||
|
"template": selected_template.get("id"),
|
||||||
|
"runtime": selected_template.get("runtime"),
|
||||||
|
"language": selected_template.get("language"),
|
||||||
|
"health_url": selected_template.get("default_health_path"),
|
||||||
|
"container_port": selected_template.get("default_port"),
|
||||||
|
},
|
||||||
|
)
|
||||||
deploy_result = run_command([DEPLOY_SCRIPT, app_id])
|
deploy_result = run_command([DEPLOY_SCRIPT, app_id])
|
||||||
|
|
||||||
status = "OK" if create_result.returncode == 0 and deploy_result.returncode == 0 else "FAILED"
|
status = "OK" if create_result.returncode == 0 and deploy_result.returncode == 0 else "FAILED"
|
||||||
@@ -924,7 +955,7 @@ def create_app(
|
|||||||
metadata={
|
metadata={
|
||||||
"app_id": app_id,
|
"app_id": app_id,
|
||||||
"app_name": app_name,
|
"app_name": app_name,
|
||||||
"template": template,
|
"template": selected_template.get("id"),
|
||||||
"status": status,
|
"status": status,
|
||||||
"create_returncode": create_result.returncode,
|
"create_returncode": create_result.returncode,
|
||||||
"deploy_returncode": deploy_result.returncode,
|
"deploy_returncode": deploy_result.returncode,
|
||||||
|
|||||||
Reference in New Issue
Block a user