diff --git a/scripts/assets/web-probe-sentinel-dashboard/dashboard.css b/scripts/assets/web-probe-sentinel-dashboard/dashboard.css index 750be284..e4fd8bd1 100644 --- a/scripts/assets/web-probe-sentinel-dashboard/dashboard.css +++ b/scripts/assets/web-probe-sentinel-dashboard/dashboard.css @@ -457,6 +457,21 @@ select { background: #eef7ff; } +.runs-table tr.limit-row { + cursor: default; +} + +.runs-table tr.limit-row:hover { + background: transparent; +} + +.runs-table tr.limit-row td { + color: var(--muted); + font-size: 12px; + font-weight: 700; + text-align: center; +} + .runs-table td small { display: block; margin-top: 3px; @@ -969,6 +984,10 @@ select { border-bottom: 1px solid #edf0f4; } + .runs-table tr.limit-row { + padding: 12px; + } + .runs-table td { display: grid; grid-template-columns: 82px minmax(0, 1fr); @@ -985,6 +1004,16 @@ select { text-transform: uppercase; } + .runs-table tr.limit-row td { + display: block; + padding: 0; + text-align: left; + } + + .runs-table tr.limit-row td::before { + content: none; + } + .run-identity div { grid-template-columns: 54px minmax(0, 1fr); } diff --git a/scripts/assets/web-probe-sentinel-dashboard/dashboard.js b/scripts/assets/web-probe-sentinel-dashboard/dashboard.js index cf333f3b..54b7411d 100644 --- a/scripts/assets/web-probe-sentinel-dashboard/dashboard.js +++ b/scripts/assets/web-probe-sentinel-dashboard/dashboard.js @@ -75,6 +75,13 @@ const state = { findingFilters: readFindingFiltersFromLocation(), }; +const dashboardLimits = { + timeline: { mobile: 6, tablet: 9, desktop: 12 }, + runs: { mobile: 8, tablet: 12, desktop: 24 }, + findingsRed: { mobile: 4, tablet: 6, desktop: 8 }, + findingsOther: { mobile: 2, tablet: 3, desktop: 5 }, +}; + const dashboardApi = createDashboardApi(); const autoRefresh = createAutoRefresh({ storageKey: "hwlab.webProbeSentinel.dashboard.autoRefresh", @@ -114,6 +121,12 @@ refs.findingClearFilters.addEventListener("click", () => { document.addEventListener("visibilitychange", () => { if (!document.hidden && autoRefresh.enabled()) loadDashboard({ silent: true }); }); +window.addEventListener("resize", debounce(() => { + if (!state.overview) return; + renderTimeline(); + renderRuns(); + renderFindings(); +}, 150)); hydrateControls(); writeFiltersToControls(); @@ -323,7 +336,7 @@ function renderOverview() { } function renderTimeline() { - const visibleRuns = state.runs.slice(0, 12); + const visibleRuns = state.runs.slice(0, responsiveLimit(dashboardLimits.timeline)); refs.timelineCount.textContent = `最近 ${formatNumber(visibleRuns.length)} / ${formatNumber(state.runs.length)} 次`; if (state.runs.length === 0) { refs.timeline.innerHTML = '
暂无时间线
'; @@ -343,12 +356,15 @@ function renderTimeline() { } function renderRuns() { - refs.runsCount.textContent = `${formatNumber(state.runs.length)} 条可见`; + const visibleLimit = responsiveLimit(dashboardLimits.runs); + const visibleRuns = state.runs.slice(0, visibleLimit); + const hiddenCount = Math.max(0, state.runs.length - visibleRuns.length); + refs.runsCount.textContent = `${formatNumber(visibleRuns.length)} / ${formatNumber(state.runs.length)} 条可见`; if (state.runs.length === 0) { refs.runsBody.innerHTML = '暂无运行'; return; } - refs.runsBody.innerHTML = state.runs.map((run) => { + refs.runsBody.innerHTML = visibleRuns.map((run) => { const runId = run.runId || run.id || "-"; const selected = state.selectedRunId === runId ? " selected-row" : ""; return ` @@ -361,7 +377,7 @@ function renderRuns() { ${escapeHtml(String(run.findingCount ?? 0))}${run.maxSeverity ? ` ${escapeHtml(displaySeverity(run.maxSeverity))}` : ""} ${escapeHtml(run.updatedAt ? formatRelative(run.updatedAt) : "-")}${escapeHtml(run.maintenance ? "维护窗口" : "")} `; - }).join(""); + }).join("") + (hiddenCount > 0 ? `其余 ${formatNumber(hiddenCount)} 条运行通过筛选、搜索或 API drill-down 查看` : ""); for (const row of refs.runsBody.querySelectorAll("tr[data-run-id]")) { row.addEventListener("click", () => selectRun(row.dataset.runId)); } @@ -377,7 +393,7 @@ function renderFindings() { const groups = groupedFindingsBySeverity(state.findings); refs.findingsList.innerHTML = groups.map((group, groupIndex) => { const open = group.key === "red" || groupIndex === 0; - const visibleItems = group.items.slice(0, group.key === "red" ? 8 : 5); + const visibleItems = group.items.slice(0, findingVisibleLimit(group.key)); const hiddenCount = Math.max(0, group.items.length - visibleItems.length); return `
@@ -396,6 +412,17 @@ function renderFindings() { } } +function responsiveLimit(limits) { + if (window.matchMedia("(max-width: 560px)").matches) return limits.mobile; + if (window.matchMedia("(max-width: 980px)").matches) return limits.tablet; + return limits.desktop; +} + +function findingVisibleLimit(severity) { + const key = String(severity || "").toLowerCase(); + return responsiveLimit(key === "red" || key === "critical" || key === "error" ? dashboardLimits.findingsRed : dashboardLimits.findingsOther); +} + function renderFindingItem(item) { const code = item.code || item.findingId || "finding"; const latestRunId = item.latestRunId || "-";