diff --git a/scripts/assets/web-probe-sentinel-dashboard/dashboard.css b/scripts/assets/web-probe-sentinel-dashboard/dashboard.css
index 573aba95..8dbcd969 100644
--- a/scripts/assets/web-probe-sentinel-dashboard/dashboard.css
+++ b/scripts/assets/web-probe-sentinel-dashboard/dashboard.css
@@ -263,20 +263,20 @@ select {
}
.overview-checks {
- display: flex;
- flex-wrap: wrap;
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 8px;
margin: 0 0 14px;
}
.check-chip {
- display: inline-flex;
+ display: flex;
align-items: center;
min-height: 30px;
max-width: 100%;
padding: 5px 10px;
border: 1px solid #d8e0ea;
- border-radius: 999px;
+ border-radius: 8px;
background: #ffffff;
color: #475467;
font-size: 12px;
@@ -301,11 +301,12 @@ select {
}
.run-timeline {
- display: flex;
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(88px, 1fr));
gap: 8px;
min-height: 68px;
padding: 14px 16px;
- overflow-x: auto;
+ overflow: visible;
}
.timeline-node {
@@ -313,8 +314,9 @@ select {
grid-template-rows: 18px auto;
justify-items: center;
gap: 4px;
- min-width: 76px;
- max-width: 120px;
+ width: 100%;
+ min-width: 0;
+ max-width: none;
border: 0;
background: transparent;
color: #475467;
@@ -342,6 +344,7 @@ select {
.dashboard-grid {
display: grid;
grid-template-columns: minmax(0, 1.55fr) minmax(320px, 0.9fr);
+ align-items: start;
gap: 14px;
}
@@ -462,6 +465,33 @@ select {
overflow-wrap: anywhere;
}
+.run-identity {
+ display: grid;
+ gap: 5px;
+ min-width: 0;
+}
+
+.run-identity div {
+ display: grid;
+ grid-template-columns: 62px minmax(0, 1fr);
+ align-items: baseline;
+ gap: 8px;
+}
+
+.run-identity span {
+ color: var(--muted);
+ font-size: 10px;
+ font-weight: 800;
+ text-transform: uppercase;
+}
+
+.run-identity code {
+ min-width: 0;
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
+ font-size: 12px;
+ overflow-wrap: anywhere;
+}
+
.mono {
font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
font-size: 12px;
@@ -470,10 +500,49 @@ select {
.finding-list {
display: grid;
- gap: 10px;
+ gap: 12px;
padding: 12px;
}
+.finding-group {
+ min-width: 0;
+ border: 1px solid #e3e8ef;
+ border-radius: 8px;
+ background: #ffffff;
+}
+
+.finding-group summary {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: 4px 10px;
+ align-items: center;
+ min-height: 46px;
+ padding: 10px 12px;
+ cursor: pointer;
+}
+
+.finding-group summary span {
+ font-size: 13px;
+ font-weight: 800;
+}
+
+.finding-group summary strong {
+ font-size: 20px;
+ line-height: 1;
+}
+
+.finding-group summary small {
+ grid-column: 1 / -1;
+ color: var(--muted);
+ font-size: 12px;
+}
+
+.finding-group-list {
+ display: grid;
+ gap: 10px;
+ padding: 0 10px 10px;
+}
+
.finding-aggregation {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -519,6 +588,13 @@ select {
overflow-wrap: anywhere;
}
+.finding-summary {
+ color: #1f2937;
+ font-size: 13px;
+ line-height: 1.45;
+ overflow-wrap: anywhere;
+}
+
.finding-actions {
display: flex;
flex-wrap: wrap;
@@ -827,6 +903,10 @@ select {
.metric-card {
min-height: 96px;
}
+
+ .run-timeline {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ }
}
@media (max-width: 560px) {
@@ -862,4 +942,12 @@ select {
.findings-filter {
grid-template-columns: 1fr;
}
+
+ .run-timeline {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .finding-group:not([open]) summary {
+ min-height: 42px;
+ }
}
diff --git a/scripts/assets/web-probe-sentinel-dashboard/dashboard.js b/scripts/assets/web-probe-sentinel-dashboard/dashboard.js
index 9a427625..4941cf60 100644
--- a/scripts/assets/web-probe-sentinel-dashboard/dashboard.js
+++ b/scripts/assets/web-probe-sentinel-dashboard/dashboard.js
@@ -292,12 +292,13 @@ function renderDashboard() {
function renderOverview() {
const overview = state.overview || {};
const status = overview.status || (overview.ok ? "healthy" : "degraded");
- refs.statusPill.textContent = displayStatus(status);
- refs.statusPill.className = `status-pill ${statusClass(status)}`;
- refs.overall.textContent = displayStatus(status);
- refs.origin.textContent = overview.publicOrigin || root.dataset.publicOrigin || "-";
-
const latest = overview.latestRun || null;
+ const latestStatus = latest?.status || status;
+ refs.statusPill.textContent = displayStatus(latestStatus);
+ refs.statusPill.className = `status-pill ${statusClass(latestStatus)}`;
+ refs.overall.textContent = displayStatus(latestStatus);
+ refs.origin.textContent = `${overview.publicOrigin || root.dataset.publicOrigin || "-"} · 历史 ${displayStatus(status)}`;
+
refs.latestRun.textContent = latest?.runId || latest?.id || "-";
refs.latestAge.textContent = latest?.updatedAt ? `${formatRelative(latest.updatedAt)} 更新` : "-";
@@ -322,12 +323,13 @@ function renderOverview() {
}
function renderTimeline() {
- refs.timelineCount.textContent = `最近 ${formatNumber(state.runs.length)} 次`;
+ const visibleRuns = state.runs.slice(0, 12);
+ refs.timelineCount.textContent = `最近 ${formatNumber(visibleRuns.length)} / ${formatNumber(state.runs.length)} 次`;
if (state.runs.length === 0) {
refs.timeline.innerHTML = '
暂无时间线
';
return;
}
- refs.timeline.innerHTML = state.runs.slice(0, 20).map((run) => {
+ refs.timeline.innerHTML = visibleRuns.map((run) => {
const runId = run.runId || run.id || "-";
const title = `${displayStatus(run.status)} · ${run.findingCount ?? 0} 个发现 · ${run.updatedAt ? formatRelative(run.updatedAt) : "-"}`;
return `