feat(monitor): show sentinel root causes by default
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user