fix: align sentinel monitor checks with trend counts

This commit is contained in:
Codex
2026-06-28 10:08:48 +00:00
parent e682b3b9cf
commit efd544adbc
4 changed files with 583 additions and 107 deletions
@@ -123,7 +123,7 @@ code,
.metric,
.timeline-item,
.run-row,
.finding-card,
.check-table,
.check-chip {
border: 1px solid var(--line);
border-radius: 8px;
@@ -277,7 +277,8 @@ select {
grid-template-columns: minmax(420px, 1.55fr) minmax(300px, 0.9fr);
gap: 10px;
flex: 0 0 auto;
min-height: 212px;
align-items: start;
min-height: 0;
}
.trend-panel,
@@ -296,6 +297,10 @@ select {
padding: 12px;
}
.trend-panel {
align-self: start;
}
.panel-header {
display: flex;
align-items: start;
@@ -317,7 +322,6 @@ select {
.trend-chart-wrap {
position: relative;
min-height: 142px;
border: 1px solid var(--line);
border-radius: 8px;
background: linear-gradient(180deg, #ffffff 0%, #f7faf9 100%);
@@ -327,7 +331,7 @@ select {
.trend-chart {
display: block;
width: 100%;
height: 142px;
height: clamp(118px, 13vw, 150px);
}
.trend-empty {
@@ -470,16 +474,25 @@ select {
.timeline-item {
display: grid;
grid-template-columns: 78px minmax(0, 1fr) auto;
grid-template-columns: 78px minmax(0, 1fr) minmax(112px, auto);
gap: 8px;
align-items: center;
padding: 7px 8px;
font-size: 12px;
}
.run-alert-tags {
display: flex;
min-width: 0;
flex: 0 0 auto;
flex-wrap: wrap;
justify-content: flex-end;
gap: 4px;
}
.timeline-item strong,
.run-row strong,
.finding-card strong {
.check-title-cell strong {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
@@ -495,19 +508,19 @@ select {
.timeline-item.red .timeline-marker,
.run-row.red .severity-dot,
.finding-card.red .severity-dot {
.check-row.red .severity-dot {
background: var(--red);
}
.timeline-item.warning .timeline-marker,
.run-row.warning .severity-dot,
.finding-card.warning .severity-dot {
.check-row.warning .severity-dot {
background: var(--amber);
}
.timeline-item.info .timeline-marker,
.run-row.info .severity-dot,
.finding-card.info .severity-dot {
.check-row.info .severity-dot {
background: var(--blue);
}
@@ -652,14 +665,10 @@ select {
}
.finding-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
align-content: start;
gap: 8px;
min-height: 0;
}
.run-row,
.finding-card {
.run-row {
display: grid;
gap: 6px;
padding: 10px;
@@ -670,6 +679,80 @@ select {
cursor: pointer;
}
.check-table-wrap {
overflow: auto;
border: 1px solid var(--line);
border-radius: 8px;
background: #ffffff;
}
.check-table {
width: 100%;
min-width: 1120px;
border-collapse: separate;
border-spacing: 0;
font-size: 12px;
}
.check-table th,
.check-table td {
border-bottom: 1px solid var(--line);
padding: 9px 10px;
text-align: left;
vertical-align: middle;
}
.check-table th {
position: sticky;
top: 0;
z-index: 2;
background: #f7faf9;
color: var(--muted);
font-weight: 600;
}
.check-table tr:last-child td {
border-bottom: 0;
}
.check-row {
cursor: pointer;
}
.check-row:hover,
.check-row:focus-visible {
background: #f3f8f7;
outline: none;
}
.check-row.red {
box-shadow: inset 3px 0 0 var(--red);
}
.check-row.warning {
box-shadow: inset 3px 0 0 var(--amber);
}
.check-title-cell {
min-width: 260px;
}
.check-title-cell strong,
.check-title-cell span {
display: block;
}
.check-title-cell span {
margin-top: 3px;
color: var(--muted);
line-height: 1.35;
}
.detail-link {
color: var(--blue);
font-weight: 600;
}
.run-row.selected {
border-color: var(--blue);
box-shadow: inset 3px 0 0 var(--blue);
@@ -732,6 +815,83 @@ select {
color: #73500f;
}
.tag.healthy {
background: var(--green-soft);
color: #17633f;
}
.check-dialog-backdrop {
position: fixed;
inset: 0;
z-index: 50;
display: grid;
place-items: center;
background: rgba(17, 32, 30, 0.42);
padding: 24px;
}
.check-dialog {
display: flex;
width: min(1120px, 96vw);
max-height: 88dvh;
min-height: min(520px, 88dvh);
flex-direction: column;
overflow: hidden;
border: 1px solid var(--line-strong);
border-radius: 8px;
background: var(--panel);
box-shadow: 0 24px 70px rgba(17, 32, 30, 0.24);
}
.check-dialog-header {
display: flex;
flex: 0 0 auto;
align-items: start;
justify-content: space-between;
gap: 16px;
border-bottom: 1px solid var(--line);
padding: 16px;
}
.check-dialog-header h2 {
margin-top: 8px;
font-size: 20px;
}
.check-dialog-actions {
display: flex;
flex: 0 0 auto;
align-items: center;
gap: 8px;
}
.dialog-close {
min-height: 32px;
border: 1px solid var(--line-strong);
border-radius: 8px;
background: #ffffff;
color: var(--text);
padding: 0 12px;
cursor: pointer;
}
.check-dialog-body {
display: grid;
grid-template-columns: minmax(300px, 0.82fr) minmax(420px, 1fr);
gap: 12px;
min-height: 0;
overflow: auto;
padding: 16px;
}
.check-dialog-body .detail-card {
min-width: 0;
}
.detail-card-wide {
grid-column: 1 / -1;
}
.detail-card {
border: 1px solid var(--line);
border-radius: 8px;
@@ -891,6 +1051,10 @@ pre {
max-height: 80dvh;
}
.check-dialog-body {
grid-template-columns: 1fr;
}
.status-strip,
.check-summary {
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -930,8 +1094,26 @@ pre {
grid-template-columns: 62px minmax(0, 1fr);
}
.timeline-item .tag {
.timeline-item .run-alert-tags {
grid-column: 2;
width: max-content;
justify-content: flex-start;
}
.check-dialog-backdrop {
padding: 8px;
}
.check-dialog {
width: 100%;
max-height: 94dvh;
}
.check-dialog-header {
flex-direction: column;
}
.check-dialog-actions {
width: 100%;
justify-content: space-between;
}
}
@@ -104,6 +104,7 @@ createApp({
const overview = ref(null);
const runs = ref([]);
const findings = ref([]);
const runCheckSummaries = ref({});
const selectedRunId = ref("");
const selectedDetail = ref(null);
const runFilter = ref("");
@@ -113,6 +114,7 @@ createApp({
const checkTimeWindow = ref("24h");
const checkSeverityFilter = ref("alert");
const findingFilter = ref("");
const activeCheckItem = ref(null);
const autoRefresh = ref(true);
const refreshSeconds = ref(30);
const lastLoadedAt = ref("");
@@ -169,7 +171,7 @@ createApp({
timeLabel: formatDate(rawTime),
absoluteTime: formatAbsoluteDate(rawTime),
reportSha: shortHash(run.reportJsonSha256 || run.report_json_sha256 || run.reportSha256 || ""),
title: `${shortId(run.id)} ${formatAbsoluteDate(rawTime)} 错误 ${red} ${warning} 合计 ${total}`,
title: `${shortId(run.id)} ${formatAbsoluteDate(rawTime)} 错误 ${red} ${warning} 合计 ${total}`,
};
}));
const timelineRuns = computed(() => runs.value.slice(0, 16));
@@ -245,11 +247,12 @@ createApp({
} else {
console.warn("monitor-web findings refresh failed", findingsResult.reason);
}
await refreshRunCheckSummaries(runs.value);
lastLoadedAt.value = new Date().toISOString();
lastAutoRefreshAt = Date.now();
const keepSelected = runs.value.find((run) => run.id === selectedRunId.value);
const nextRun = keepSelected || runs.value[0] || latestRun.value;
if (nextRun?.id) void selectRun(nextRun, true);
if (nextRun?.id) await selectRun(nextRun, true);
} catch (cause) {
const message = String(cause?.message || cause);
if (!options.silent || runs.value.length === 0) error.value = message;
@@ -282,6 +285,63 @@ createApp({
if (run) void selectRun(run);
}
async function refreshRunCheckSummaries(rows) {
const targets = Array.isArray(rows) ? rows.slice(0, 48) : [];
const next = { ...runCheckSummaries.value };
const results = await Promise.allSettled(targets.map(async (run) => {
const runId = run?.id || run?.runId;
if (!runId) return null;
const payload = await fetchJson(`/api/runs/${encodeURIComponent(runId)}`);
const detailRows = Array.isArray(payload.findings) ? payload.findings : [];
return [runId, summarizeCheckRows(detailRows)];
}));
const failures = results.filter((item) => item.status === "rejected");
if (failures.length > 0) throw new Error(`监测项详情加载失败: ${failures.length}`);
for (const result of results) {
if (result.status !== "fulfilled" || result.value === null) continue;
const [runId, summary] = result.value;
next[runId] = summary;
}
runCheckSummaries.value = next;
}
function trendSummary(run) {
const runId = run?.id || run?.runId || "";
return runId ? runCheckSummaries.value[runId] || emptyCheckSummary() : emptyCheckSummary();
}
function runCheckErrorCount(run) {
return trendSummary(run).errorTypeCount;
}
function runCheckWarningCount(run) {
return trendSummary(run).warningTypeCount;
}
function runCheckAlertCount(run) {
return trendSummary(run).alertTypeCount;
}
function trendErrorCount(run) {
return runCheckErrorCount(run);
}
function trendWarningCount(run) {
return runCheckWarningCount(run);
}
function trendTotalCount(run) {
return runCheckAlertCount(run);
}
function openCheckDetail(item) {
activeCheckItem.value = item || null;
}
function closeCheckDetail() {
activeCheckItem.value = null;
}
async function refreshHistoricalFindings() {
try {
const findingsPayload = await fetchJson(findingsApiPath(checkTimeWindow.value));
@@ -347,6 +407,7 @@ createApp({
overview,
runs,
findings,
runCheckSummaries,
selectedRunId,
selectedDetail,
runFilter,
@@ -356,6 +417,7 @@ createApp({
checkTimeWindow,
checkSeverityFilter,
findingFilter,
activeCheckItem,
autoRefresh,
refreshSeconds,
lastLoadedAt,
@@ -384,6 +446,8 @@ createApp({
loadAll,
selectRun,
selectCheckRun,
openCheckDetail,
closeCheckDetail,
refreshNow,
currentHref,
showTrendTooltip,
@@ -393,6 +457,9 @@ createApp({
findingCount,
findingSampleCount,
alertSampleCount,
runCheckErrorCount,
runCheckWarningCount,
runCheckAlertCount,
trendTotalCount,
trendErrorCount,
trendWarningCount,
@@ -404,6 +471,12 @@ createApp({
rootCauseText,
findingTitle,
findingCode,
checkRowKey,
checkRunText,
checkTimeText,
checkActionText,
checkDetailRows,
checkEvidenceRows,
levelLabel,
findingGroupCountLabel,
timeWindowLabel,
@@ -462,13 +535,13 @@ createApp({
<section class="trend-panel" aria-labelledby="trend-heading">
<div class="panel-header">
<div>
<h2 id="trend-heading">错误 / 警告样本曲线</h2>
<p>按运行更新时间展示最近 {{ trendRows.length }} 次变化,点位为单次运行样本数</p>
<h2 id="trend-heading">错误 / 告警监测项曲线</h2>
<p>按运行更新时间展示最近 {{ trendRows.length }} 次变化,点位为单次运行监测项行数</p>
</div>
<span class="pill" :class="cadence.stale ? 'warning' : 'healthy'">{{ cadence.stale ? "非阻塞报警" : "新鲜" }}</span>
</div>
<div class="trend-chart-wrap">
<svg class="trend-chart" viewBox="0 0 720 142" role="img" aria-label="错误和警告样本数量趋势" data-monitor-trend-curve="true">
<svg class="trend-chart" viewBox="0 0 720 142" role="img" aria-label="错误和告警监测项数量趋势" data-monitor-trend-curve="true">
<line class="trend-grid-line" x1="24" x2="696" y1="24" y2="24"></line>
<line class="trend-grid-line" x1="24" x2="696" y1="75" y2="75"></line>
<line class="trend-grid-line" x1="24" x2="696" y1="126" y2="126"></line>
@@ -505,16 +578,16 @@ createApp({
<strong>{{ shortId(hoveredTrendDot.runId) }}</strong>
<span>{{ hoveredTrendDot.absoluteTime }}</span>
<span>状态 {{ hoveredTrendDot.status }}</span>
<span>错误 {{ hoveredTrendDot.red }} / 告 {{ hoveredTrendDot.warning }} / 合计 {{ hoveredTrendDot.total }}</span>
<span>错误 {{ hoveredTrendDot.red }} / 告 {{ hoveredTrendDot.warning }} / 合计 {{ hoveredTrendDot.total }}</span>
<span v-if="hoveredTrendDot.reportSha">report {{ hoveredTrendDot.reportSha }}</span>
</div>
<div v-if="trendRows.length === 0" class="trend-empty">暂无运行数据</div>
</div>
<div class="trend-legend">
<span class="legend-item"><span class="legend-swatch red"></span>最新点错误 {{ trendErrorCount(latestTrendRun) }}</span>
<span class="legend-item"><span class="legend-swatch warning"></span>最新点告 {{ trendWarningCount(latestTrendRun) }}</span>
<span class="legend-item"><span class="legend-swatch total"></span>最新点错误+告合计 {{ trendTotalCount(latestTrendRun) }}</span>
<span class="legend-item">历史样本累计 错误 {{ redCount({ severityCounts: severityTotals }) }} / 告 {{ warningCount({ severityCounts: severityTotals }) }}</span>
<span class="legend-item"><span class="legend-swatch warning"></span>最新点告 {{ trendWarningCount(latestTrendRun) }}</span>
<span class="legend-item"><span class="legend-swatch total"></span>最新点错误+告合计 {{ trendTotalCount(latestTrendRun) }}</span>
<span class="legend-item">历史样本累计 错误 {{ redCount({ severityCounts: severityTotals }) }} / 告 {{ warningCount({ severityCounts: severityTotals }) }}</span>
<span class="legend-item">{{ cadence.alert }}</span>
</div>
</section>
@@ -538,7 +611,11 @@ createApp({
>
<span class="muted">{{ formatDate(run.updatedAt || run.createdAt) }}</span>
<span class="severity-line"><span class="timeline-marker"></span><strong>{{ run.scenarioId || shortId(run.id) }}</strong></span>
<span class="tag" :class="severityClass(run)">{{ findingCount(run) }} 项</span>
<span class="run-alert-tags">
<span v-if="runCheckErrorCount(run) > 0" class="tag red">错误 {{ runCheckErrorCount(run) }}</span>
<span v-if="runCheckWarningCount(run) > 0" class="tag warning">告警 {{ runCheckWarningCount(run) }}</span>
<span v-if="runCheckAlertCount(run) === 0" class="tag healthy">正常</span>
</span>
</button>
<div v-if="timelineRuns.length === 0" class="empty">暂无时间线记录</div>
</div>
@@ -559,7 +636,7 @@ createApp({
<strong>{{ redCount({ severityCounts: severityTotals }) }}</strong>
</div>
<div class="metric">
<span>历史告样本</span>
<span>历史告样本</span>
<strong>{{ warningCount({ severityCounts: severityTotals }) }}</strong>
</div>
<div class="metric">
@@ -584,7 +661,7 @@ createApp({
<select v-model="severityFilter" aria-label="严重级别筛选">
<option value="">全部</option>
<option value="red">错误</option>
<option value="warning">告</option>
<option value="warning">告</option>
<option value="info">信息</option>
<option value="healthy">正常</option>
</select>
@@ -600,7 +677,11 @@ createApp({
>
<span class="row-line">
<span class="severity-line"><span class="severity-dot"></span><strong>{{ run.scenarioId || shortId(run.id) }}</strong></span>
<span class="tag" :class="severityClass(run)">{{ findingCount(run) }}</span>
<span class="run-alert-tags">
<span v-if="runCheckErrorCount(run) > 0" class="tag red" data-run-error-tag="true">错误 {{ runCheckErrorCount(run) }}</span>
<span v-if="runCheckWarningCount(run) > 0" class="tag warning" data-run-warning-tag="true">告警 {{ runCheckWarningCount(run) }}</span>
<span v-if="runCheckAlertCount(run) === 0" class="tag healthy">正常</span>
</span>
</span>
<span class="row-line muted">
<span>{{ run.status || "-" }}</span>
@@ -625,7 +706,7 @@ createApp({
<div class="detail-grid">
<div class="metric"><span>状态</span><strong>{{ selectedRun.status || "-" }}</strong></div>
<div class="metric"><span>监测项类型</span><strong>{{ findingCount(selectedRun) }}</strong></div>
<div class="metric"><span>错误/告样本</span><strong>{{ alertSampleCount(selectedRun) }}</strong></div>
<div class="metric"><span>错误/告样本</span><strong>{{ alertSampleCount(selectedRun) }}</strong></div>
<div class="metric"><span>全部样本</span><strong>{{ findingSampleCount(selectedRun) }}</strong></div>
<div class="metric"><span>Observer</span><strong>{{ selectedRun.observerId || "-" }}</strong></div>
<div class="metric"><span>更新时间</span><strong>{{ formatDate(selectedRun.updatedAt || selectedRun.createdAt) }}</strong></div>
@@ -663,6 +744,8 @@ createApp({
:data-check-scope="checkScope"
:data-check-run-id="checkScopeRun ? checkScopeRun.id : ''"
:data-check-type-count="scopedCheckSummary.typeCount"
:data-check-error-type-count="scopedCheckSummary.errorTypeCount"
:data-check-warning-type-count="scopedCheckSummary.warningTypeCount"
:data-check-alert-type-count="scopedCheckSummary.alertTypeCount"
:data-check-error-samples="scopedCheckSummary.errorSamples"
:data-check-warning-samples="scopedCheckSummary.warningSamples"
@@ -675,7 +758,7 @@ createApp({
<h2 id="findings-heading">监测项</h2>
<p class="muted">{{ checkScopeText }}</p>
</div>
<span class="tag">错误/告样本 {{ scopedCheckSummary.alertSamples }}</span>
<span class="tag">错误/告样本 {{ scopedCheckSummary.alertSamples }}</span>
</div>
<div class="filter-row">
<select v-model="checkScope" aria-label="监测项作用域">
@@ -692,9 +775,9 @@ createApp({
<option value="all">全部历史</option>
</select>
<select v-model="checkSeverityFilter" aria-label="监测项等级筛选">
<option value="alert">错误+告</option>
<option value="alert">错误+告</option>
<option value="red">错误</option>
<option value="warning">告</option>
<option value="warning">告</option>
<option value="info">信息</option>
<option value="all">全部</option>
</select>
@@ -703,28 +786,101 @@ createApp({
<div class="check-summary" aria-label="监测项摘要">
<div class="metric"><span>当前作用域</span><strong>{{ checkScope === "history" ? timeWindowLabel(checkTimeWindow) : shortId(checkScopeRun && checkScopeRun.id) }}</strong></div>
<div class="metric"><span>监测项类型</span><strong>{{ scopedCheckSummary.typeCount }}</strong></div>
<div class="metric"><span>错误/告类型</span><strong>{{ scopedCheckSummary.alertTypeCount }}</strong></div>
<div class="metric"><span>错误/告类型</span><strong>{{ scopedCheckSummary.alertTypeCount }}</strong></div>
<div class="metric"><span>错误样本</span><strong>{{ scopedCheckSummary.errorSamples }}</strong></div>
<div class="metric"><span>告样本</span><strong>{{ scopedCheckSummary.warningSamples }}</strong></div>
<div class="metric"><span>错误/告样本</span><strong>{{ scopedCheckSummary.alertSamples }}</strong></div>
<div class="metric"><span>告样本</span><strong>{{ scopedCheckSummary.warningSamples }}</strong></div>
<div class="metric"><span>错误/告样本</span><strong>{{ scopedCheckSummary.alertSamples }}</strong></div>
</div>
<div class="finding-list">
<article
v-for="item in visibleCheckFindings"
:key="item.code || item.findingId || item.latestRunId"
class="finding-card"
:class="severityClass(item)"
>
<span class="row-line">
<span class="severity-line"><span class="severity-dot"></span><span class="check-code">{{ findingCode(item) }}</span><strong>{{ findingTitle(item) }}</strong></span>
<span class="tag" :class="severityClass(item)">{{ levelLabel(item) }} · {{ findingGroupCountLabel(item) }}</span>
</span>
<p class="muted">{{ rootCauseText(item) }}</p>
<p v-if="item.nextAction" class="muted">处理: {{ item.nextAction }}</p>
</article>
<div class="finding-list check-table-wrap">
<table v-if="visibleCheckFindings.length > 0" class="check-table" aria-label="监测项列表">
<thead>
<tr>
<th scope="col">编号</th>
<th scope="col">等级</th>
<th scope="col">标题</th>
<th scope="col">样本</th>
<th scope="col">运行记录</th>
<th scope="col">时间</th>
<th scope="col">处理建议</th>
<th scope="col">详情</th>
</tr>
</thead>
<tbody>
<tr
v-for="(item, index) in visibleCheckFindings"
:key="checkRowKey(item, index)"
class="check-row"
:class="severityClass(item)"
data-check-row="true"
tabindex="0"
role="button"
:aria-label="'查看监测项详情 ' + findingCode(item) + ' ' + findingTitle(item)"
:data-check-code="findingCode(item)"
@click="openCheckDetail(item)"
@keydown.enter.prevent="openCheckDetail(item)"
@keydown.space.prevent="openCheckDetail(item)"
>
<td><span class="check-code">{{ findingCode(item) }}</span></td>
<td><span class="tag" :class="severityClass(item)">{{ levelLabel(item) }}</span></td>
<td class="check-title-cell"><strong>{{ findingTitle(item) }}</strong><span>{{ rootCauseText(item) }}</span></td>
<td>{{ findingGroupCountLabel(item) }}</td>
<td class="mono">{{ checkRunText(item) }}</td>
<td>{{ checkTimeText(item) }}</td>
<td>{{ checkActionText(item) }}</td>
<td><span class="detail-link">查看</span></td>
</tr>
</tbody>
</table>
<div v-if="visibleCheckFindings.length === 0" class="empty">暂无匹配监测项</div>
</div>
</section>
<div
v-if="activeCheckItem"
class="check-dialog-backdrop"
data-check-dialog="true"
@click.self="closeCheckDetail"
@keydown.esc="closeCheckDetail"
>
<section class="check-dialog" role="dialog" aria-modal="true" aria-labelledby="check-dialog-title" tabindex="-1">
<header class="check-dialog-header">
<div>
<span class="check-code">{{ findingCode(activeCheckItem) }}</span>
<h2 id="check-dialog-title">{{ findingTitle(activeCheckItem) }}</h2>
<p class="muted">{{ checkScopeText }}</p>
</div>
<div class="check-dialog-actions">
<span class="tag" :class="severityClass(activeCheckItem)">{{ levelLabel(activeCheckItem) }} · {{ findingGroupCountLabel(activeCheckItem) }}</span>
<button class="dialog-close" type="button" aria-label="关闭监测项详情" @click="closeCheckDetail">关闭</button>
</div>
</header>
<div class="check-dialog-body">
<section class="detail-card">
<h3>基本信息</h3>
<div class="summary-list">
<div v-for="row in checkDetailRows(activeCheckItem)" :key="row.key" class="summary-row">
<span>{{ row.label }}</span>
<strong>{{ row.value }}</strong>
</div>
</div>
</section>
<section class="detail-card">
<h3>用户影响</h3>
<p>{{ rootCauseText(activeCheckItem) }}</p>
<p class="muted">处理建议: {{ checkActionText(activeCheckItem) }}</p>
</section>
<section class="detail-card detail-card-wide">
<h3>关联证据</h3>
<div class="summary-list">
<div v-for="row in checkEvidenceRows(activeCheckItem)" :key="row.key" class="summary-row">
<span>{{ row.label }}</span>
<strong>{{ row.value }}</strong>
</div>
</div>
</section>
</div>
</section>
</div>
</div>
`,
}).mount("#monitor-web-root");
@@ -775,27 +931,6 @@ function warningCount(item) {
return number(counts.warning) + number(counts.warn) + number(counts.amber);
}
function trendSeverityCounts(item) {
const counts = item?.severityCounts;
return counts && typeof counts === "object" && !Array.isArray(counts) ? counts : null;
}
function trendErrorCount(item) {
const counts = trendSeverityCounts(item);
if (!counts) return 0;
return number(counts.red) + number(counts.critical) + number(counts.error);
}
function trendWarningCount(item) {
const counts = trendSeverityCounts(item);
if (!counts) return 0;
return number(counts.warning) + number(counts.warn) + number(counts.amber);
}
function trendTotalCount(item) {
return trendErrorCount(item) + trendWarningCount(item);
}
function findingCount(item) {
if (Number.isFinite(Number(item?.findingTypeCount))) return Number(item.findingTypeCount);
if (Number.isFinite(Number(item?.findingCount))) return Number(item.findingCount);
@@ -836,6 +971,19 @@ function summarizeCheckRows(rows) {
};
}
function emptyCheckSummary() {
return {
typeCount: 0,
errorTypeCount: 0,
warningTypeCount: 0,
alertTypeCount: 0,
errorSamples: 0,
warningSamples: 0,
alertSamples: 0,
allSamples: 0,
};
}
function checkMatchesLevel(item, filter) {
const value = String(filter || "alert");
const bucket = severityBucket(item);
@@ -931,7 +1079,7 @@ function levelLabel(item) {
const value = String(item?.checkLevel || item?.severity || item?.level || "").toLowerCase();
if (["critical", "red"].includes(value)) return "严重";
if (["error", "blocked", "failed"].includes(value)) return "错误";
if (["warning", "warn", "amber"].includes(value)) return "告";
if (["warning", "warn", "amber"].includes(value)) return "告";
if (["info", "notice"].includes(value)) return "信息";
return "未知";
}
@@ -965,6 +1113,61 @@ function findingSearchText(item) {
].filter((value) => value !== null && value !== undefined).join(" ").toLowerCase();
}
function checkRowKey(item, index) {
return [
findingCode(item),
item?.latestRunId || item?.runId || item?.run?.id || "",
item?.sampleSeq ?? item?.count ?? index,
].join(":");
}
function checkRunText(item) {
return shortId(item?.latestRunId || item?.runId || item?.run?.id || item?.scenarioId || "");
}
function checkTimeText(item) {
const value = item?.latestRunUpdatedAt || item?.updatedAt || item?.updated_at || item?.createdAt || item?.created_at || item?.timestamp || "";
return value ? formatDate(value) : "-";
}
function checkActionText(item) {
return safeUserText(item?.nextAction || item?.action || item?.recommendation) || "查看详情后处理";
}
function checkDetailRows(item) {
if (!item) return [{ key: "empty", label: "状态", value: "未选择监测项" }];
return [
{ key: "code", label: "编号", value: findingCode(item) },
{ key: "level", label: "等级", value: levelLabel(item) },
{ key: "samples", label: "样本", value: findingGroupCountLabel(item) },
{ key: "run", label: "运行记录", value: checkRunText(item) },
{ key: "time", label: "时间", value: checkTimeText(item) },
{ key: "scenario", label: "场景", value: safeDetailValue(item?.scenarioId || item?.scenario_id) },
{ key: "observer", label: "观察任务", value: safeDetailValue(item?.observerId || item?.observer_id) },
{ key: "report", label: "报告", value: shortHash(item?.reportJsonSha256 || item?.report_json_sha256 || item?.reportSha256 || "") || "-" },
].filter((row) => row.value !== "");
}
function checkEvidenceRows(item) {
if (!item) return [{ key: "empty", label: "状态", value: "未选择监测项" }];
const evidence = item?.evidence && typeof item.evidence === "object" && !Array.isArray(item.evidence) ? item.evidence : {};
const rows = [
{ key: "summary", label: "证据摘要", value: safeUserText(item?.evidenceSummary || evidence.summary || item?.summary) || rootCauseText(item) },
{ key: "sample", label: "样本序号", value: safeDetailValue(item?.sampleSeq ?? evidence.sampleSeq) },
{ key: "page", label: "页面", value: safeDetailValue(item?.pageRole || evidence.pageRole) },
{ key: "command", label: "命令编号", value: safeDetailValue(item?.commandId || evidence.commandId) },
{ key: "range", label: "采集范围", value: safeDetailValue(item?.sentinelRange || evidence.sentinelRange) },
{ key: "blocking", label: "阻塞状态", value: item?.blocking === true ? "阻塞" : "非阻塞" },
].filter((row) => row.value !== "" && row.value !== "-");
return rows.length > 0 ? rows : [{ key: "none", label: "证据摘要", value: "已记录到报告详情。" }];
}
function safeDetailValue(value) {
if (value === null || value === undefined || value === "") return "-";
const text = String(value).replace(/\s+/g, " ").trim();
return text.length > 0 ? text : "-";
}
function checkDisplay(item) {
const rawCode = rawCheckCode(item);
const registered = checkDisplayCatalog[rawCode];
@@ -1009,10 +1212,10 @@ function detailSummaryRows(detail) {
{ key: "scenario", label: "场景", value: run.scenarioId || run.scenario_id || "-" },
{ key: "status", label: "状态", value: run.status || "-" },
{ key: "checks", label: "监测项类型", value: String(findingCount(run)) },
{ key: "alertSamples", label: "错误/告样本", value: String(alertSampleCount(run)) },
{ key: "alertSamples", label: "错误/告样本", value: String(alertSampleCount(run)) },
{ key: "allSamples", label: "全部样本", value: String(findingSampleCount(run)) },
{ key: "error", label: "错误样本", value: String(redCount({ severityCounts: counts })) },
{ key: "warning", label: "告样本", value: String(warningCount({ severityCounts: counts })) },
{ key: "warning", label: "告样本", value: String(warningCount({ severityCounts: counts })) },
{ key: "observer", label: "Observer", value: run.observerId || run.observer_id || "-" },
{ key: "updated", label: "更新时间", value: formatAbsoluteDate(run.updatedAt || run.updated_at || run.createdAt || run.created_at) },
{ key: "report", label: "报告", value: shortHash(artifacts.reportJsonSha256 || run.reportJsonSha256 || run.report_json_sha256 || "") || "-" },
@@ -1033,7 +1236,7 @@ function statusLabel(status) {
const value = String(status || "");
if (value === "blocked") return "阻塞";
if (value === "degraded") return "降级";
if (value === "warning") return "告";
if (value === "warning") return "告";
if (value === "healthy") return "健康";
return "空闲";
}
+117 -31
View File
@@ -2266,10 +2266,11 @@ const dom = await page.evaluate(async ({ expectedRoutePrefix, expectedSentinelId
const detailHeader = detailPane?.querySelector(".pane-header");
const checksHeader = checksPanel?.querySelector(".pane-header");
const internalTextPattern = /||Trace|trace|Shell|API|DOM|Console|console|Runner|runner|JSONL|steer|facts||HTTP|http|requestfailed|pageerror|Final Response|Code Agent|web-probe|observe|analyzer|/u;
const cards = Array.from(document.querySelectorAll(".finding-card")).slice(0, 8).map((card) => ({
code: String(card.querySelector(".check-code")?.textContent || "").trim(),
title: String(card.querySelector("strong")?.textContent || "").trim(),
body: String(card.textContent || "").replace(/\s+/g, " ").trim().slice(0, 180),
const checkRows = Array.from(document.querySelectorAll("[data-check-row='true']"));
const cards = checkRows.slice(0, 8).map((row) => ({
code: String(row.querySelector(".check-code")?.textContent || "").trim(),
title: String(row.querySelector(".check-title-cell strong")?.textContent || row.querySelector("strong")?.textContent || "").trim(),
body: String(row.textContent || "").replace(/\s+/g, " ").trim().slice(0, 180),
}));
const badCardTitles = cards.filter((card) => internalTextPattern.test(card.title));
const badCardBodies = cards.filter((card) => internalTextPattern.test(card.body));
@@ -2280,9 +2281,9 @@ const dom = await page.evaluate(async ({ expectedRoutePrefix, expectedSentinelId
return match ? Number(match[1]) : null;
};
const chartCounts = {
error: legendNumber("错误"),
warning: legendNumber("警"),
total: legendNumber("错误+告合计"),
error: legendNumber("最新点错误"),
warning: legendNumber("最新点告警"),
total: legendNumber("错误+告合计"),
};
chartCounts.ok = typeof chartCounts.error === "number" && typeof chartCounts.warning === "number" && typeof chartCounts.total === "number"
? chartCounts.total === chartCounts.error + chartCounts.warning
@@ -2302,9 +2303,13 @@ const dom = await page.evaluate(async ({ expectedRoutePrefix, expectedSentinelId
const latestRunCounts = {
runId: latestRun?.id || latestRun?.runId || null,
typeCount: numberValue(latestRun?.findingTypeCount ?? latestRun?.findingCount ?? latestRun?.finding_count),
error: errorSampleCount(latestCounts),
warning: warningSampleCount(latestCounts),
total: errorSampleCount(latestCounts) + warningSampleCount(latestCounts),
error: 0,
warning: 0,
total: 0,
all: 0,
errorSamples: errorSampleCount(latestCounts),
warningSamples: warningSampleCount(latestCounts),
alertSamples: errorSampleCount(latestCounts) + warningSampleCount(latestCounts),
allSamples: allSampleCount(latestCounts),
};
const latestDetailPayload = latestRunCounts.runId
@@ -2325,6 +2330,8 @@ const dom = await page.evaluate(async ({ expectedRoutePrefix, expectedSentinelId
const sum = (items) => items.reduce((total, row) => total + sampleCount(row), 0);
return {
typeCount: rows.length,
errorTypeCount: errorRows.length,
warningTypeCount: warningRows.length,
alertTypeCount: errorRows.length + warningRows.length,
errorSamples: sum(errorRows),
warningSamples: sum(warningRows),
@@ -2332,6 +2339,11 @@ const dom = await page.evaluate(async ({ expectedRoutePrefix, expectedSentinelId
};
};
const latestDetailSummary = summarizeRows(latestDetailRows);
latestRunCounts.typeCount = latestDetailSummary.typeCount;
latestRunCounts.error = latestDetailSummary.errorTypeCount;
latestRunCounts.warning = latestDetailSummary.warningTypeCount;
latestRunCounts.total = latestDetailSummary.alertTypeCount;
latestRunCounts.all = latestDetailSummary.typeCount;
const workspaceRect = workspace?.getBoundingClientRect();
const checksRect = checksPanel?.getBoundingClientRect();
const heightSummary = (rect) => {
@@ -2368,30 +2380,46 @@ const dom = await page.evaluate(async ({ expectedRoutePrefix, expectedSentinelId
scope: checksPanel?.getAttribute("data-check-scope") || null,
runId: checksPanel?.getAttribute("data-check-run-id") || null,
typeCount: numberValue(checksPanel?.getAttribute("data-check-type-count")),
errorTypeCount: numberValue(checksPanel?.getAttribute("data-check-error-type-count")),
warningTypeCount: numberValue(checksPanel?.getAttribute("data-check-warning-type-count")),
alertTypeCount: numberValue(checksPanel?.getAttribute("data-check-alert-type-count")),
errorSamples: numberValue(checksPanel?.getAttribute("data-check-error-samples")),
warningSamples: numberValue(checksPanel?.getAttribute("data-check-warning-samples")),
alertSamples: numberValue(checksPanel?.getAttribute("data-check-alert-samples")),
visibleCardCount: document.querySelectorAll(".finding-list .finding-card").length,
visibleRowCount: document.querySelectorAll("[data-check-row='true']").length,
visibleAlertSamples: numberValue(checksPanel?.getAttribute("data-visible-check-alert-samples")),
matchesLatestRun: false,
matchesRunDetail: false,
belowWorkspace: Boolean(workspaceRect && checksRect && checksRect.top >= workspaceRect.bottom - 2),
fullWidth: Boolean(workspaceRect && checksRect && checksRect.width >= workspaceRect.width - 2),
};
const selectedRunRow = document.querySelector(".run-list .run-row.selected");
const selectedRunTags = {
error: numberValue(String(selectedRunRow?.querySelector("[data-run-error-tag='true']")?.textContent || "").match(/(\\d+)/u)?.[1]),
warning: numberValue(String(selectedRunRow?.querySelector("[data-run-warning-tag='true']")?.textContent || "").match(/(\\d+)/u)?.[1]),
errorVisible: Boolean(selectedRunRow?.querySelector("[data-run-error-tag='true']")),
warningVisible: Boolean(selectedRunRow?.querySelector("[data-run-warning-tag='true']")),
matchesRunDetail: false,
};
selectedRunTags.matchesRunDetail = selectedRunTags.error === latestDetailSummary.errorTypeCount
&& selectedRunTags.warning === latestDetailSummary.warningTypeCount
&& selectedRunTags.errorVisible === (latestDetailSummary.errorTypeCount > 0)
&& selectedRunTags.warningVisible === (latestDetailSummary.warningTypeCount > 0);
checkScope.matchesLatestRun = checkScope.present === true
&& checkScope.scope === "run"
&& checkScope.runId === latestRunCounts.runId
&& checkScope.errorSamples === latestRunCounts.error
&& checkScope.warningSamples === latestRunCounts.warning
&& checkScope.alertSamples === latestRunCounts.total;
&& checkScope.errorTypeCount === latestRunCounts.error
&& checkScope.warningTypeCount === latestRunCounts.warning
&& checkScope.alertTypeCount === latestRunCounts.total;
checkScope.matchesRunDetail = checkScope.present === true
&& checkScope.typeCount === latestDetailSummary.typeCount
&& checkScope.errorTypeCount === latestDetailSummary.errorTypeCount
&& checkScope.warningTypeCount === latestDetailSummary.warningTypeCount
&& checkScope.alertTypeCount === latestDetailSummary.alertTypeCount
&& checkScope.errorSamples === latestDetailSummary.errorSamples
&& checkScope.warningSamples === latestDetailSummary.warningSamples
&& checkScope.alertSamples === latestDetailSummary.alertSamples
&& checkScope.visibleCardCount === latestDetailSummary.alertTypeCount
&& checkScope.visibleRowCount === latestDetailSummary.alertTypeCount
&& checkScope.visibleAlertSamples === latestDetailSummary.alertSamples;
const overviewCounts = overviewPayload?.severityCounts && typeof overviewPayload.severityCounts === "object" && !Array.isArray(overviewPayload.severityCounts)
? overviewPayload.severityCounts
@@ -2406,6 +2434,32 @@ const dom = await page.evaluate(async ({ expectedRoutePrefix, expectedSentinelId
&& chartCounts.error === latestRunCounts.error
&& chartCounts.warning === latestRunCounts.warning
&& chartCounts.total === latestRunCounts.total;
const trendPanel = document.querySelector(".trend-panel");
const trendLegend = document.querySelector(".trend-panel .trend-legend");
const trendPanelRect = trendPanel?.getBoundingClientRect();
const trendLegendRect = trendLegend?.getBoundingClientRect();
const trendPanelCompact = {
present: Boolean(trendPanelRect && trendLegendRect),
bottomSlackPx: trendPanelRect && trendLegendRect ? Math.round(trendPanelRect.bottom - trendLegendRect.bottom) : null,
ok: Boolean(trendPanelRect && trendLegendRect && trendPanelRect.bottom - trendLegendRect.bottom <= 28),
};
const firstCheckRow = document.querySelector("[data-check-row='true']");
let checkDialog = { opened: false, title: "", width: null, height: null, large: false };
if (firstCheckRow instanceof HTMLElement) {
firstCheckRow.click();
await new Promise((resolve) => window.setTimeout(resolve, 80));
const dialog = document.querySelector("[data-check-dialog='true'] .check-dialog");
const rect = dialog?.getBoundingClientRect();
checkDialog = {
opened: Boolean(dialog),
title: String(dialog?.querySelector("#check-dialog-title")?.textContent || "").trim(),
width: rect ? Math.round(rect.width) : null,
height: rect ? Math.round(rect.height) : null,
large: Boolean(rect && rect.width >= Math.min(900, window.innerWidth * 0.7) && rect.height >= Math.min(460, window.innerHeight * 0.5)),
};
const close = dialog?.querySelector("button[aria-label='关闭监测项详情']");
if (close instanceof HTMLElement) close.click();
}
const datasetSentinelId = root?.getAttribute("data-sentinel-id") || "";
const finalPath = new URL(window.location.href).pathname.replace(/\/+$/u, "") || "/";
const expectedPath = expectedRoutePrefix.replace(/\/+$/u, "") || "/";
@@ -2470,7 +2524,7 @@ const dom = await page.evaluate(async ({ expectedRoutePrefix, expectedSentinelId
subtitle: text(".subtitle"),
summaryText: text(".status-strip"),
runRows: document.querySelectorAll(".run-list .run-row").length,
findingItems: document.querySelectorAll(".finding-list .finding-card").length,
checkRows: document.querySelectorAll("[data-check-row='true']").length,
badCardTitleCount: badCardTitles.length,
badCardBodyCount: badCardBodies.length,
trendCurve: Boolean(trend),
@@ -2481,6 +2535,9 @@ const dom = await page.evaluate(async ({ expectedRoutePrefix, expectedSentinelId
latestRunCounts,
latestDetailSummary,
checkScope,
selectedRunTags,
trendPanelCompact,
checkDialog,
overviewSamples,
panelHeights,
scopeLabels: {
@@ -2515,7 +2572,7 @@ const dom = await page.evaluate(async ({ expectedRoutePrefix, expectedSentinelId
return {
visible: Boolean(element && body.length > 0),
text: body.slice(0, 240),
hasValues: /\s+\d+/u.test(body) && /\s+\d+/u.test(body) && /\s+\d+/u.test(body),
hasValues: /\s+\d+/u.test(body) && /\s+\d+/u.test(body) && /\s+\d+/u.test(body),
hasTime: /UTC/u.test(body) || /\d{4}-\d{2}-\d{2}/u.test(body),
};
}
@@ -2573,6 +2630,8 @@ const runFilterProbe = await page.evaluate(async ({ expectedRoutePrefix }) => {
const sum = (items) => items.reduce((total, row) => total + sampleCount(row), 0);
return {
typeCount: rows.length,
errorTypeCount: errorRows.length,
warningTypeCount: warningRows.length,
alertTypeCount: errorRows.length + warningRows.length,
errorSamples: sum(errorRows),
warningSamples: sum(warningRows),
@@ -2581,16 +2640,21 @@ const runFilterProbe = await page.evaluate(async ({ expectedRoutePrefix }) => {
};
const panelCounts = () => {
const panel = document.querySelector("[data-monitor-checks='true']");
const selectedRunRow = document.querySelector(".run-list .run-row.selected");
return {
present: Boolean(panel),
runId: panel?.getAttribute("data-check-run-id") || null,
typeCount: numberValue(panel?.getAttribute("data-check-type-count")),
errorTypeCount: numberValue(panel?.getAttribute("data-check-error-type-count")),
warningTypeCount: numberValue(panel?.getAttribute("data-check-warning-type-count")),
alertTypeCount: numberValue(panel?.getAttribute("data-check-alert-type-count")),
errorSamples: numberValue(panel?.getAttribute("data-check-error-samples")),
warningSamples: numberValue(panel?.getAttribute("data-check-warning-samples")),
alertSamples: numberValue(panel?.getAttribute("data-check-alert-samples")),
visibleCardCount: document.querySelectorAll(".finding-list .finding-card").length,
visibleRowCount: document.querySelectorAll("[data-check-row='true']").length,
visibleAlertSamples: numberValue(panel?.getAttribute("data-visible-check-alert-samples")),
selectedRunErrorTag: numberValue(String(selectedRunRow?.querySelector("[data-run-error-tag='true']")?.textContent || "").match(/(\\d+)/u)?.[1]),
selectedRunWarningTag: numberValue(String(selectedRunRow?.querySelector("[data-run-warning-tag='true']")?.textContent || "").match(/(\\d+)/u)?.[1]),
};
};
const waitForRun = async (runId) => {
@@ -2620,12 +2684,16 @@ const runFilterProbe = await page.evaluate(async ({ expectedRoutePrefix }) => {
const observed = panelCounts();
const matchesRunDetail = observed.runId === targetRunId
&& observed.typeCount === expected.typeCount
&& observed.errorTypeCount === expected.errorTypeCount
&& observed.warningTypeCount === expected.warningTypeCount
&& observed.alertTypeCount === expected.alertTypeCount
&& observed.errorSamples === expected.errorSamples
&& observed.warningSamples === expected.warningSamples
&& observed.alertSamples === expected.alertSamples
&& observed.visibleCardCount === expected.alertTypeCount
&& observed.visibleAlertSamples === expected.alertSamples;
&& observed.visibleRowCount === expected.alertTypeCount
&& observed.visibleAlertSamples === expected.alertSamples
&& observed.selectedRunErrorTag === expected.errorTypeCount
&& observed.selectedRunWarningTag === expected.warningTypeCount;
return {
ok: panelReady === true && matchesRunDetail === true,
requestedRunId,
@@ -2657,6 +2725,7 @@ const ok = !navigationError
&& dom.chartCounts?.matchesLatestRun === true
&& dom.checkScope?.matchesLatestRun === true
&& dom.checkScope?.matchesRunDetail === true
&& dom.selectedRunTags?.matchesRunDetail === true
&& dom.runFilterProbe?.ok === true
&& dom.runFilterProbe?.requestedOptionPresent === true
&& dom.checkScope?.belowWorkspace === true
@@ -2664,6 +2733,10 @@ const ok = !navigationError
&& dom.scopeLabels?.latestPointLegend === true
&& dom.scopeLabels?.historicalSamples === true
&& (dom.trendDotCount === 0 || (dom.trendTooltip?.visible === true && dom.trendTooltip?.hasValues === true && dom.trendTooltip?.hasTime === true))
&& dom.trendPanelCompact?.ok === true
&& dom.checkRows > 0
&& dom.checkDialog?.opened === true
&& dom.checkDialog?.large === true
&& dom.badCardTitleCount === 0
&& dom.badCardBodyCount === 0
&& dom.timelineVisible === true
@@ -4966,6 +5039,9 @@ function renderDashboardResult(result: Record<string, unknown>): string {
const latestRunCounts = record(dom.latestRunCounts);
const latestDetailSummary = record(dom.latestDetailSummary);
const checkScope = record(dom.checkScope);
const selectedRunTags = record(dom.selectedRunTags);
const trendPanelCompact = record(dom.trendPanelCompact);
const checkDialog = record(dom.checkDialog);
const runFilterProbe = record(dom.runFilterProbe);
const runFilterObserved = record(runFilterProbe.observed);
const runFilterExpected = record(runFilterProbe.expected);
@@ -4982,11 +5058,11 @@ function renderDashboardResult(result: Record<string, unknown>): string {
"",
table(["NODE", "LANE", "SENTINEL", "STATUS", "URL"], [[result.node, result.lane, result.sentinelId, result.ok === true ? "pass" : "blocked", result.publicUrl]]),
"",
table(["HTTP", "SHELL", "RUN_ROWS", "FINDINGS", "TABS", "ERRORS", "CONSOLE_ERR", "REQ_FAIL"], [[
table(["HTTP", "SHELL", "RUN_ROWS", "CHECK_ROWS", "TABS", "ERRORS", "CONSOLE_ERR", "REQ_FAIL"], [[
page.httpStatus ?? "-",
dom.shell,
dom.runRows,
dom.findingItems,
dom.checkRows,
dom.detailTabs,
page.pageErrorCount,
page.consoleErrorCount,
@@ -4995,7 +5071,7 @@ function renderDashboardResult(result: Record<string, unknown>): string {
"",
table(["TITLE", "STATUS_TEXT", "CONTRACT", "BASE_PATH"], [[dom.title, dom.statusText, dataset.contractVersion, dataset.basePath ?? "-"]]),
"",
table(["TREND_ERROR", "TREND_WARNING", "TREND_TOTAL", "TREND_EXACT", "MATCH_LATEST", "BAD_TITLE", "BAD_BODY"], [[
table(["TREND_ERR_TYPES", "TREND_ALERT_TYPES", "TREND_TOTAL_TYPES", "TREND_EXACT", "MATCH_LATEST", "BAD_TITLE", "BAD_BODY"], [[
chartCounts.error ?? "-",
chartCounts.warning ?? "-",
chartCounts.total ?? "-",
@@ -5005,36 +5081,46 @@ function renderDashboardResult(result: Record<string, unknown>): string {
dom.badCardBodyCount ?? "-",
]]),
"",
table(["LATEST_RUN", "TYPE_COUNT", "LATEST_ERR", "LATEST_WARN", "LATEST_TOTAL", "LATEST_ALL", "HIST_ERR", "HIST_WARN"], [[
table(["LATEST_RUN", "TYPE_COUNT", "ERR_TYPES", "ALERT_TYPES", "TOTAL_TYPES", "SAMPLE_TOTAL", "HIST_ERR", "HIST_ALERT"], [[
latestRunCounts.runId ?? "-",
latestRunCounts.typeCount ?? "-",
latestRunCounts.error ?? "-",
latestRunCounts.warning ?? "-",
latestRunCounts.total ?? "-",
latestRunCounts.allSamples ?? "-",
latestRunCounts.alertSamples ?? "-",
overviewSamples.error ?? "-",
overviewSamples.warning ?? "-",
]]),
"",
table(["CHECK_SCOPE", "CHECK_RUN", "CHECK_TYPES", "CHECK_ALERT_TYPES", "CHECK_ERR", "CHECK_WARN", "CHECK_TOTAL", "CHECK_MATCH_LATEST", "CHECK_MATCH_DETAIL"], [[
table(["CHECK_SCOPE", "CHECK_RUN", "CHECK_TYPES", "CHECK_ERR_TYPES", "CHECK_ALERT_TYPES", "SAMPLE_ERR", "SAMPLE_ALERT", "CHECK_MATCH_LATEST", "CHECK_MATCH_DETAIL"], [[
checkScope.scope ?? "-",
checkScope.runId ?? "-",
`${checkScope.typeCount ?? "-"}/${latestDetailSummary.typeCount ?? "-"}`,
`${checkScope.errorTypeCount ?? "-"}/${latestDetailSummary.errorTypeCount ?? "-"}`,
`${checkScope.alertTypeCount ?? "-"}/${latestDetailSummary.alertTypeCount ?? "-"}`,
checkScope.errorSamples ?? "-",
checkScope.warningSamples ?? "-",
checkScope.alertSamples ?? "-",
checkScope.matchesLatestRun ?? "-",
checkScope.matchesRunDetail ?? "-",
]]),
"",
table(["CHECK_VISIBLE", "CHECK_VISIBLE_ALERT", "BELOW_WORKSPACE", "FULL_WIDTH"], [[
checkScope.visibleCardCount ?? "-",
table(["CHECK_VISIBLE_ROWS", "CHECK_VISIBLE_ALERT", "RUN_TAG_ERR", "RUN_TAG_ALERT", "RUN_TAG_MATCH", "BELOW_WORKSPACE", "FULL_WIDTH"], [[
checkScope.visibleRowCount ?? "-",
checkScope.visibleAlertSamples ?? "-",
selectedRunTags.error ?? "-",
selectedRunTags.warning ?? "-",
selectedRunTags.matchesRunDetail ?? "-",
checkScope.belowWorkspace ?? "-",
checkScope.fullWidth ?? "-",
]]),
"",
table(["TREND_PANEL_SLACK", "TREND_PANEL_COMPACT", "DETAIL_DIALOG", "DIALOG_LARGE"], [[
trendPanelCompact.bottomSlackPx ?? "-",
trendPanelCompact.ok ?? "-",
checkDialog.opened ?? "-",
checkDialog.large ?? "-",
]]),
"",
table(["WORKSPACE_H", "WORKSPACE_RATIO", "WORKSPACE_80", "CHECKS_H", "CHECKS_RATIO", "CHECKS_80", "PANES_80"], [[
`${workspaceHeight.heightPx ?? "-"}/${workspaceHeight.targetPx ?? "-"}`,
workspaceHeight.ratio ?? "-",
@@ -5045,13 +5131,13 @@ function renderDashboardResult(result: Record<string, unknown>): string {
panelHeights.workspacePaneBounded ?? "-",
]]),
"",
table(["FILTER_RUN", "FILTER_OPTION", "FILTER_TYPES", "FILTER_ALERT_TYPES", "FILTER_ERR", "FILTER_WARN", "FILTER_TOTAL", "FILTER_MATCH_DETAIL"], [[
table(["FILTER_RUN", "FILTER_OPTION", "FILTER_TYPES", "FILTER_ERR_TYPES", "FILTER_ALERT_TYPES", "FILTER_SAMPLE_ERR", "FILTER_SAMPLE_ALERT", "FILTER_MATCH_DETAIL"], [[
runFilterProbe.targetRunId ?? "-",
runFilterProbe.requestedOptionPresent ?? "-",
`${runFilterObserved.typeCount ?? "-"}/${runFilterExpected.typeCount ?? "-"}`,
`${runFilterObserved.errorTypeCount ?? "-"}/${runFilterExpected.errorTypeCount ?? "-"}`,
`${runFilterObserved.alertTypeCount ?? "-"}/${runFilterExpected.alertTypeCount ?? "-"}`,
runFilterObserved.errorSamples ?? "-",
runFilterObserved.warningSamples ?? "-",
runFilterObserved.alertSamples ?? "-",
runFilterProbe.matchesRunDetail ?? "-",
]]),
@@ -10,6 +10,9 @@ const checks: Array<{ readonly path: string; readonly contains: readonly string[
"/api/overview",
"data-monitor-trend-curve",
"data-monitor-independent-scroll",
"data-check-row",
"data-check-dialog",
"错误 / 告警监测项曲线",
"rootCause",
],
},
@@ -19,6 +22,8 @@ const checks: Array<{ readonly path: string; readonly contains: readonly string[
contains: [
".trend-stage",
".workspace-grid",
".check-table",
".check-dialog",
"overflow: hidden",
"overflow: auto",
".trend-red",