import html from fastapi import APIRouter, Depends, Form, HTTPException, Request from fastapi.responses import HTMLResponse, RedirectResponse from app.auth import require_user from app.config import ( get_auth_domain_readiness, get_google_redirect_uri, get_portal_public_url, ) from app.db.audit import log_audit_event from app.db.jobs import create_job from app.db.migration_readiness import ( BOOTSTRAP_SCRIPT, PREFLIGHT_SCRIPT, get_latest_bootstrap_job, get_latest_preflight_job, summarize_readiness, summarize_script_run, ) from app.routes.jobs import render_job_status from app.templates.layout import page router = APIRouter() BOOTSTRAP_MODES = {"check", "repair", "deploy"} def render_readiness_pill(ready: bool) -> str: if ready: return 'READY' return 'NOT READY' def render_boolean_pill(enabled: bool) -> str: if enabled: return 'YES' return 'NO' def render_auth_domain_readiness() -> str: readiness = get_auth_domain_readiness() public_url = get_portal_public_url() or "relative/proxy mode" redirect_uri = get_google_redirect_uri() or "not configured" warning = "" if readiness["google_oauth_enabled"] and ( not readiness["portal_public_url_configured"] or not readiness["google_redirect_uri_configured"] ): warning = """
Google OAuth is enabled but domain readiness is incomplete.

Set APPFACTORY_PORTAL_PUBLIC_URL or APPFACTORY_ENABLE_HTTPS=true with APPFACTORY_PORTAL_DOMAIN, and make sure a Google redirect URI is available.

""" return f"""

Auth & Domain Readiness

Portal public URL{html.escape(public_url)}
HTTPS enabled{render_boolean_pill(readiness["https_enabled"])}
Google OAuth enabled{render_boolean_pill(readiness["google_oauth_enabled"])}
Google OAuth configured{render_boolean_pill(readiness["google_oauth_configured"])}
Google redirect URI effective value{html.escape(redirect_uri)}
{warning}
""" def render_script_result(title: str, script_name: str, summary: dict) -> str: job = summary.get("job") job_rows = f'Zatím není uložený žádný výsledek skriptu {html.escape(script_name)}.' if job: job_id = html.escape(str(job.get("id", ""))) return_code = summary.get("return_code") return_code_label = "" if return_code is None else html.escape(str(return_code)) job_rows = f""" Úloha#{job_id} Status{render_job_status(job.get("status"))} Return code{return_code_label} Started at{html.escape(job.get("started_at", "") or "")} Finished at{html.escape(job.get("finished_at", "") or "")} """ stdout = html.escape(summary.get("stdout") or "Zatím není uložený žádný stdout log.") stderr = html.escape(summary.get("stderr") or "Zatím není uložený žádný stderr log.") return f"""

{html.escape(title)}

Poslední výsledek maintenance skriptu {html.escape(script_name)} uložený v databázi.

{job_rows}

Raw stdout log

{stdout}

Raw stderr log

{stderr}
""" @router.get("/migration-readiness/auth-domain-config") def auth_domain_readiness_config(user=Depends(require_user)): return get_auth_domain_readiness() @router.get("/migration-readiness", response_class=HTMLResponse) def migration_readiness_page(request: Request, user=Depends(require_user)): latest_job = get_latest_preflight_job() summary = summarize_readiness(latest_job) bootstrap_summary = summarize_script_run(get_latest_bootstrap_job()) job = summary["job"] log_audit_event( user, action="migration_readiness.viewed", target_type="migration_readiness", target_id=PREFLIGHT_SCRIPT, metadata={"job_id": job.get("id") if job else None}, ) log_audit_event( user, action="auth_domain_readiness.viewed", target_type="auth_domain_readiness", metadata=get_auth_domain_readiness(), ) job_rows = 'Zatím není uložený žádný výsledek preflight kontroly.' if job: job_id = html.escape(str(job.get("id", ""))) return_code = summary.get("return_code") return_code_label = "" if return_code is None else html.escape(str(return_code)) job_rows = f""" Úloha#{job_id} Stav úlohy{render_job_status(job.get("status"))} Return code{return_code_label} Vytvořeno{html.escape(job.get("created_at", "") or "")} Spuštěno{html.escape(job.get("started_at", "") or "")} Dokončeno{html.escape(job.get("finished_at", "") or "")} Worker{html.escape(job.get("worker_id", "") or "")} """ stdout = html.escape(summary.get("stdout") or "Zatím není uložený žádný stdout log.") stderr = html.escape(summary.get("stderr") or "Zatím není uložený žádný stderr log.") return page( "Migration Readiness", f"""

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.

Bootstrap v2 má explicitní režimy.

Portál bootstrap nespouští přímo přes shell. Pouze založí maintenance job pro {html.escape(BOOTSTRAP_SCRIPT)}.

Plný Bootstrap Deploy není výchozí akce.

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

Stav{render_readiness_pill(summary["ready"])}
OK{html.escape(str(summary["ok_count"]))}
WARN{html.escape(str(summary["warn_count"]))}
FAIL{html.escape(str(summary["fail_count"]))}
{render_auth_domain_readiness()}

Poslední preflight úloha

{job_rows}

Raw preflight stdout

{stdout}

Raw preflight stderr

{stderr}
{render_script_result("Poslední výsledek Bootstrap v2", BOOTSTRAP_SCRIPT, bootstrap_summary)} """, user=user, ) @router.post("/migration-readiness/run") def run_migration_readiness(user=Depends(require_user)): job_id = create_job( job_type="run_script", target_type="maintenance_script", target_id=PREFLIGHT_SCRIPT, payload={"script_name": PREFLIGHT_SCRIPT}, user=user, source="portal_manual", ) return RedirectResponse(url=f"/portal/jobs/{job_id}", status_code=303) @router.post("/migration-readiness/bootstrap") def run_bootstrap_v2(mode: str = Form(...), user=Depends(require_user)): mode = (mode or "").strip().lower() if mode not in BOOTSTRAP_MODES: raise HTTPException(status_code=400, detail="Neplatný režim bootstrapu") job_id = create_job( job_type="run_script", target_type="maintenance_script", target_id=BOOTSTRAP_SCRIPT, payload={ "script_name": BOOTSTRAP_SCRIPT, "args": [mode], "arguments": [mode], "mode": mode, }, user=user, source="portal_manual", ) return RedirectResponse(url=f"/portal/jobs/{job_id}", status_code=303)