const overviewCards = document.getElementById("overview-cards"); const dashboardUpdated = document.getElementById("dashboard-updated"); const timelineHeader = document.getElementById("timeline-header"); const healthGrid = document.getElementById("health-grid"); const dashboardEmpty = document.getElementById("dashboard-empty"); const refreshDashboardBtn = document.getElementById("refresh-dashboard"); const DISPLAY_TIMEZONE = "Asia/Shanghai"; const dateTimeFormatter = new Intl.DateTimeFormat("zh-CN", { hour: "2-digit", minute: "2-digit", hour12: false, timeZone: DISPLAY_TIMEZONE, }); const SUMMARY_WIDTH = 360; const CELL_SIZE = 42; const CELL_GAP = 8; function formatDateTime(value) { if (!value) return "--"; const date = new Date(value); if (Number.isNaN(date.getTime())) return "--"; return dateTimeFormatter.format(date); } function rateMeta(rate) { if (rate === null || rate === undefined) return { label: "暂无数据", className: "status-cyan", colorClass: "cell-empty" }; if (rate >= 95) return { label: "优秀", className: "status-cyan", colorClass: "cell-cyan" }; if (rate >= 80) return { label: "良好", className: "status-green", colorClass: "cell-green" }; if (rate >= 50) return { label: "告警", className: "status-orange", colorClass: "cell-orange" }; return { label: "异常", className: "status-red", colorClass: "cell-red" }; } function createSummaryCard(label, value, detail = "") { const card = document.createElement("article"); card.className = "summary-card"; card.innerHTML = `${label}${value}
${detail}
`; return card; } function applyTimelineGrid(pointsCount) { const headerColumns = `minmax(${SUMMARY_WIDTH}px, 1fr) repeat(${pointsCount}, ${CELL_SIZE}px)`; timelineHeader.style.gridTemplateColumns = headerColumns; } function renderOverview(data) { overviewCards.innerHTML = ""; const averageHealth = data.average_health === null || data.average_health === undefined ? "--" : `${data.average_health.toFixed(2)}%`; const totalCalls = data.total_requests ?? 0; const healthyModels = (data.models || []).filter((model) => (model.latest_success_rate ?? 0) >= 95).length; const displayedBuckets = data.models?.[0]?.points?.length || 0; const healthWindowMinutes = data.health_window_minutes || 120; overviewCards.appendChild(createSummaryCard("总调用次数", totalCalls, "统计来自网关累计成功与失败请求")); overviewCards.appendChild(createSummaryCard("平均健康度", averageHealth, `按监控模型最近 ${healthWindowMinutes} 分钟滚动成功率平均值计算`)); overviewCards.appendChild(createSummaryCard("高健康模型数", healthyModels, `最近 ${healthWindowMinutes} 分钟滚动成功率达到 95% 以上的模型数量`)); overviewCards.appendChild(createSummaryCard("统计窗口", `${displayedBuckets * (data.bucket_minutes || 10)} 分钟`, `当前按 ${data.bucket_minutes || 10} 分钟粒度滚动统计`)); dashboardUpdated.textContent = formatDateTime(data.generated_at); } function renderTimelineHeader(models) { timelineHeader.innerHTML = ""; const points = models?.[0]?.points || []; applyTimelineGrid(points.length); const emptyCell = document.createElement("div"); emptyCell.className = "timeline-label timeline-left-placeholder"; timelineHeader.appendChild(emptyCell); points.forEach((point) => { const cell = document.createElement("div"); cell.className = "timeline-label"; cell.textContent = point.label || formatDateTime(point.bucket_start); timelineHeader.appendChild(cell); }); } function renderHealthRows(models) { healthGrid.innerHTML = ""; if (!models || models.length === 0) { dashboardEmpty.textContent = "当前没有可展示的模型健康数据,请确认 MODEL_LIST 配置并等待请求进入统计。"; return; } dashboardEmpty.textContent = ""; renderTimelineHeader(models); models.forEach((model) => { const latestRate = model.latest_success_rate === null || model.latest_success_rate === undefined ? "--" : `${model.latest_success_rate.toFixed(2)}%`; const latestMeta = rateMeta(model.latest_success_rate); const healthWindowMinutes = model.health_window_minutes || 120; const row = document.createElement("article"); row.className = "health-row-card"; row.style.gridTemplateColumns = `minmax(${SUMMARY_WIDTH}px, 1fr) max-content`; const summary = document.createElement("div"); summary.className = "health-row-summary"; summary.innerHTML = `