diff --git a/scripts/assets/web-probe-sentinel-monitor-web/monitor-web.css b/scripts/assets/web-probe-sentinel-monitor-web/monitor-web.css
index 0f9b1d82..2f7db17d 100644
--- a/scripts/assets/web-probe-sentinel-monitor-web/monitor-web.css
+++ b/scripts/assets/web-probe-sentinel-monitor-web/monitor-web.css
@@ -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;
}
}
diff --git a/scripts/assets/web-probe-sentinel-monitor-web/monitor-web.js b/scripts/assets/web-probe-sentinel-monitor-web/monitor-web.js
index a1d05fc1..de62c44d 100644
--- a/scripts/assets/web-probe-sentinel-monitor-web/monitor-web.js
+++ b/scripts/assets/web-probe-sentinel-monitor-web/monitor-web.js
@@ -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({
-
暂无运行数据
最新点错误 {{ trendErrorCount(latestTrendRun) }}
- 最新点警告 {{ trendWarningCount(latestTrendRun) }}
- 最新点错误+警告合计 {{ trendTotalCount(latestTrendRun) }}
- 历史样本累计 错误 {{ redCount({ severityCounts: severityTotals }) }} / 警告 {{ warningCount({ severityCounts: severityTotals }) }}
+ 最新点告警 {{ trendWarningCount(latestTrendRun) }}
+ 最新点错误+告警合计 {{ trendTotalCount(latestTrendRun) }}
+ 历史样本累计 错误 {{ redCount({ severityCounts: severityTotals }) }} / 告警 {{ warningCount({ severityCounts: severityTotals }) }}
{{ cadence.alert }}
@@ -538,7 +611,11 @@ createApp({
>
{{ formatDate(run.updatedAt || run.createdAt) }}
{{ run.scenarioId || shortId(run.id) }}
- {{ findingCount(run) }} 项
+
+ 错误 {{ runCheckErrorCount(run) }}
+ 告警 {{ runCheckWarningCount(run) }}
+ 正常
+
暂无时间线记录
@@ -559,7 +636,7 @@ createApp({
{{ redCount({ severityCounts: severityTotals }) }}
- 历史警告样本
+ 历史告警样本
{{ warningCount({ severityCounts: severityTotals }) }}
@@ -584,7 +661,7 @@ createApp({
@@ -600,7 +677,11 @@ createApp({
>
{{ run.scenarioId || shortId(run.id) }}
- {{ findingCount(run) }}
+
+ 错误 {{ runCheckErrorCount(run) }}
+ 告警 {{ runCheckWarningCount(run) }}
+ 正常
+
{{ run.status || "-" }}
@@ -625,7 +706,7 @@ createApp({
状态{{ selectedRun.status || "-" }}
监测项类型{{ findingCount(selectedRun) }}
-
错误/警告样本{{ alertSampleCount(selectedRun) }}
+
错误/告警样本{{ alertSampleCount(selectedRun) }}
全部样本{{ findingSampleCount(selectedRun) }}
Observer{{ selectedRun.observerId || "-" }}
更新时间{{ formatDate(selectedRun.updatedAt || selectedRun.createdAt) }}
@@ -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({
监测项
{{ checkScopeText }}
- 错误/警告样本 {{ scopedCheckSummary.alertSamples }}
+ 错误/告警样本 {{ scopedCheckSummary.alertSamples }}
@@ -703,28 +786,101 @@ createApp({
当前作用域{{ checkScope === "history" ? timeWindowLabel(checkTimeWindow) : shortId(checkScopeRun && checkScopeRun.id) }}
监测项类型{{ scopedCheckSummary.typeCount }}
-
错误/警告类型{{ scopedCheckSummary.alertTypeCount }}
+
错误/告警类型{{ scopedCheckSummary.alertTypeCount }}
错误样本{{ scopedCheckSummary.errorSamples }}
-
警告样本{{ scopedCheckSummary.warningSamples }}
-
错误/警告样本{{ scopedCheckSummary.alertSamples }}
+
告警样本{{ scopedCheckSummary.warningSamples }}
+
错误/告警样本{{ scopedCheckSummary.alertSamples }}
-
-
-
- {{ findingCode(item) }}{{ findingTitle(item) }}
- {{ levelLabel(item) }} · {{ findingGroupCountLabel(item) }}
-
- {{ rootCauseText(item) }}
- 处理: {{ item.nextAction }}
-
+
+
+
+
+ | 编号 |
+ 等级 |
+ 标题 |
+ 样本 |
+ 运行记录 |
+ 时间 |
+ 处理建议 |
+ 详情 |
+
+
+
+
+ | {{ findingCode(item) }} |
+ {{ levelLabel(item) }} |
+ {{ findingTitle(item) }}{{ rootCauseText(item) }} |
+ {{ findingGroupCountLabel(item) }} |
+ {{ checkRunText(item) }} |
+ {{ checkTimeText(item) }} |
+ {{ checkActionText(item) }} |
+ 查看 |
+
+
+
暂无匹配监测项
+
+
+
+
+
+
+ 基本信息
+
+
+ {{ row.label }}
+ {{ row.value }}
+
+
+
+
+ 用户影响
+ {{ rootCauseText(activeCheckItem) }}
+ 处理建议: {{ checkActionText(activeCheckItem) }}
+
+
+ 关联证据
+
+
+ {{ row.label }}
+ {{ row.value }}
+
+
+
+
+
+
`,
}).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 "空闲";
}
diff --git a/scripts/src/hwlab-node-web-sentinel-cicd.ts b/scripts/src/hwlab-node-web-sentinel-cicd.ts
index ef77da98..f3e9ce97 100644
--- a/scripts/src/hwlab-node-web-sentinel-cicd.ts
+++ b/scripts/src/hwlab-node-web-sentinel-cicd.ts
@@ -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 {
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 {
"",
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 {
"",
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 {
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 {
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 ?? "-",
]]),
diff --git a/scripts/verify-web-probe-sentinel-monitor-web.ts b/scripts/verify-web-probe-sentinel-monitor-web.ts
index dc91a944..9ce2a575 100644
--- a/scripts/verify-web-probe-sentinel-monitor-web.ts
+++ b/scripts/verify-web-probe-sentinel-monitor-web.ts
@@ -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",