From bad55a7d417fd018e1931c7f0dc3a7a6778cde57 Mon Sep 17 00:00:00 2001 From: JiriUhlir <149317995+JiriUhlir@users.noreply.github.com> Date: Tue, 16 Jun 2026 10:23:43 +0200 Subject: [PATCH] fixes + guest --- app/auth.py | 17 +++- app/db/users.py | 4 +- app/routes/alerting.py | 30 +++--- app/routes/apps.py | 104 ++++++++++---------- app/routes/audit.py | 2 +- app/routes/auth.py | 26 ++++- app/routes/deployments.py | 16 +-- app/routes/developers.py | 3 +- app/routes/environment.py | 4 +- app/routes/incidents.py | 2 +- app/routes/jobs.py | 18 ++-- app/routes/migration_readiness.py | 10 +- app/routes/operations.py | 2 +- app/routes/runtime.py | 10 +- app/routes/scheduled_scripts.py | 157 ++++++++++++++++-------------- app/routes/users.py | 13 +-- app/routes/workers.py | 6 +- app/templates/layout.py | 80 +++++++++++---- 18 files changed, 295 insertions(+), 209 deletions(-) diff --git a/app/auth.py b/app/auth.py index b8b7124..4bb6104 100644 --- a/app/auth.py +++ b/app/auth.py @@ -125,7 +125,7 @@ def find_or_create_oauth_user(username: str, display_name: str, email: str) -> d created_at, updated_at ) - VALUES (?, ?, ?, '', 'developer', 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) + VALUES (?, ?, ?, '', 'guest', 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) """, (username, display_name, email), ) @@ -196,7 +196,9 @@ def find_or_create_google_user(email: str, display_name: str, provider_subject: created = False user_id = row["id"] else: - role = "admin" if con.execute("SELECT COUNT(*) AS count FROM users").fetchone()["count"] == 0 else "viewer" + # První účet v prázdné tabulce je admin (bootstrap), každý další nový účet je guest – + # bez přístupu, dokud mu admin nepřidělí vyšší roli. + role = "admin" if con.execute("SELECT COUNT(*) AS count FROM users").fetchone()["count"] == 0 else "guest" insert_columns = [ "username", "display_name", @@ -292,6 +294,12 @@ def current_user(request: Request) -> dict[str, Any] | None: def require_user(request: Request) -> dict[str, Any]: user = current_user(request) if user: + # Guest nemá přístup nikam – přesměruj ho na stránku s výzvou o přidělení práv. + if is_guest(user): + raise HTTPException( + status_code=status.HTTP_303_SEE_OTHER, + headers={"Location": "/portal/guest"}, + ) return user raise HTTPException( @@ -307,6 +315,11 @@ def require_user(request: Request) -> dict[str, Any]: # variables, resources, metadata). Cannot delete services or access admin areas. # admin – full access, including deleting services and the Administration / Users area. +def is_guest(user: dict | None) -> bool: + """True for the default role assigned to a freshly created account (no access yet).""" + return bool(user) and (user.get("role") or "").lower() == "guest" + + def is_admin(user: dict | None) -> bool: return bool(user) and (user.get("role") or "").lower() == "admin" diff --git a/app/db/users.py b/app/db/users.py index c8d2dbd..5e48e75 100644 --- a/app/db/users.py +++ b/app/db/users.py @@ -3,7 +3,9 @@ from typing import Any from app.db.database import get_connection from app.db.migrations import run_migrations -ROLES = ("admin", "developer", "viewer") +# Pořadí od nejvyšších práv po nejnižší. "guest" je výchozí role nového účtu – +# nemá přístup nikam, jen vidí výzvu k přidělení práv administrátorem. +ROLES = ("admin", "developer", "viewer", "guest") def _table_columns(con, table_name: str) -> set[str]: diff --git a/app/routes/alerting.py b/app/routes/alerting.py index c5c00e1..afa4d41 100644 --- a/app/routes/alerting.py +++ b/app/routes/alerting.py @@ -196,7 +196,7 @@ def render_rule_form(rule: dict | None, action: str) -> str:
- +
""" @@ -230,7 +230,7 @@ def render_event_rows(events: list[dict], empty_colspan: int = 9, include_rule: {render_status(event.get("status"))} {job_link} {html.escape(event.get("created_at", "") or "")} - Detail + Detail """ @@ -273,7 +273,7 @@ def render_script_editor(rule: dict, user: dict) -> str:
- +
@@ -307,13 +307,13 @@ def alert_rules_page(request: Request, user=Depends(require_user)): {last_event} {html.escape(rule.get("updated_at", "") or "")} - Detail - Upravit + Detail + Upravit
- +
- +
@@ -329,8 +329,8 @@ def alert_rules_page(request: Request, user=Depends(require_user)):

Alert pravidla

Správa pravidel pro spouštění alert skriptů při událostech incidentů.

- + Nové alert pravidlo - Alert eventy + Nové alert pravidlo + Alert eventy

@@ -415,15 +415,15 @@ def alert_rule_detail(rule_id: int, request: Request, user=Depends(require_user)

{html.escape(rule.get("name", "") or "")}

← Zpět na alert pravidla - Upravit - Alert eventy + Upravit + Alert eventy

- +
- +
@@ -580,7 +580,7 @@ def alert_events_page(request: Request, user=Depends(require_user)):

Alert eventy

Historie vyvolaných alertů a jejich zpracování.

-

Alert pravidla

+

Alert pravidla

@@ -632,7 +632,7 @@ def alert_event_detail(event_id: int, request: Request, user=Depends(require_use

Alert event #{html.escape(str(event_id))}

← Zpět na alert eventy - Alert pravidla + Alert pravidla

diff --git a/app/routes/apps.py b/app/routes/apps.py index a302d53..06af123 100644 --- a/app/routes/apps.py +++ b/app/routes/apps.py @@ -144,7 +144,7 @@ def ensure_variable_key_allowed(app_id: str, key: str, variable_id: int | None = def render_template_options(templates: list[dict], selected_template: str, include_blank: bool = True) -> str: - options = [''] if include_blank else [] + options = [''] if include_blank else [] selected_exists = not selected_template for template in templates: template_id = template.get("id", "") or "" @@ -164,7 +164,7 @@ def render_template_options(templates: list[dict], selected_template: str, inclu ) if not selected_exists: options.append( - f'' + f'' ) return "".join(options) @@ -175,7 +175,7 @@ def render_template_label(templates: list[dict], template_id: str) -> str: for template in templates: if template.get("id") == template_id: return html.escape(template.get("name", "") or template_id) - return "Neznámá šablona" + return "Neznámá šablona" def render_environment_usage_example(language: str, runtime: str, template: str) -> str: @@ -361,8 +361,8 @@ def apps_page( """ @@ -377,13 +377,13 @@ def apps_page(
- +
- +
""" else: @@ -470,12 +470,12 @@ def apps_page( return f"/portal/apps?{urlencode(params)}" previous_link = ( - f'Předchozí' + f' Předchozí' if page_number > 1 else "" ) next_link = ( - f'Další' + f'Další ' if page_number < total_pages else "" ) @@ -502,7 +502,7 @@ def apps_page( """

Nová služba

- +
""" if can_manage @@ -709,17 +709,17 @@ def app_detail(app_id: str, request: Request, message: str = "", error: str = "" - +
- +
""" if not variable_rows: - variable_rows = 'Zatím nejsou evidované žádné proměnné.' + variable_rows = 'Zatím nejsou evidované žádné proměnné.' def render_method_options(selected: str) -> str: selected_upper = (selected or "").upper() @@ -749,10 +749,10 @@ def app_detail(app_id: str, request: Request, message: str = "", error: str = "" - +
- +
@@ -761,7 +761,7 @@ def app_detail(app_id: str, request: Request, message: str = "", error: str = "" """ if not ip_access_rule_rows: - ip_access_rule_rows = 'Zatím nejsou evidovaná žádná IP access pravidla.' + ip_access_rule_rows = 'Zatím nejsou evidovaná žádná IP access pravidla.' notice = "" if message: @@ -774,7 +774,7 @@ def app_detail(app_id: str, request: Request, message: str = "", error: str = "" redeploy_block = ( f"""
- +
""" if can_manage @@ -783,17 +783,17 @@ def app_detail(app_id: str, request: Request, message: str = "", error: str = "" detail_tabs = ( """ -
- Metadata - Proměnné - Security / IP Access - Historie +
+ Metadata + Proměnné + Security / IP Access + Historie
""" if can_manage else """ -
- Historie + """ ) @@ -803,13 +803,13 @@ def app_detail(app_id: str, request: Request, message: str = "", error: str = ""

Metadata

@@ -852,9 +852,9 @@ def app_detail(app_id: str, request: Request, message: str = "", error: str = "" variables_card = ( f"""
-

Proměnné

+

Proměnné

- +
{environment_usage_example} @@ -868,14 +868,14 @@ def app_detail(app_id: str, request: Request, message: str = "", error: str = ""
@@ -889,9 +889,9 @@ def app_detail(app_id: str, request: Request, message: str = "", error: str = ""

Security / IP Access

- Pravidla určují, z jakých IP/CIDR adres lze volat dané HTTP metody. - WRITE = POST, PUT, PATCH, DELETE; ALL = všechny běžné metody včetně GET. - Pravidla se zatím pouze ukládají (Caddy je začne vynucovat později). + Pravidla určují, z jakých IP/CIDR adres lze volat dané HTTP metody. + WRITE = POST, PUT, PATCH, DELETE; ALL = všechny běžné metody včetně GET. + Pravidla se zatím pouze ukládají (Caddy je začne vynucovat později).

@@ -905,16 +905,16 @@ def app_detail(app_id: str, request: Request, message: str = "", error: str = ""
@@ -932,8 +932,8 @@ def app_detail(app_id: str, request: Request, message: str = "", error: str = "" {notice}

← Zpět na služby - Nasazení služby - Úlohy služby + Nasazení služby + Úlohy služby

{redeploy_block}
@@ -952,7 +952,7 @@ def app_detail(app_id: str, request: Request, message: str = "", error: str = "" ID{escaped_app_id} Název{name} Popis{description} - Vlastník{owner} + Vlastník{owner} Template{template_value} Runtime{runtime} Repository URL{repository_url} @@ -961,8 +961,8 @@ def app_detail(app_id: str, request: Request, message: str = "", error: str = "" Domain{domain} Health URL{health_url} Container Port{container_port} - Veřejná služba{"Ano" if is_public else "Ne"} - Aktivní služba{"Ano" if is_enabled else "Ne"} + Veřejná služba{"Ano" if is_public else "Ne"} + Aktivní služba{"Ano" if is_enabled else "Ne"} Jazyk{language} Verze{version} Status{status} @@ -998,13 +998,13 @@ def app_detail(app_id: str, request: Request, message: str = "", error: str = ""
-

Incidenty služby

+

Incidenty služby

- - + + - + {render_incident_history_rows(incidents, include_service=False)} @@ -1364,10 +1364,10 @@ def new_app_form(request: Request, user=Depends(require_developer)):

- +
diff --git a/app/routes/audit.py b/app/routes/audit.py index ceb638b..cbd045a 100644 --- a/app/routes/audit.py +++ b/app/routes/audit.py @@ -37,7 +37,7 @@ def audit_page(request: Request, user=Depends(require_user)): "Audit", f"""
-

Audit

+

Audit

Přehled interních akcí provedených v portálu.

diff --git a/app/routes/auth.py b/app/routes/auth.py index 4122bf9..e7a4952 100644 --- a/app/routes/auth.py +++ b/app/routes/auth.py @@ -259,6 +259,28 @@ def google_callback(request: Request, code: str = "", state: str = "", error: st return _render_login("Google login failed.") +@router.get("/guest", response_class=HTMLResponse) +def guest_page(request: Request): + user = current_user(request) + if not user: + return RedirectResponse(url="/portal/login", status_code=303) + # Účet s přidělenými právy sem nepatří – pošli ho na běžnou úvodní stránku. + if (user.get("role") or "").lower() != "guest": + return RedirectResponse(url="/portal/apps", status_code=303) + + return page( + "Čekání na schválení", + """ +
+

Účet zatím nemá přidělená práva

+

Požádejte o přidělení práv administrátora.

+

Jakmile vám administrátor přiřadí roli, obnovte stránku a portál se vám zpřístupní.

+
+ """, + user=user, + ) + + @router.post("/logout") def logout(request: Request): user = current_user(request) @@ -424,7 +446,7 @@ def _render_login(error: str | None = None) -> str:

- + """ @@ -464,7 +486,7 @@ def _render_login(error: str | None = None) -> str:

- + """, diff --git a/app/routes/deployments.py b/app/routes/deployments.py index 4baca65..454c3f7 100644 --- a/app/routes/deployments.py +++ b/app/routes/deployments.py @@ -211,7 +211,7 @@ async def deployments_page( """ @@ -226,12 +226,12 @@ async def deployments_page( first_item = offset + 1 if total_deployments else 0 last_item = min(offset + len(deployments), total_deployments) previous_link = ( - f'Předchozí' + f' Předchozí' if page_number > 1 else "" ) next_link = ( - f'Další' + f'Další ' if page_number < total_pages else "" ) @@ -253,7 +253,7 @@ async def deployments_page( f""" {refresh}
-

Historie nasazení

+

Historie nasazení

Přehled posledních běhů nasazení a jejich výsledků.

← Zpět na přehled
@@ -272,8 +272,8 @@ async def deployments_page( - - Reset + + Reset @@ -361,8 +361,8 @@ async def deployment_detail_page( {failed_notice}

← Zpět na nasazení - Detail služby - Surové logy + Detail služby + Surové logy

diff --git a/app/routes/developers.py b/app/routes/developers.py index 9f564a3..2cae5cc 100644 --- a/app/routes/developers.py +++ b/app/routes/developers.py @@ -53,6 +53,7 @@ PUBLIC_ENDPOINTS = [ # Roles model. ROLES = [ + ("guest", "Výchozí role nového účtu po prvním přihlášení. Bez přístupu – vidí jen výzvu požádat administrátora o přidělení práv."), ("viewer", "Pouze pro čtení: vidí jaké služby běží a jejich dokumentaci (Swagger). Bez Gitu, úprav, nasazení a mazání."), ("developer", "Vše co viewer + provozní workflow služeb: Git/klonování, nasazení, proměnné, prostředky, metadata. Nemůže mazat služby ani do Administrace."), ("admin", "Plný přístup: vše co developer + mazání služeb, správa záloh a jejich obnova, správa uživatelů, audit a admin-only operace."), @@ -120,7 +121,7 @@ def developers_page(user=Depends(require_user)): Naklonujte repozitář.
- +
Přesný příkaz pro konkrétní službu (HTTP i SSH) najdete také na stránce Služby. diff --git a/app/routes/environment.py b/app/routes/environment.py index da8915c..0e255bd 100644 --- a/app/routes/environment.py +++ b/app/routes/environment.py @@ -29,7 +29,7 @@ def _render_rows(entries: list[tuple[str, str]]) -> str: """ @@ -109,7 +109,7 @@ def _render_page(user: dict, entries: list[tuple[str, str]], message: str = "", const tr = document.createElement("tr"); tr.innerHTML = '' + '' - + ''; + + ''; tbody.appendChild(tr); }} diff --git a/app/routes/incidents.py b/app/routes/incidents.py index 0deb907..47d9b2a 100644 --- a/app/routes/incidents.py +++ b/app/routes/incidents.py @@ -111,7 +111,7 @@ def incidents_page(request: Request, user=Depends(require_user)): "Incidenty", f"""
-

Incidenty

+

Incidenty

P\u0159ehled incident\u016f vytv\u00e1\u0159en\u00fdch a uzav\u00edran\u00fdch monitorem.

diff --git a/app/routes/jobs.py b/app/routes/jobs.py index 61e190c..58bf45b 100644 --- a/app/routes/jobs.py +++ b/app/routes/jobs.py @@ -198,7 +198,7 @@ def jobs_page( - + """ @@ -222,12 +222,12 @@ def jobs_page( return f"/portal/jobs?{urlencode(params)}" previous_link = ( - f'Předchozí' + f' Předchozí' if page_number > 1 else "" ) next_link = ( - f'Další' + f'Další ' if page_number < total_pages else "" ) @@ -249,7 +249,7 @@ def jobs_page( f""" {refresh}
-

Úlohy

+

Úlohy

Fronta portálových a webhook úloh připravená pro centrální worker.

@@ -267,8 +267,8 @@ def jobs_page( - - Reset + + Reset @@ -335,7 +335,7 @@ def job_detail_page(job_id: int, request: Request, user=Depends(require_user)): if status_value == "failed" and can_retry_job(job): actions += f""" - + """ elif status_value == "failed" and (job.get("target_id") or "") in IGNORED_RETRY_REPOSITORIES: @@ -343,7 +343,7 @@ def job_detail_page(job_id: int, request: Request, user=Depends(require_user)): if status_value in {"queued", "running"}: actions += f""" - + """ if actions: @@ -441,7 +441,7 @@ def job_detail_page(job_id: int, request: Request, user=Depends(require_user)):

{title}

← Zpět na úlohy - Detail cíle + Detail cíle

{retry_blocked_notice} {actions} diff --git a/app/routes/migration_readiness.py b/app/routes/migration_readiness.py index 3295f30..37c3365 100644 --- a/app/routes/migration_readiness.py +++ b/app/routes/migration_readiness.py @@ -157,19 +157,19 @@ def migration_readiness_page(request: Request, user=Depends(require_user)): "Migration Readiness", f"""
-

Migration Readiness

+

Migration Readiness

Poslední výsledek maintenance skriptu {html.escape(PREFLIGHT_SCRIPT)} uložený v databázi. Stav READY / NOT READY vychází pouze z return code jobu.

- +
- +
- +
@@ -189,7 +189,7 @@ def migration_readiness_page(request: Request, user=Depends(require_user)):

Režim deploy deployuje core services a může změnit stav systému. Spouštějte ho jen po kontrole dopadu.

- + diff --git a/app/routes/operations.py b/app/routes/operations.py index 07bb4a5..687522d 100644 --- a/app/routes/operations.py +++ b/app/routes/operations.py @@ -182,7 +182,7 @@ def operations_dashboard(request: Request, user=Depends(require_user)): "Přehled", f"""
-

Přehled systému

+

Přehled systému

Rychlá odpověď na otázku, zda jsou služby, úlohy a nasazení v pořádku.

diff --git a/app/routes/runtime.py b/app/routes/runtime.py index b27376a..9f630de 100644 --- a/app/routes/runtime.py +++ b/app/routes/runtime.py @@ -14,10 +14,12 @@ from app.templates.layout import page router = APIRouter() # Portál NEMÁ vlastní deploy logiku. Všechny provozní akce deleguje do existujících shell skriptů -# v appfactory-tools přes existující job systém (job_type="run_script"; worker spustí scripts/
NázevZačátekNázevZačátek KonecTrváníTrvání Stav
{returncode_label} {finished_at} - Detail + Detail
- +
{created_at} {started_at} {finished_at}Detail Detail