fix: expose sentinel run timing in monitor

This commit is contained in:
Codex
2026-07-01 00:25:27 +00:00
parent 100a846899
commit e48d77f83a
4 changed files with 188 additions and 9 deletions
@@ -401,7 +401,7 @@ select {
position: absolute;
z-index: 8;
display: grid;
width: min(218px, calc(100% - 16px));
width: min(286px, calc(100% - 16px));
max-width: calc(100% - 16px);
gap: 3px;
transform: translate(-50%, -100%);
@@ -154,6 +154,8 @@ createApp({
const redY = trendY(red);
const warningY = trendY(warning);
const rawTime = run.updatedAt || run.createdAt || "";
const durationText = runDurationText(run);
const configTimingText = runTimingConfigText(run);
return {
id: run.id || String(index),
runId: run.id || "",
@@ -170,8 +172,10 @@ createApp({
rawTime,
timeLabel: formatDate(rawTime),
absoluteTime: formatAbsoluteDate(rawTime),
durationText,
configTimingText,
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} 运行 ${durationText} ${configTimingText}`,
};
}));
const timelineRuns = computed(() => runs.value.slice(0, 16));
@@ -463,6 +467,8 @@ createApp({
trendTotalCount,
trendErrorCount,
trendWarningCount,
runDurationText,
runTimingConfigText,
severityClass,
formatDate,
formatAbsoluteDate,
@@ -578,6 +584,8 @@ createApp({
<strong>{{ shortId(hoveredTrendDot.runId) }}</strong>
<span>{{ hoveredTrendDot.absoluteTime }}</span>
<span>状态 {{ hoveredTrendDot.status }}</span>
<span>运行 {{ hoveredTrendDot.durationText }}</span>
<span v-if="hoveredTrendDot.configTimingText !== '-'">配置 {{ hoveredTrendDot.configTimingText }}</span>
<span>错误 {{ hoveredTrendDot.red }} / 告警 {{ hoveredTrendDot.warning }} / 合计 {{ hoveredTrendDot.total }}</span>
<span v-if="hoveredTrendDot.reportSha">report {{ hoveredTrendDot.reportSha }}</span>
</div>
@@ -615,6 +623,7 @@ createApp({
<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 class="tag">运行 {{ runDurationText(run) }}</span>
</span>
</button>
<div v-if="timelineRuns.length === 0" class="empty">暂无时间线记录</div>
@@ -687,6 +696,7 @@ createApp({
</span>
<span class="row-line muted">
<span>{{ run.status || "-" }}</span>
<span>运行 {{ runDurationText(run) }}</span>
<span>{{ formatDate(run.updatedAt || run.createdAt) }}</span>
</span>
</button>
@@ -707,6 +717,8 @@ createApp({
<h3>摘要</h3>
<div class="detail-grid">
<div class="metric"><span>状态</span><strong>{{ selectedRun.status || "-" }}</strong></div>
<div class="metric"><span>运行分钟</span><strong>{{ runDurationText(selectedRun) }}</strong></div>
<div class="metric"><span>配置周期</span><strong>{{ runTimingConfigText(selectedRun) }}</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>{{ findingSampleCount(selectedRun) }}</strong></div>
@@ -1035,6 +1047,35 @@ function formatDuration(seconds) {
return `${Math.round(value / 86400)}d`;
}
function formatMinutes(value) {
const numberValue = optionalNumber(value);
if (numberValue === null) return "-";
const rounded = Math.round(numberValue * 100) / 100;
return `${Number.isInteger(rounded) ? String(rounded) : String(rounded).replace(/0+$/u, "").replace(/\.$/u, "")} 分钟`;
}
function runDurationText(run) {
const timing = run?.timing || {};
return formatMinutes(optionalNumber(run?.runDurationMinutes, run?.durationMinutes, timing.runDurationMinutes, timing.durationMinutes));
}
function runTimingConfigText(run) {
const timing = run?.timing || {};
const cadence = optionalNumber(run?.scenarioCadenceMinutes, timing.scenarioCadenceMinutes);
const maxRun = optionalNumber(run?.scenarioMaxRunMinutes, timing.scenarioMaxRunMinutes);
const scheduler = optionalNumber(run?.schedulerIntervalMinutes, timing.schedulerIntervalMinutes);
const parts = [];
if (cadence !== null) parts.push(`场景周期 ${formatMinutes(cadence)}`);
if (maxRun !== null) parts.push(`上限 ${formatMinutes(maxRun)}`);
if (scheduler !== null) parts.push(`调度 ${formatMinutes(scheduler)}`);
return parts.length > 0 ? parts.join(" / ") : "-";
}
function runTimingSourceText(run) {
const timing = run?.timing || {};
return safeDetailValue(run?.durationSource || timing.durationSource || timing.sourceOfTruth);
}
function timeWindowLabel(value) {
if (value === "1h") return "最近 1 小时";
if (value === "24h") return "最近 24 小时";
@@ -1248,6 +1289,9 @@ function detailSummaryRows(detail) {
{ key: "run", label: "运行", value: shortId(run.id || run.runId || detail.runId) },
{ key: "scenario", label: "场景", value: run.scenarioId || run.scenario_id || "-" },
{ key: "status", label: "状态", value: run.status || "-" },
{ key: "duration", label: "运行分钟", value: runDurationText(run) },
{ key: "timing", label: "周期/上限", value: runTimingConfigText(run) },
{ key: "durationSource", label: "计时来源", value: runTimingSourceText(run) },
{ key: "checks", label: "监测项类型", value: String(findingCount(run)) },
{ key: "alertSamples", label: "错误/告警样本", value: String(alertSampleCount(run)) },
{ key: "allSamples", label: "全部样本", value: String(findingSampleCount(run)) },
@@ -1282,3 +1326,12 @@ function number(value) {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : 0;
}
function optionalNumber(...values) {
for (const value of values) {
if (value === null || value === undefined || value === "") continue;
const parsed = Number(value);
if (Number.isFinite(parsed)) return parsed;
}
return null;
}