feat(monitor): show sentinel root causes by default

This commit is contained in:
Codex
2026-06-27 02:09:17 +00:00
parent 3b2399c902
commit 834ea98e39
2 changed files with 70 additions and 2 deletions
@@ -989,6 +989,17 @@ select {
font-size: 12px;
}
.aggregation-root-cause {
align-items: stretch;
}
.root-cause-chip {
max-width: 100%;
justify-content: flex-start;
white-space: normal;
text-align: left;
}
.finding-list {
display: grid;
gap: 8px;
@@ -1052,6 +1063,10 @@ select {
display: none;
}
.finding-item.is-collapsed {
gap: 5px;
}
.finding-row {
display: flex;
align-items: center;
@@ -1077,6 +1092,21 @@ select {
gap: 6px;
}
.finding-direct-cause {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
gap: 6px;
align-items: start;
color: #1f2937;
font-size: 11px;
line-height: 1.35;
overflow-wrap: anywhere;
}
.finding-direct-cause strong {
color: #1d4ed8;
}
.finding-summary {
color: #1f2937;
font-size: 12px;
@@ -306,6 +306,10 @@ async function loadDashboard(options) {
state.runViews = null;
state.selectedTraceChoiceIndex = 0;
}
if (state.selectedRunId === null) {
const latestRunId = latestRunIdFrom(overview, state.runs);
if (latestRunId) state.selectedRunId = latestRunId;
}
renderDashboard();
if (state.selectedRunId && state.runDetail === null) await selectRun(state.selectedRunId);
} catch (error) {
@@ -477,7 +481,8 @@ function renderFindings() {
refs.findingsList.innerHTML = groups.map((group) => {
const visibleItems = group.items.slice(0, findingVisibleLimit(group.key));
const hiddenCount = Math.max(0, group.items.length - visibleItems.length);
return `<details class="finding-group finding-group-${escapeAttr(group.key)}">
const defaultOpen = group.items.some(findingHasRootCause) || isBlockingSeverity(group.key) || groups.length <= 2;
return `<details class="finding-group finding-group-${escapeAttr(group.key)}"${defaultOpen ? " open" : ""}>
<summary>
<span>${escapeHtml(displaySeverity(group.key))}</span>
<strong>${formatNumber(group.totalCount)}</strong>
@@ -526,11 +531,13 @@ function renderFindingItemCollapsed(item) {
const rootCause = findingRootCauseText(item);
const evidence = findingEvidenceText(item);
const nextAction = item.nextAction || findingNextAction(code);
const directCause = rootCause || nextAction;
return `<article class="finding-item is-collapsed" data-finding-run-id="${escapeAttr(latestRunId)}">
<div class="finding-row" data-finding-toggle>
<span class="finding-title">${escapeHtml(codeLabel)}${codeLabel === code ? "" : ` <small class="mono">${escapeHtml(code)}</small>`}<small> · ${formatNumber(item.count ?? 0)} 次</small></span>
<span class="severity-pill ${severityClass(item.severity)}">${escapeHtml(displaySeverity(item.severity))}</span>
</div>
${directCause ? `<div class="finding-direct-cause"><strong>${rootCause ? "根因" : "下一步"}</strong><span>${escapeHtml(shortText(directCause, 180))}</span></div>` : ""}
<div class="finding-detail">
<div class="finding-summary">${escapeHtml(shortText(displayFindingSummary(code, item.summary || ""), 220))}</div>
<div class="finding-actions">
@@ -586,12 +593,27 @@ function groupedFindingsBySeverity(findings) {
return Array.from(groups.entries())
.map(([key, items]) => ({
key,
items: items.sort((a, b) => Number(b.count || 0) - Number(a.count || 0)),
items: items.sort(compareFindingsForVisibility),
totalCount: items.reduce((sum, item) => sum + Number(item.count || 0), 0),
}))
.sort((a, b) => severityRank(a.key, severityOrder) - severityRank(b.key, severityOrder));
}
function compareFindingsForVisibility(a, b) {
return Number(findingHasRootCause(b)) - Number(findingHasRootCause(a))
|| Number(b.count || 0) - Number(a.count || 0)
|| String(a.code || a.findingId || "").localeCompare(String(b.code || b.findingId || ""));
}
function findingHasRootCause(item) {
return Boolean(findingRootCauseText(item));
}
function isBlockingSeverity(severity) {
const key = String(severity || "").toLowerCase();
return key === "red" || key === "critical" || key === "error";
}
function severityRank(key, order) {
const index = order.indexOf(key);
return index >= 0 ? index : order.length;
@@ -605,9 +627,11 @@ function renderFindingAggregation() {
const severityEntries = aggregateFindings((item) => item.severity || "unknown").slice(0, 5);
const codeEntries = aggregateFindings((item) => item.code || item.findingId || "unknown").slice(0, 5);
const scenarioEntries = aggregateFindings((item) => item.scenarioId || "unknown").slice(0, 5);
const rootCauseEntries = state.findings.filter(findingHasRootCause).sort(compareFindingsForVisibility).slice(0, 3);
refs.findingAggregation.innerHTML = [
aggregationGroup("严重级别", severityEntries.map((item) => ({ ...item, label: displaySeverity(item.key) })), "severity"),
aggregationGroup("代码", codeEntries.map((item) => ({ ...item, label: displayFindingCode(item.key) })), "code"),
rootCauseEntries.length > 0 ? aggregationRootCauseGroup(rootCauseEntries) : "",
aggregationGroup("场景", scenarioEntries, "scenario"),
`<div class="aggregation-group"><span>窗口</span><button type="button" class="filter-chip active">${escapeHtml(state.findingFilters.window || "全部")}</button></div>`,
].join("");
@@ -623,6 +647,15 @@ function aggregationGroup(label, entries, filterKey) {
return `<div class="aggregation-group"><span>${escapeHtml(label)}</span>${chips}</div>`;
}
function aggregationRootCauseGroup(entries) {
const chips = entries.map((item) => {
const code = item.code || item.findingId || "finding";
const text = findingRootCauseText(item);
return `<button type="button" class="filter-chip root-cause-chip" data-finding-filter="code" data-filter-value="${escapeAttr(code)}">${escapeHtml(displayFindingCode(code))}: ${escapeHtml(shortText(text, 96))}</button>`;
}).join("");
return `<div class="aggregation-group aggregation-root-cause"><span>根因</span>${chips}</div>`;
}
function aggregateFindings(keyFn) {
const counts = new Map();
for (const item of state.findings) {
@@ -977,6 +1010,11 @@ function jumpToLatestRun() {
if (latest) selectRun(latest.runId || latest.id);
}
function latestRunIdFrom(overview, runs) {
const latest = overview?.latestRun || null;
return latest?.runId || latest?.id || runs[0]?.runId || runs[0]?.id || "";
}
function toggleRedOnly() {
const active = refs.filterRed.classList.toggle("active");
if (active) {