Merge pull request #1232 from pikasTech/fix/1213-sentinel-report-timing

补齐哨兵报告 timing 详情可见性
This commit is contained in:
Lyon
2026-06-28 19:39:34 +08:00
committed by GitHub
3 changed files with 52 additions and 4 deletions
@@ -1110,6 +1110,8 @@ function findingSearchText(item) {
item?.rootCause,
item?.evidenceSummary,
item?.nextAction,
item?.timingStatus,
item?.timingSourceOfTruth,
item?.scenarioId,
item?.latestRunId,
].filter((value) => value !== null && value !== undefined).join(" ").toLowerCase();
@@ -1146,6 +1148,7 @@ function checkDetailRows(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: "timing", label: "计时来源", value: checkTimingText(item) },
{ key: "report", label: "报告", value: shortHash(item?.reportJsonSha256 || item?.report_json_sha256 || item?.reportSha256 || "") || "-" },
].filter((row) => row.value !== "");
}
@@ -1159,11 +1162,23 @@ function checkEvidenceRows(item) {
{ 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: "timing", label: "计时状态", value: checkTimingText(item) },
{ key: "blocking", label: "阻塞状态", value: item?.blocking === true ? "阻塞" : "非阻塞" },
].filter((row) => row.value !== "" && row.value !== "-");
return rows.length > 0 ? rows : [{ key: "none", label: "证据摘要", value: "已记录到报告详情。" }];
}
function checkTimingText(item) {
const status = item?.timingStatus || "";
const source = item?.timingSourceOfTruth || item?.expectedElapsedSource || item?.evidenceKind || "";
if (!status && !source) return "";
return [
status ? `status=${safeDetailValue(status)}` : "",
source ? `source=${safeDetailValue(source)}` : "",
item?.timingAlert === true ? "alert=true" : "",
].filter(Boolean).join(" ");
}
function safeDetailValue(value) {
if (value === null || value === undefined || value === "") return "-";
const text = String(value).replace(/\s+/g, " ").trim();
+13 -3
View File
@@ -3453,6 +3453,9 @@ function compactQuickVerifyRecordFinding(value: unknown): Record<string, unknown
rootCauseConfidence: boundQuickVerifyRecordText(item.rootCauseConfidence, 40),
nextAction: boundQuickVerifyRecordText(item.nextAction, 240),
evidenceSummary: stringAtNullable(item, "evidenceSummary") ?? compactQuickVerifyFindingEvidence(item.evidence),
timingSourceOfTruth: boundQuickVerifyRecordText(item.timingSourceOfTruth ?? item.expectedElapsedSource ?? item.evidenceKind, 100),
timingStatus: boundQuickVerifyRecordText(item.timingStatus, 60),
timingAlert: item.timingAlert === true,
blocking: item.blocking === true,
valuesRedacted: true,
};
@@ -4197,7 +4200,7 @@ function readAnalysisSummaryFromWorkspace(state: SentinelCicdState, stateDir: st
"let artifactCount=0; let screenshot=null;",
"function walk(dir){let entries=[]; try{entries=fs.readdirSync(dir,{withFileTypes:true})}catch{return}; for(const e of entries){const p=path.join(dir,e.name); if(e.isDirectory()) walk(p); else { artifactCount++; if(/\\.png$/i.test(e.name)){const b=read(p); screenshot={path:p,sha256:sha(b),bytes:b?b.length:0}; } } }}",
"walk(stateDir);",
"const findings=arr(report?.findings ?? report?.archiveSummary?.redFindings).slice(0,20).map((item)=>{const v=rec(item); return {id:clip(v.id??v.kind??v.code,80),kind:clip(v.kind??v.id??v.code,80),code:clip(v.code??v.kind??v.id,80),severity:clip(v.severity??v.level,32),level:clip(v.level??v.severity,32),count:Number(v.count??v.sampleCount??1),summary:clip(v.summary??v.message,220),message:clip(v.message??v.summary,220),rootCause:clip(v.rootCause,140),rootCauseStatus:clip(v.rootCauseStatus,90),rootCauseConfidence:clip(v.rootCauseConfidence,40),nextAction:clip(v.nextAction,240),evidenceSummary:v.evidence?clip(JSON.stringify(rec(v.evidence)),220):clip(v.evidenceSummary,220),blocking:v.blocking===true,afterRound:v.afterRound??null,canarySessionId:clip(v.canarySessionId,80),routeSessionId:clip(v.routeSessionId,80),activeSessionId:clip(v.activeSessionId,80),consecutiveUserMessageCount:v.consecutiveUserMessageCount??null,sentinelRange:clip(v.sentinelRange,80),sampleSeq:v.sampleSeq??null,traceIds:arr(v.traceIds).slice(0,8).map((x)=>clip(x,80)),pageRole:clip(v.pageRole,32),pageId:clip(v.pageId,80),observerId:clip(v.observerId,80),stateDir:clip(v.stateDir,160),commandId:clip(v.commandId,80),valuesRedacted:true};});",
"const findings=arr(report?.findings ?? report?.archiveSummary?.redFindings).slice(0,20).map((item)=>{const v=rec(item); return {id:clip(v.id??v.kind??v.code,80),kind:clip(v.kind??v.id??v.code,80),code:clip(v.code??v.kind??v.id,80),severity:clip(v.severity??v.level,32),level:clip(v.level??v.severity,32),count:Number(v.count??v.sampleCount??1),summary:clip(v.summary??v.message,220),message:clip(v.message??v.summary,220),rootCause:clip(v.rootCause,140),rootCauseStatus:clip(v.rootCauseStatus,90),rootCauseConfidence:clip(v.rootCauseConfidence,40),nextAction:clip(v.nextAction,240),evidenceSummary:v.evidence?clip(JSON.stringify(rec(v.evidence)),220):clip(v.evidenceSummary,220),timingSourceOfTruth:clip(v.timingSourceOfTruth??v.expectedElapsedSource??v.evidenceKind,100),timingStatus:clip(v.timingStatus,60),timingAlert:v.timingAlert===true,blocking:v.blocking===true,afterRound:v.afterRound??null,canarySessionId:clip(v.canarySessionId,80),routeSessionId:clip(v.routeSessionId,80),activeSessionId:clip(v.activeSessionId,80),consecutiveUserMessageCount:v.consecutiveUserMessageCount??null,sentinelRange:clip(v.sentinelRange,80),sampleSeq:v.sampleSeq??null,traceIds:arr(v.traceIds).slice(0,8).map((x)=>clip(x,80)),pageRole:clip(v.pageRole,32),pageId:clip(v.pageId,80),observerId:clip(v.observerId,80),stateDir:clip(v.stateDir,160),commandId:clip(v.commandId,80),valuesRedacted:true};});",
"const slow=arr(report?.pagePerformanceSlowApi ?? report?.archivePagePerformanceSlowApi).slice(0,8).map((item)=>{const v=rec(item); return {path:clip(v.path??v.route,120),sampleCount:v.sampleCount??null,p95Ms:v.p95Ms??null,maxMs:v.maxMs??null,overFiveSecondCount:v.overFiveSecondCount??null};});",
"console.log(JSON.stringify({ok:!!report,reportOk:!!report&&report.ok!==false,stateDir,reportJsonPath:reportPath,reportJsonSha256:sha(jsonBuf),reportMdPath,reportMdSha256:sha(read(reportMdPath)),findingCount:Number(report?.findingCount??findings.length),artifactCount,screenshot,findings,counts:rec(report?.counts),analysisWindow:rec(report?.analysisWindow??report?.windows?.recent?.summary),pagePerformanceSlowApi:slow,valuesRedacted:true}));",
"NODE",
@@ -4964,7 +4967,7 @@ function renderQuickVerifySummary(input: Record<string, unknown>): string {
`publicOrigin=${input.publicOrigin ?? "-"}`,
"",
"Findings",
findings.length === 0 ? "-" : findings.map((item) => `${item.severity ?? item.level ?? "-"} ${item.kind ?? item.id ?? item.code ?? "-"} count=${item.count ?? "-"} ${item.summary ?? item.message ?? ""}`).join("\n"),
findings.length === 0 ? "-" : findings.map((item) => `${item.severity ?? item.level ?? "-"} ${item.kind ?? item.id ?? item.code ?? "-"} count=${item.count ?? "-"}${formatQuickVerifyTimingSuffix(item)} ${item.summary ?? item.message ?? ""}`).join("\n"),
].join("\n");
}
@@ -5000,10 +5003,17 @@ function renderAuthSessionSwitchQuickVerifySummary(input: Record<string, unknown
commandSteps.length === 0 ? "-" : commandSteps.join("\n"),
"",
"Findings",
findingRows.length === 0 ? "-" : findingRows.map((item) => `${item.severity ?? item.level ?? "-"} ${item.kind ?? item.id ?? item.code ?? "-"} count=${item.count ?? "-"} ${item.summary ?? item.message ?? ""}`).join("\n"),
findingRows.length === 0 ? "-" : findingRows.map((item) => `${item.severity ?? item.level ?? "-"} ${item.kind ?? item.id ?? item.code ?? "-"} count=${item.count ?? "-"}${formatQuickVerifyTimingSuffix(item)} ${item.summary ?? item.message ?? ""}`).join("\n"),
].join("\n");
}
function formatQuickVerifyTimingSuffix(item: Record<string, unknown>): string {
const status = stringAtNullable(item, "timingStatus");
const source = stringAtNullable(item, "timingSourceOfTruth") ?? stringAtNullable(item, "expectedElapsedSource") ?? stringAtNullable(item, "evidenceKind");
if (status === null && source === null) return "";
return ` timing=${[status === null ? null : `status=${status}`, source === null ? null : `source=${source}`].filter((part) => part !== null).join(" ")}`;
}
function renderMaintenanceResult(result: Record<string, unknown>): string {
const serviceHealth = record(result.serviceHealth);
const maintenance = record(result.maintenance);
+24 -1
View File
@@ -870,6 +870,9 @@ function dashboardFindings(config: WebProbeSentinelServiceConfig, db: Database,
rootCauseConfidence: stringOrNull(latestDetail?.rootCauseConfidence),
nextAction: stringOrNull(latestDetail?.nextAction),
evidenceSummary: stringOrNull(latestDetail?.evidenceSummary),
timingSourceOfTruth: stringOrNull(latestDetail?.timingSourceOfTruth),
timingStatus: stringOrNull(latestDetail?.timingStatus),
timingAlert: latestDetail?.timingAlert === true,
traceability: latestRun === null ? null : runTraceability(config, latestRun),
valuesRedacted: true,
});
@@ -1147,6 +1150,9 @@ function enrichFindingRowWithStoredDetail(config: WebProbeSentinelServiceConfig,
rootCauseConfidence: stringOrNull(detail?.rootCauseConfidence),
nextAction: stringOrNull(detail?.nextAction),
evidenceSummary: stringOrNull(detail?.evidenceSummary),
timingSourceOfTruth: stringOrNull(detail?.timingSourceOfTruth),
timingStatus: stringOrNull(detail?.timingStatus),
timingAlert: detail?.timingAlert === true,
valuesRedacted: true,
});
}
@@ -1184,6 +1190,9 @@ function compactStoredFinding(value: unknown): Record<string, unknown> {
rootCauseConfidence: stringOrNull(item.rootCauseConfidence),
nextAction: stringOrNull(item.nextAction),
evidenceSummary: stringOrNull(item.evidenceSummary) ?? compactFindingEvidenceSummary(item.evidence),
timingSourceOfTruth: stringOrNull(item.timingSourceOfTruth) ?? stringOrNull(item.expectedElapsedSource) ?? stringOrNull(item.evidenceKind),
timingStatus: stringOrNull(item.timingStatus),
timingAlert: item.timingAlert === true,
valuesRedacted: true,
};
}
@@ -1540,7 +1549,7 @@ function reportRunView(config: WebProbeSentinelServiceConfig, db: Database, view
const findings = findingsForRun(config, db, selectedRunId, 50);
const views = record(stored.views);
const storedView = record(views[view]);
const renderedText = typeof storedView.renderedText === "string" ? storedView.renderedText : view === "summary" ? renderStoredSummary(row, stored, findings) : view === "findings" ? renderStoredFindings(row, findings) : null;
const renderedText = view === "findings" ? renderStoredFindings(row, findings) : typeof storedView.renderedText === "string" ? storedView.renderedText : view === "summary" ? renderStoredSummary(row, stored, findings) : null;
if (renderedText === null) {
return { ok: false, error: "report-view-not-indexed", runId: selectedRunId, view, availableViews: Object.keys(views), valuesRedacted: true };
}
@@ -1564,14 +1573,28 @@ function formatStoredFindingLine(item: Record<string, unknown>): string {
const status = stringOrNull(item.rootCauseStatus);
const nextAction = stringOrNull(item.nextAction);
const evidence = stringOrNull(item.evidenceSummary);
const timing = formatStoredFindingTiming(item);
return [
`${item.severity ?? "-"} ${code} ${title} count=${item.count ?? "-"} ${summary}`,
timing,
rootCause === null ? null : `rootCause=${rootCause}${status === null ? "" : ` status=${status}`}`,
evidence === null ? null : `evidence=${evidence}`,
nextAction === null ? null : `next=${nextAction}`,
].filter((part) => part !== null).join(" | ");
}
function formatStoredFindingTiming(item: Record<string, unknown>): string | null {
const source = stringOrNull(item.timingSourceOfTruth);
const status = stringOrNull(item.timingStatus);
if (source === null && status === null) return null;
const pieces = [
status === null ? null : `status=${status}`,
source === null ? null : `source=${source}`,
item.timingAlert === true ? "alert=true" : null,
].filter((part): part is string => part !== null);
return `timing=${pieces.join(" ")}`;
}
function renderStoredSummary(row: Record<string, unknown>, stored: Record<string, unknown>, findings: readonly Record<string, unknown>[]): string {
const summary = record(stored.summary);
return [