Add files via upload

This commit is contained in:
公明
2026-07-09 16:47:00 +08:00
committed by GitHub
parent e092f6c590
commit 878cf158b0
8 changed files with 536 additions and 54 deletions
+161 -19
View File
@@ -13,6 +13,8 @@
var DASHBOARD_POLL_INTERVAL_MS = 60 * 1000;
var DASHBOARD_STALE_THRESHOLD_MS = 5 * 60 * 1000;
var DASHBOARD_STALE_CHECK_INTERVAL_MS = 30 * 1000;
var DASHBOARD_SEVERITY_STATUS_FILTER_STORAGE_KEY = 'cyberstrike.dashboard.severityStatusFilter';
var DASHBOARD_SEVERITY_STATUS_FILTER_VALUES = ['', 'open', 'confirmed', 'fixed', 'ignored', 'false_positive'];
var dashboardState = {
currentController: null, // 当前正在进行的 fetch 的 AbortController
@@ -23,6 +25,7 @@ var dashboardState = {
lastResources: null, // 上一轮关键资源快照,用于判断是否首次有数据 / 智能 CTA
recentFeedTab: 'vulns', // 最近漏洞 / 近期事实 Tab
accessTab: 'c2', // 接入概览 Tabc2 | webshell
severityStatusFilter: null, // 严重程度分布当前状态筛选:'' | open | confirmed | fixed | false_positive | ignored
lastProjectSummary: null, // 最近一次项目仪表盘摘要(供 Tab 切换时重绘)
};
@@ -99,6 +102,7 @@ async function refreshDashboard() {
};
try {
var selectedSeverityStatus = getDashboardSeverityStatusFilter();
// /api/vulnerabilities/stats 只给出 by_severity 与 by_status 两个独立维度,
// 无法得到「严重 × 待处理」的交叉计数。这里按四档各拉一次(limit=1,仅取 total),
// 用真实的「待处理 × 各严重度」数量驱动告警条 / KPI 副标 / 风险概览卡的加权分,
@@ -113,7 +117,7 @@ async function refreshDashboard() {
hitlPendingRes, notificationsRes, externalMcpStatsRes,
webshellRes,
c2ListenersRes, c2SessionsRes, c2TasksRes,
projectSummaryRes
projectSummaryRes, severityFilteredStatsRes
] = await Promise.all([
fetchJson('/api/agent-loop/tasks'),
fetchJson('/api/vulnerabilities/stats'),
@@ -144,7 +148,8 @@ async function refreshDashboard() {
fetchJson('/api/c2/listeners'),
fetchJson('/api/c2/sessions?limit=500'),
fetchJson('/api/c2/tasks?page=1&page_size=1'),
fetchJson('/api/projects/dashboard-summary?fact_limit=10')
fetchJson('/api/projects/dashboard-summary?fact_limit=10'),
selectedSeverityStatus ? fetchJson('/api/vulnerabilities/stats?status=' + encodeURIComponent(selectedSeverityStatus)) : Promise.resolve(null)
]);
// 如果在 await 期间 controller 已被 abort,说明又有新刷新启动了,丢弃本次结果
@@ -191,6 +196,9 @@ async function refreshDashboard() {
if (vulnTotalEl) vulnTotalEl.textContent = String(vulnRes.total);
const bySeverity = vulnRes.by_severity || {};
const total = vulnRes.total || 0;
const severityDisplayRes = selectedSeverityStatus && severityFilteredStatsRes ? severityFilteredStatsRes : vulnRes;
const displayBySeverity = severityDisplayRes.by_severity || {};
const displayTotal = typeof severityDisplayRes.total === 'number' ? severityDisplayRes.total : total;
criticalCount = bySeverity.critical || 0;
highCount = bySeverity.high || 0;
mediumCount = bySeverity.medium || 0;
@@ -200,17 +208,7 @@ async function refreshDashboard() {
openHighCount = pickOpenCount(openHighRes, highCount);
openMediumCount = pickOpenCount(openMediumRes, mediumCount);
openLowCount = pickOpenCount(openLowRes, lowCount);
severityIds.forEach(sev => {
const count = bySeverity[sev] || 0;
const el = document.getElementById('dashboard-severity-' + sev);
if (el) el.textContent = String(count);
const pctEl = document.getElementById('dashboard-severity-' + sev + '-pct');
if (pctEl) {
const pct = total > 0 ? Math.round((count / total) * 100) : 0;
pctEl.textContent = pct + '%';
}
});
renderSeverityDonut(bySeverity, total);
renderDashboardSeveritySummary(displayBySeverity, displayTotal, severityIds);
renderVulnStatusPanel(vulnRes.by_status || {}, total);
renderSeverityInsights(
{ critical: openCriticalCount, high: openHighCount, medium: openMediumCount, low: openLowCount },
@@ -1483,7 +1481,7 @@ function esc(s) {
}
// 漏洞处置状态 + 修复进度面板
// byStatus: { open, confirmed, fixed, false_positive }(任一字段缺失视作 0)
// byStatus: { open, confirmed, fixed, false_positive, ignored }(任一字段缺失视作 0
// total: 漏洞总数(来自 stats.total
function renderVulnStatusPanel(byStatus, total) {
var get = function (k) {
@@ -1494,11 +1492,13 @@ function renderVulnStatusPanel(byStatus, total) {
var confirmed = get('confirmed');
var fixed = get('fixed');
var fp = get('false_positive');
var ignored = get('ignored');
setEl('dashboard-status-open', formatNumber(open));
setEl('dashboard-status-confirmed', formatNumber(confirmed));
setEl('dashboard-status-fixed', formatNumber(fixed));
setEl('dashboard-status-fp', formatNumber(fp));
setEl('dashboard-status-ignored', formatNumber(ignored));
// 修复率:fixed / total(不计入 false_positive 时也可,按 total 维持一致)
var t = Number(total || 0);
@@ -1705,6 +1705,149 @@ function navigateToVulnerabilitiesWithFilter(opts) {
}
window.navigateToVulnerabilitiesWithFilter = navigateToVulnerabilitiesWithFilter;
function normalizeDashboardSeverityStatusFilter(status) {
status = String(status || '');
return DASHBOARD_SEVERITY_STATUS_FILTER_VALUES.indexOf(status) >= 0 ? status : '';
}
function readDashboardSeverityStatusFilterFromStorage() {
try {
return normalizeDashboardSeverityStatusFilter(window.localStorage.getItem(DASHBOARD_SEVERITY_STATUS_FILTER_STORAGE_KEY));
} catch (_) {
return '';
}
}
function writeDashboardSeverityStatusFilterToStorage(status) {
try {
if (status) {
window.localStorage.setItem(DASHBOARD_SEVERITY_STATUS_FILTER_STORAGE_KEY, status);
} else {
window.localStorage.removeItem(DASHBOARD_SEVERITY_STATUS_FILTER_STORAGE_KEY);
}
} catch (_) {
// localStorage 可能被浏览器隐私设置禁用,筛选本身仍可在当前页面生效。
}
}
function dashboardSeverityStatusFilterLabel(status) {
status = normalizeDashboardSeverityStatusFilter(status);
if (!status) return dt('dashboard.allStatuses', null, '全部状态');
return statusShortLabel(status);
}
function getDashboardSeverityStatusFilter() {
if (dashboardState.severityStatusFilter === null) {
dashboardState.severityStatusFilter = readDashboardSeverityStatusFilterFromStorage();
}
syncDashboardSeverityStatusFilterUI();
return dashboardState.severityStatusFilter || '';
}
function updateDashboardSeverityStatusFilter(status) {
dashboardState.severityStatusFilter = normalizeDashboardSeverityStatusFilter(status);
writeDashboardSeverityStatusFilterToStorage(dashboardState.severityStatusFilter);
syncDashboardSeverityStatusFilterUI();
refreshDashboard();
}
window.updateDashboardSeverityStatusFilter = updateDashboardSeverityStatusFilter;
function selectDashboardSeverityStatusFilter(status, ev) {
if (ev) {
ev.preventDefault();
ev.stopPropagation();
}
closeDashboardSeverityStatusFilterMenu();
updateDashboardSeverityStatusFilter(status);
}
window.selectDashboardSeverityStatusFilter = selectDashboardSeverityStatusFilter;
function syncDashboardSeverityStatusFilterUI() {
var status = dashboardState.severityStatusFilter;
if (status === null) status = readDashboardSeverityStatusFilterFromStorage();
status = normalizeDashboardSeverityStatusFilter(status);
var root = document.getElementById('dashboard-severity-status-filter');
var textEl = document.getElementById('dashboard-severity-status-filter-text');
if (root) root.setAttribute('data-value', status);
if (textEl) textEl.textContent = dashboardSeverityStatusFilterLabel(status);
document.querySelectorAll('.dashboard-severity-status-filter-option[data-status]').forEach(function (item) {
var active = item.getAttribute('data-status') === status;
item.classList.toggle('is-active', active);
item.setAttribute('aria-selected', active ? 'true' : 'false');
});
}
function toggleDashboardSeverityStatusFilterMenu(ev) {
if (ev) {
ev.preventDefault();
ev.stopPropagation();
}
syncDashboardSeverityStatusFilterUI();
var root = document.getElementById('dashboard-severity-status-filter');
var btn = document.getElementById('dashboard-severity-status-filter-btn');
var menu = document.getElementById('dashboard-severity-status-filter-menu');
if (!root || !btn || !menu) return;
var willOpen = menu.hasAttribute('hidden');
menu.toggleAttribute('hidden', !willOpen);
root.classList.toggle('is-open', willOpen);
btn.setAttribute('aria-expanded', willOpen ? 'true' : 'false');
ensureDashboardSeverityStatusFilterOutsideListener();
}
window.toggleDashboardSeverityStatusFilterMenu = toggleDashboardSeverityStatusFilterMenu;
function closeDashboardSeverityStatusFilterMenu() {
var root = document.getElementById('dashboard-severity-status-filter');
var btn = document.getElementById('dashboard-severity-status-filter-btn');
var menu = document.getElementById('dashboard-severity-status-filter-menu');
if (menu) menu.setAttribute('hidden', '');
if (root) root.classList.remove('is-open');
if (btn) btn.setAttribute('aria-expanded', 'false');
}
function ensureDashboardSeverityStatusFilterOutsideListener() {
if (dashboardState.severityStatusFilterOutsideBound) return;
dashboardState.severityStatusFilterOutsideBound = true;
document.addEventListener('click', function (ev) {
var root = document.getElementById('dashboard-severity-status-filter');
if (root && root.contains(ev.target)) return;
closeDashboardSeverityStatusFilterMenu();
});
document.addEventListener('keydown', function (ev) {
if (ev.key === 'Escape') closeDashboardSeverityStatusFilterMenu();
});
}
function openDashboardSeverityVulnerabilities() {
navigateToVulnerabilitiesWithFilter({ status: getDashboardSeverityStatusFilter() });
}
window.openDashboardSeverityVulnerabilities = openDashboardSeverityVulnerabilities;
function navigateToSeverityWithDashboardStatus(severity) {
navigateToVulnerabilitiesWithFilter({
severity: severity,
status: getDashboardSeverityStatusFilter()
});
}
function renderDashboardSeveritySummary(bySeverity, total, severityIds) {
severityIds = Array.isArray(severityIds) && severityIds.length ? severityIds : ['critical', 'high', 'medium', 'low', 'info'];
total = Number(total || 0);
bySeverity = bySeverity && typeof bySeverity === 'object' ? bySeverity : {};
severityIds.forEach(function (sev) {
var count = Number(bySeverity[sev] || 0) || 0;
var el = document.getElementById('dashboard-severity-' + sev);
if (el) el.textContent = String(count);
var pctEl = document.getElementById('dashboard-severity-' + sev + '-pct');
if (pctEl) {
var pct = total > 0 ? Math.round((count / total) * 100) : 0;
pctEl.textContent = pct + '%';
}
});
renderSeverityDonut(bySeverity, total);
}
// 漏洞严重程度分布:半环形(donut)渲染
// 几何参数固定,便于配合 viewBox 0 0 560 320 的 SVG 容器
// 段间分隔由 gapRad 几何间隙完成,不使用描边,避免浅色/暗色下白边或黑边过重
@@ -2201,7 +2344,7 @@ function severityDonutClick(ev) {
var id = target.getAttribute('data-severity');
if (!id) return;
ev.preventDefault();
navigateToVulnerabilitiesWithFilter({ severity: id });
navigateToSeverityWithDashboardStatus(id);
}
function severityDonutKeydown(ev) {
@@ -2210,7 +2353,7 @@ function severityDonutKeydown(ev) {
if (!target) return;
ev.preventDefault();
var id = target.getAttribute('data-severity');
if (id) navigateToVulnerabilitiesWithFilter({ severity: id });
if (id) navigateToSeverityWithDashboardStatus(id);
}
function severityLegendPointerOver(ev) {
@@ -2236,7 +2379,7 @@ function severityLegendClick(ev) {
var id = item.getAttribute('data-severity');
if (!id) return;
ev.preventDefault();
navigateToVulnerabilitiesWithFilter({ severity: id });
navigateToSeverityWithDashboardStatus(id);
}
function severityLegendKeydown(ev) {
@@ -2245,7 +2388,7 @@ function severityLegendKeydown(ev) {
if (!item) return;
ev.preventDefault();
var id = item.getAttribute('data-severity');
if (id) navigateToVulnerabilitiesWithFilter({ severity: id });
if (id) navigateToSeverityWithDashboardStatus(id);
}
// SVG 半环(背景轨迹)路径
@@ -2325,4 +2468,3 @@ document.addEventListener('click', function (ev) {
var banner = document.getElementById('dashboard-alert-banner');
if (banner) banner.hidden = true;
});
+6 -6
View File
@@ -1,6 +1,6 @@
/**
* 项目事实图渲染(Cytoscape + ELK),供项目管理页使用。
* 节点采用 SVG 卡片背景(图标 + 多行文字),避免 Cytoscape 原生 label 定位问题。
* 节点采用 SVG 卡片背景(左上角图标 + 多行文字),避免 Cytoscape 原生 label 定位问题。
*/
(function (global) {
'use strict';
@@ -25,11 +25,11 @@
const CARD_PAD = 14;
const CARD_TEXT_PAD_RIGHT = 12;
const CARD_ICON = 36;
const CARD_ICON_GAP = 12;
const CARD_ICON = 24;
const CARD_ICON_GAP = 8;
const CARD_TEXT_X = CARD_PAD + CARD_ICON + CARD_ICON_GAP;
const CARD_MIN_W = 300;
const CARD_TARGET_W = 360;
const CARD_MIN_W = 340;
const CARD_TARGET_W = 380;
const CARD_MIN_H = 88;
const CARD_MAX_H = 176;
const CARD_HEADER_FS = 11;
@@ -320,7 +320,7 @@
const isTentative = conf === 'tentative';
const isDeprecated = conf === 'deprecated';
const iconX = CARD_PAD;
const iconY = (height - CARD_ICON) / 2;
const iconY = CARD_PAD + 1;
const headerY = CARD_PAD + CARD_HEADER_FS;
const keyY = CARD_PAD + headerLines.length * CARD_HEADER_LH + CARD_SECTION_GAP + CARD_KEY_FS;
const summaryY =
+43 -4
View File
@@ -72,6 +72,17 @@
else t.term.writeln('');
}
function writeTermData(tab, data) {
if (!tab || !tab.term || data === undefined || data === null) return;
try {
tab.term.write(data, function () {
try { tab.term.scrollToBottom(); } catch (e) {}
});
} catch (e) {
tab.term.write(data);
}
}
function writeOutput(tab, text, isError) {
var t = tab || getCurrent();
if (!t || !t.term || !text) return;
@@ -136,18 +147,18 @@
// 处理二进制消息和文本消息
if (ev.data instanceof ArrayBuffer) {
var decoder = new TextDecoder('utf-8');
tab.term.write(decoder.decode(ev.data));
writeTermData(tab, decoder.decode(ev.data));
} else if (ev.data instanceof Blob) {
// Blob 类型,需要异步读取
var reader = new FileReader();
reader.onload = function () {
var decoder = new TextDecoder('utf-8');
tab.term.write(decoder.decode(reader.result));
writeTermData(tab, decoder.decode(reader.result));
};
reader.readAsArrayBuffer(ev.data);
} else {
// 字符串类型
tab.term.write(ev.data);
writeTermData(tab, ev.data);
}
};
@@ -183,6 +194,9 @@
fontSize: 13,
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
lineHeight: 1.2,
smoothScrollDuration: 0,
scrollSensitivity: 1,
fastScrollSensitivity: 5,
scrollback: 1000,
theme: {
background: '#0d1117',
@@ -258,6 +272,19 @@
tab.term = term;
tab.fitAddon = fitAddon;
if (typeof ResizeObserver !== 'undefined' && fitAddon) {
var resizeTimer;
tab.resizeObserver = new ResizeObserver(function () {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function () {
try {
fitAddon.fit();
term.scrollToBottom();
} catch (e) {}
}, 50);
});
tab.resizeObserver.observe(container);
}
// 立即建立 WebSocket,让后端 PTY/Shell 马上启动并输出提示符;
// 若等到首次按键才 connect,用户会感觉必须先按回车才能输入(实为连接尚未建立)。
ensureTerminalWS(tab);
@@ -278,9 +305,19 @@
});
var t = getCurrent();
if (t && t.term) {
try {
if (t.fitAddon) t.fitAddon.fit();
t.term.scrollToBottom();
} catch (e) {}
if (prevId !== id) {
requestAnimationFrame(function () {
if (currentTabId === id && t.term) t.term.focus();
if (currentTabId === id && t.term) {
try {
if (t.fitAddon) t.fitAddon.fit();
t.term.scrollToBottom();
} catch (e) {}
t.term.focus();
}
});
} else {
t.term.focus();
@@ -356,9 +393,11 @@
var switchToIndex = deletingCurrent ? (idx > 0 ? idx - 1 : 0) : -1;
var tab = terminals[idx];
if (tab.resizeObserver && tab.resizeObserver.disconnect) tab.resizeObserver.disconnect();
if (tab.term && tab.term.dispose) tab.term.dispose();
tab.term = null;
tab.fitAddon = null;
tab.resizeObserver = null;
terminals.splice(idx, 1);
var tabDiv = document.querySelector('.terminal-tab[data-tab-id="' + id + '"]');
+1 -1
View File
@@ -39,7 +39,7 @@ function vulnStatusLabel(code) {
return m[code] ? vulnT(m[code]) : code;
}
const VULN_STATUS_CODES = ['open', 'confirmed', 'fixed', 'false_positive', 'ignored'];
const VULN_STATUS_CODES = ['open', 'confirmed', 'fixed', 'ignored', 'false_positive'];
const VULNERABILITY_REMOVE_ANIM_MS = 200;
function getVulnerabilityScrollContainer() {