Hotovo. Přidal jsem podporu bootstrap-v2.sh do stránky Migration Readiness.

Co se změnilo:
migration_readiness.py (line 9) nově zná BOOTSTRAP_SCRIPT = "bootstrap-v2.sh".
Helper umí najít poslední job pro bootstrap a připravit status, return code, started_at, finished_at, raw stdout/stderr z DB.
routes/migration_readiness.py (line 111) přidává tlačítko Run Bootstrap v2, které pouze vytvoří run_script job s script_name=bootstrap-v2.sh.
Přidal jsem výrazné varování k bootstrapu a blok “Poslední výsledek Bootstrap v2”.
Zobrazení logů redaktuje env-like citlivé hodnoty typu TOKEN, PASSWORD, SECRET, API_KEY, PRIVATE, CREDENTIAL.
This commit is contained in:
JiriUhlir
2026-06-08 08:02:56 +02:00
parent ad0b35c09f
commit 6622db2fb3
2 changed files with 171 additions and 14 deletions
+85 -6
View File
@@ -6,7 +6,14 @@ from fastapi.responses import HTMLResponse, RedirectResponse
from app.auth import require_user
from app.db.audit import log_audit_event
from app.db.jobs import create_job
from app.db.migration_readiness import PREFLIGHT_SCRIPT, get_latest_preflight_job, summarize_readiness
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
@@ -19,10 +26,51 @@ def render_readiness_pill(ready: bool) -> str:
return '<span class="pill pill-danger">NOT READY</span>'
def render_script_result(title: str, script_name: str, summary: dict) -> str:
job = summary.get("job")
job_rows = f'<tr><td colspan="2">Zatím není uložený žádný výsledek skriptu {html.escape(script_name)}.</td></tr>'
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"""
<tr><th>Úloha</th><td><a href="/portal/jobs/{job_id}">#{job_id}</a></td></tr>
<tr><th>Status</th><td>{render_job_status(job.get("status"))}</td></tr>
<tr><th>Return code</th><td>{return_code_label}</td></tr>
<tr><th>Started at</th><td>{html.escape(job.get("started_at", "") or "")}</td></tr>
<tr><th>Finished at</th><td>{html.escape(job.get("finished_at", "") or "")}</td></tr>
"""
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"""
<div class="card">
<h2>{html.escape(title)}</h2>
<p class="muted">Poslední výsledek maintenance skriptu {html.escape(script_name)} uložený v databázi.</p>
<table>
{job_rows}
</table>
</div>
<div class="grid">
<div class="card">
<h2>Raw stdout log</h2>
<pre class="log-viewer log-stdout">{stdout}</pre>
</div>
<div class="card">
<h2>Raw stderr log</h2>
<pre class="log-viewer log-stderr">{stderr}</pre>
</div>
</div>
"""
@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(
@@ -55,9 +103,25 @@ def migration_readiness_page(request: Request, user=Depends(require_user)):
<div class="card">
<h2>Migration Readiness</h2>
<p class="muted">Poslední výsledek maintenance skriptu {html.escape(PREFLIGHT_SCRIPT)} uložený v databázi. Stav vychází pouze z výstupu skriptu.</p>
<form method="post" action="/portal/migration-readiness/run">
<button type="submit">Spustit preflight kontrolu</button>
</form>
<div class="inline-form">
<form method="post" action="/portal/migration-readiness/run">
<button type="submit">Spustit preflight kontrolu</button>
</form>
<form method="post" action="/portal/migration-readiness/bootstrap" onsubmit="return confirm('Spustit Bootstrap v2? Tato akce upravuje server přes maintenance job.');">
<button type="submit" class="danger">Run Bootstrap v2</button>
</form>
</div>
</div>
<div class="alert alert-danger">
<strong>Bootstrap v2 provádí zásahy do systému.</strong>
<ul>
<li>Opravuje ownership /opt/appfactory.</li>
<li>Nastavuje executable práva pro maintenance a alert skripty.</li>
<li>Deployuje core services.</li>
<li>Spouští preflight-check.sh.</li>
</ul>
<p>Portál bootstrap nespouští přímo přes shell. Pouze založí maintenance job pro {html.escape(BOOTSTRAP_SCRIPT)}.</p>
</div>
<div class="stats-grid">
@@ -68,16 +132,18 @@ def migration_readiness_page(request: Request, user=Depends(require_user)):
</div>
<div class="card">
<h2>Poslední úloha</h2>
<h2>Poslední preflight úloha</h2>
<table>
{job_rows}
</table>
</div>
<div class="card">
<h2>Raw log výstup</h2>
<h2>Raw preflight log výstup</h2>
<pre class="log-viewer log-stdout">{raw_output}</pre>
</div>
{render_script_result("Poslední výsledek Bootstrap v2", BOOTSTRAP_SCRIPT, bootstrap_summary)}
""",
user=user,
)
@@ -94,3 +160,16 @@ def run_migration_readiness(user=Depends(require_user)):
source="portal_manual",
)
return RedirectResponse(url=f"/portal/jobs/{job_id}", status_code=303)
@router.post("/migration-readiness/bootstrap")
def run_bootstrap_v2(user=Depends(require_user)):
job_id = create_job(
job_type="run_script",
target_type="maintenance_script",
target_id=BOOTSTRAP_SCRIPT,
payload={"script_name": BOOTSTRAP_SCRIPT},
user=user,
source="portal_manual",
)
return RedirectResponse(url=f"/portal/jobs/{job_id}", status_code=303)