fix: compact sentinel dashboard mobile density
This commit is contained in:
@@ -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 || "-";
|
||||||
|
|||||||
Reference in New Issue
Block a user