from __future__ import annotations import html from pathlib import Path from typing import Any, Dict, List from lab.config import CASE_RUNS_DIR, DASHBOARD_DIR, RUNS_DIR from lab.utils import ensure_dir, load_json_dir, read_json, write_json, write_text def mermaid_from_steps(run: Dict[str, Any]) -> str: lines = [ "flowchart LR", 'A["Select Advisory"] --> B["Resolve Repro Profile"]', 'B --> C["Provision Compose Environment"]', 'C --> D["Baseline Snapshot"]', 'D --> E["Controlled Attack Steps"]', 'E --> F["Browser Replay"]', 'F --> G["Collect Logs and Evidence"]', 'G --> H["Update Registry and Reports"]', ] if run.get("blocked_reason"): lines.append(f'H --> I["Blocked: {run["blocked_reason"][:60]}"]') return "\n".join(lines) def render_run(run: Dict[str, Any]) -> Dict[str, str]: run_dir = CASE_RUNS_DIR / run["run_id"] ensure_dir(run_dir / "assets") timeline_path = run_dir / "timeline.mmd" write_text(timeline_path, mermaid_from_steps(run)) md_lines = [ f"# Run {run['run_id']}", "", "> `LAB ONLY` | `AUTHORIZED TARGETS ONLY` | 自动生成 run bundle", "", f"- Advisory: `{run['advisory_id']}`", f"- 系统: `{run['system_id']}`", f"- Repro Profile: `{run['repro_profile_id']}`", f"- 实证状态: `{run['verification_status']}`", f"- 实证方式: `{run['verification_mode']}`", f"- Artifact 模式: `{run['artifact_mode']}`", f"- 启动时间: `{run['started_at']}`", f"- 完成时间: `{run['finished_at']}`", f"- 阻塞原因: `{run.get('blocked_reason') or '-'}`", "", "## 运行时间线", "", f"- Mermaid: [{timeline_path.name}]({timeline_path})", "", "## 证据摘要", "", f"- Baseline: `{len(run.get('baseline_refs', []))}`", f"- 攻击步骤: `{len(run.get('attack_steps', []))}`", f"- 浏览器证据: `{len(run.get('browser_refs', []))}`", f"- 容器日志: `{len(run.get('container_log_refs', []))}`", f"- 请求日志: `{len(run.get('request_log_refs', []))}`", "", "## 最小化验证说明", "", "- 仅限自有资产、本地靶场或已授权实验目标。", "- 默认执行 minimal-proof;不会把破坏性或不可回滚动作作为默认路径。", "", ] if run.get("browser_refs"): md_lines.extend(["## 浏览器证据", ""]) for ref in run["browser_refs"]: md_lines.append(f"- {ref}") md_lines.append("") if run.get("container_log_refs"): md_lines.extend(["## 容器日志", ""]) for ref in run["container_log_refs"]: md_lines.append(f"- {ref}") md_lines.append("") report_md = run_dir / "report.md" write_text(report_md, "\n".join(md_lines)) html_body = [ "", "websafe run report", "", "", f"

Run {html.escape(run['run_id'])}

", "
", f"
Advisory
{html.escape(run['advisory_id'])}
", f"
Status
{html.escape(run['verification_status'])}
", f"
Profile
{html.escape(run['repro_profile_id'])}
", f"
Artifact Mode
{html.escape(run['artifact_mode'])}
", "
", "

Mermaid Timeline

", f"
{html.escape(mermaid_from_steps(run))}
", "

Evidence

", ""]) report_html = run_dir / "report.html" write_text(report_html, "\n".join(html_body)) return {"bundle_dir": str(run_dir), "report_md": str(report_md), "report_html": str(report_html), "timeline": str(timeline_path)} def render_dashboard() -> Dict[str, str]: ensure_dir(DASHBOARD_DIR) runs = load_json_dir(RUNS_DIR) summary = { "run_count": len(runs), "statuses": {}, "recent_runs": sorted(runs, key=lambda item: item.get("finished_at") or "", reverse=True)[:50], } for item in runs: status = item.get("verification_status", "triage-manual") summary["statuses"][status] = summary["statuses"].get(status, 0) + 1 write_json(DASHBOARD_DIR / "summary.json", summary) write_json(DASHBOARD_DIR / "runs.json", summary["recent_runs"]) html_page = """ websafe dashboard

websafe Local Lab Dashboard

LAB ONLY | AUTHORIZED TARGETS ONLY | 本地静态看板

Recent Runs

RunAdvisoryStatusModeFinishedReport
""" write_text(DASHBOARD_DIR / "index.html", html_page) return { "dashboard_dir": str(DASHBOARD_DIR), "index_html": str(DASHBOARD_DIR / "index.html"), }