// app.js — 视图路由、运行列表渲染、健康检查。整个控制台的入口编排。 const App = { currentRunId: null, views: ["runs", "new", "report"], titles: { runs: "运行列表", new: "新建评估", report: "报告详情" }, // 初始化:绑定导航、加载首屏、启动健康检查。 init() { document.querySelectorAll(".nav-item").forEach((btn) => { btn.addEventListener("click", () => App.switchView(btn.dataset.view)); }); document.getElementById("refresh-btn").addEventListener("click", () => App.refreshCurrent()); Runner.init(); App.switchView("runs"); App.checkHealth(); setInterval(App.checkHealth, 15000); }, // 切换主视图,并同步导航高亮与标题。 switchView(view) { if (view === "report" && !App.currentRunId) { // 没有选中的运行时,报告页显示占位。 } App.views.forEach((name) => { const el = document.getElementById(`view-${name}`); if (el) el.hidden = name !== view; }); document.querySelectorAll(".nav-item").forEach((btn) => { btn.classList.toggle("active", btn.dataset.view === view); }); document.getElementById("view-title").textContent = App.titles[view] || view; App.activeView = view; if (view === "runs") App.loadRuns(); if (view === "new") Runner.loadScenarios(); if (view === "report") Report.render(App.currentRunId); }, // 刷新当前视图的数据。 refreshCurrent() { App.switchView(App.activeView || "runs"); }, // 加载并渲染运行列表。 async loadRuns() { const container = document.getElementById("runs-container"); const empty = document.getElementById("runs-empty"); container.innerHTML = '
加载中…
'; try { const data = await API.runs(); const runs = data.runs || []; if (runs.length === 0) { container.innerHTML = ""; empty.hidden = false; return; } empty.hidden = true; container.innerHTML = ""; runs.forEach((run) => container.appendChild(App.renderRunCard(run))); } catch (err) { container.innerHTML = `加载失败:${App.escape(err.message)}
`; } }, // 构造一张运行卡片。 renderRunCard(run) { const card = document.createElement("div"); card.className = "run-card"; card.addEventListener("click", () => { App.currentRunId = run.run_id; App.enableReportNav(); App.switchView("report"); }); const chips = (run.metrics || []) .map((m) => { const val = run.metric_means ? run.metric_means[m] : null; const cls = App.scoreClass(val); const text = val === null || val === undefined ? "n/a" : val.toFixed(2); return `${App.escape(App.shortMetric(m))} ${text}`; }) .join(""); card.innerHTML = `