fix: compact sentinel dashboard mobile density

This commit is contained in:
Codex
2026-06-26 11:45:32 +00:00
parent d3fccec8b5
commit bab5e95f54
2 changed files with 61 additions and 5 deletions
@@ -457,6 +457,21 @@ select {
background: #eef7ff; 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 { .runs-table td small {
display: block; display: block;
margin-top: 3px; margin-top: 3px;
@@ -969,6 +984,10 @@ select {
border-bottom: 1px solid #edf0f4; border-bottom: 1px solid #edf0f4;
} }
.runs-table tr.limit-row {
padding: 12px;
}
.runs-table td { .runs-table td {
display: grid; display: grid;
grid-template-columns: 82px minmax(0, 1fr); grid-template-columns: 82px minmax(0, 1fr);
@@ -985,6 +1004,16 @@ select {
text-transform: uppercase; 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 { .run-identity div {
grid-template-columns: 54px minmax(0, 1fr); grid-template-columns: 54px minmax(0, 1fr);
} }
@@ -75,6 +75,13 @@ const state = {
findingFilters: readFindingFiltersFromLocation(), 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 dashboardApi = createDashboardApi();
const autoRefresh = createAutoRefresh({ const autoRefresh = createAutoRefresh({
storageKey: "hwlab.webProbeSentinel.dashboard.autoRefresh", storageKey: "hwlab.webProbeSentinel.dashboard.autoRefresh",
@@ -114,6 +121,12 @@ refs.findingClearFilters.addEventListener("click", () => {
document.addEventListener("visibilitychange", () => { document.addEventListener("visibilitychange", () => {
if (!document.hidden && autoRefresh.enabled()) loadDashboard({ silent: true }); if (!document.hidden && autoRefresh.enabled()) loadDashboard({ silent: true });
}); });
window.addEventListener("resize", debounce(() => {
if (!state.overview) return;
renderTimeline();
renderRuns();
renderFindings();
}, 150));
hydrateControls(); hydrateControls();
writeFiltersToControls(); writeFiltersToControls();
@@ -323,7 +336,7 @@ function renderOverview() {
} }
function renderTimeline() { 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)}`; refs.timelineCount.textContent = `最近 ${formatNumber(visibleRuns.length)} / ${formatNumber(state.runs.length)}`;
if (state.runs.length === 0) { if (state.runs.length === 0) {
refs.timeline.innerHTML = '<div class="empty-state">暂无时间线</div>'; refs.timeline.innerHTML = '<div class="empty-state">暂无时间线</div>';
@@ -343,12 +356,15 @@ function renderTimeline() {
} }
function renderRuns() { 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) { if (state.runs.length === 0) {
refs.runsBody.innerHTML = '<tr><td class="empty-state" colspan="5">暂无运行</td></tr>'; refs.runsBody.innerHTML = '<tr><td class="empty-state" colspan="5">暂无运行</td></tr>';
return; return;
} }
refs.runsBody.innerHTML = state.runs.map((run) => { refs.runsBody.innerHTML = visibleRuns.map((run) => {
const runId = run.runId || run.id || "-"; const runId = run.runId || run.id || "-";
const selected = state.selectedRunId === runId ? " selected-row" : ""; const selected = state.selectedRunId === runId ? " selected-row" : "";
return `<tr class="${selected}" data-run-id="${escapeAttr(runId)}"> return `<tr class="${selected}" data-run-id="${escapeAttr(runId)}">
@@ -361,7 +377,7 @@ function renderRuns() {
<td data-label="发现项">${escapeHtml(String(run.findingCount ?? 0))}${run.maxSeverity ? ` <span class="severity-pill ${severityClass(run.maxSeverity)}">${escapeHtml(displaySeverity(run.maxSeverity))}</span>` : ""}</td> <td data-label="发现项">${escapeHtml(String(run.findingCount ?? 0))}${run.maxSeverity ? ` <span class="severity-pill ${severityClass(run.maxSeverity)}">${escapeHtml(displaySeverity(run.maxSeverity))}</span>` : ""}</td>
<td data-label="更新时间"><span>${escapeHtml(run.updatedAt ? formatRelative(run.updatedAt) : "-")}</span><small>${escapeHtml(run.maintenance ? "维护窗口" : "")}</small></td> <td data-label="更新时间"><span>${escapeHtml(run.updatedAt ? formatRelative(run.updatedAt) : "-")}</span><small>${escapeHtml(run.maintenance ? "维护窗口" : "")}</small></td>
</tr>`; </tr>`;
}).join(""); }).join("") + (hiddenCount > 0 ? `<tr class="limit-row"><td colspan="5">其余 ${formatNumber(hiddenCount)} 条运行通过筛选、搜索或 API drill-down 查看</td></tr>` : "");
for (const row of refs.runsBody.querySelectorAll("tr[data-run-id]")) { for (const row of refs.runsBody.querySelectorAll("tr[data-run-id]")) {
row.addEventListener("click", () => selectRun(row.dataset.runId)); row.addEventListener("click", () => selectRun(row.dataset.runId));
} }
@@ -377,7 +393,7 @@ function renderFindings() {
const groups = groupedFindingsBySeverity(state.findings); const groups = groupedFindingsBySeverity(state.findings);
refs.findingsList.innerHTML = groups.map((group, groupIndex) => { refs.findingsList.innerHTML = groups.map((group, groupIndex) => {
const open = group.key === "red" || groupIndex === 0; 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); const hiddenCount = Math.max(0, group.items.length - visibleItems.length);
return `<details class="finding-group finding-group-${escapeAttr(group.key)}" ${open ? "open" : ""}> return `<details class="finding-group finding-group-${escapeAttr(group.key)}" ${open ? "open" : ""}>
<summary> <summary>
@@ -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) { function renderFindingItem(item) {
const code = item.code || item.findingId || "finding"; const code = item.code || item.findingId || "finding";
const latestRunId = item.latestRunId || "-"; const latestRunId = item.latestRunId || "-";