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
+286 -15
View File
@@ -7201,12 +7201,16 @@ html[data-theme="dark"] .openapi-doc-btn:hover {
.terminal-panes {
position: relative;
height: 400px;
min-height: 400px;
overflow: hidden;
}
.terminal-pane {
display: none;
min-height: 400px;
height: 100%;
min-height: 0;
overflow: hidden;
}
.terminal-pane.active {
@@ -7214,38 +7218,46 @@ html[data-theme="dark"] .openapi-doc-btn:hover {
}
.terminal-container {
min-height: 400px;
padding: 8px;
height: 100%;
min-height: 0;
padding: 0;
box-sizing: border-box;
overflow: hidden;
}
.terminal-container .xterm {
padding: 0;
padding: 8px 12px 8px 8px;
width: 100%;
height: 100%;
box-sizing: border-box;
overflow: hidden;
background: #0d1117;
}
.terminal-container .xterm-viewport {
border-radius: 0;
/* 与 WebShell 终端一致:细窄、深色,避免系统默认浅色粗滚动条 */
background: #0d1117;
scrollbar-width: thin;
scrollbar-color: rgba(110, 118, 129, 0.5) transparent;
scrollbar-color: rgba(139, 148, 158, 0.7) rgba(22, 27, 34, 0.95);
}
.terminal-container .xterm-viewport::-webkit-scrollbar {
width: 6px;
width: 8px;
}
.terminal-container .xterm-viewport::-webkit-scrollbar-track {
background: transparent;
margin: 4px 0;
border-radius: 3px;
background: rgba(22, 27, 34, 0.95);
border-left: 1px solid rgba(48, 54, 61, 0.9);
}
.terminal-container .xterm-viewport::-webkit-scrollbar-thumb {
background: rgba(110, 118, 129, 0.4);
border-radius: 3px;
min-height: 44px;
background: rgba(139, 148, 158, 0.72);
border: 1px solid rgba(22, 27, 34, 0.95);
border-radius: 8px;
}
.terminal-container .xterm-viewport::-webkit-scrollbar-thumb:hover {
background: rgba(110, 118, 129, 0.65);
background: rgba(177, 186, 196, 0.82);
}
.terminal-container .xterm-viewport::-webkit-scrollbar-thumb:active {
background: rgba(139, 148, 158, 0.7);
background: rgba(201, 209, 217, 0.9);
}
.terminal-error {
@@ -20031,6 +20043,160 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible {
transform: translateX(2px);
}
.dashboard-section-actions {
display: inline-flex;
align-items: center;
gap: 10px;
margin-left: auto;
flex-shrink: 0;
}
.dashboard-severity-status-filter {
display: inline-flex;
align-items: center;
position: relative;
}
.dashboard-severity-status-filter-btn {
height: 30px;
min-width: 124px;
padding: 0 10px;
border-radius: 7px;
border: 1px solid rgba(148, 163, 184, 0.22);
background: rgba(248, 250, 252, 0.62);
color: var(--text-primary);
font-size: 0.78rem;
font-weight: 600;
cursor: pointer;
outline: none;
display: inline-flex;
align-items: center;
justify-content: space-between;
gap: 7px;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.46);
transition: border-color 0.2s, box-shadow 0.2s, background 0.2s, transform 0.15s;
}
.dashboard-severity-status-filter-btn:hover,
.dashboard-severity-status-filter.is-open .dashboard-severity-status-filter-btn {
border-color: rgba(0, 102, 255, 0.24);
background: rgba(255, 255, 255, 0.78);
box-shadow: 0 6px 14px rgba(15, 23, 42, 0.06), inset 0 1px 0 rgba(255, 255, 255, 0.65);
}
.dashboard-severity-status-filter-btn:focus-visible {
border-color: rgba(0, 102, 255, 0.42);
box-shadow: 0 0 0 2px rgba(0, 102, 255, 0.10);
}
.dashboard-severity-status-filter-dot {
width: 6px;
height: 6px;
border-radius: 999px;
background: #64748b;
box-shadow: 0 0 0 3px rgba(100, 116, 139, 0.12);
flex-shrink: 0;
}
.dashboard-severity-status-filter[data-value="open"] .dashboard-severity-status-filter-dot { background: #ef4444; box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.14); }
.dashboard-severity-status-filter[data-value="confirmed"] .dashboard-severity-status-filter-dot { background: #f59e0b; box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.16); }
.dashboard-severity-status-filter[data-value="fixed"] .dashboard-severity-status-filter-dot { background: #22c55e; box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.16); }
.dashboard-severity-status-filter[data-value="false_positive"] .dashboard-severity-status-filter-dot { background: #94a3b8; box-shadow: 0 0 0 3px rgba(148, 163, 184, 0.16); }
.dashboard-severity-status-filter[data-value="ignored"] .dashboard-severity-status-filter-dot { background: #64748b; box-shadow: 0 0 0 3px rgba(100, 116, 139, 0.16); }
.dashboard-severity-status-filter-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dashboard-severity-status-filter-caret {
width: 7px;
height: 7px;
border-right: 1.5px solid currentColor;
border-bottom: 1.5px solid currentColor;
transform: translateY(-2px) rotate(45deg);
opacity: 0.62;
flex-shrink: 0;
transition: transform 0.18s ease, opacity 0.18s ease;
}
.dashboard-severity-status-filter.is-open .dashboard-severity-status-filter-caret {
transform: translateY(2px) rotate(225deg);
opacity: 0.9;
}
.dashboard-severity-status-filter-menu {
position: absolute;
top: calc(100% + 6px);
left: 0;
z-index: 30;
width: 156px;
padding: 5px;
border-radius: 9px;
border: 1px solid rgba(148, 163, 184, 0.18);
background: rgba(255, 255, 255, 0.94);
box-shadow: 0 14px 30px rgba(15, 23, 42, 0.16), 0 1px 0 rgba(255, 255, 255, 0.65) inset;
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
.dashboard-severity-status-filter-option {
width: 100%;
height: 30px;
padding: 0 10px 0 28px;
border: 0;
border-radius: 7px;
background: transparent;
color: var(--text-primary);
font-size: 0.78rem;
font-weight: 600;
text-align: left;
cursor: pointer;
position: relative;
transition: background 0.15s, color 0.15s;
}
.dashboard-severity-status-filter-option::before {
content: '';
position: absolute;
left: 10px;
top: 50%;
width: 9px;
height: 5px;
border-left: 2px solid currentColor;
border-bottom: 2px solid currentColor;
transform: translateY(-65%) rotate(-45deg);
opacity: 0;
}
.dashboard-severity-status-filter-option:hover,
.dashboard-severity-status-filter-option.is-active {
background: rgba(0, 102, 255, 0.075);
color: #1d4ed8;
}
.dashboard-severity-status-filter-option.is-active::before {
opacity: 1;
}
@media (max-width: 640px) {
.dashboard-section-actions {
width: 100%;
justify-content: space-between;
}
.dashboard-section-header--severity {
flex-wrap: wrap;
}
.dashboard-severity-status-filter-btn {
min-width: 124px;
}
.dashboard-severity-status-filter-menu {
left: auto;
right: 0;
}
}
/* 最近漏洞 / 近期事实 Tab */
.dashboard-section-header--tabs {
align-items: center;
@@ -21540,10 +21706,16 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible {
.dashboard-severity-status-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 10px;
}
@media (max-width: 1180px) {
.dashboard-severity-status-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (max-width: 480px) {
.dashboard-severity-status-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -21591,6 +21763,7 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible {
.dashboard-severity-status-cell.s-confirmed .dashboard-severity-status-icon { background: rgba(245, 158, 11, 0.12); color: #d97706; }
.dashboard-severity-status-cell.s-fixed .dashboard-severity-status-icon { background: rgba(34, 197, 94, 0.12); color: #16a34a; }
.dashboard-severity-status-cell.s-fp .dashboard-severity-status-icon { background: rgba(148, 163, 184, 0.18); color: #64748b; }
.dashboard-severity-status-cell.s-ignored .dashboard-severity-status-icon { background: rgba(100, 116, 139, 0.16); color: #64748b; }
.dashboard-severity-status-text {
display: flex;
@@ -30833,6 +31006,37 @@ html[data-theme="dark"] .dashboard-feed-tabs {
border-color: #263244 !important;
}
html[data-theme="dark"] .dashboard-severity-status-filter-btn {
background: rgba(15, 23, 42, 0.46);
border-color: rgba(71, 85, 105, 0.58);
color: var(--text-primary);
box-shadow: inset 0 1px 0 rgba(255,255,255,0.035);
}
html[data-theme="dark"] .dashboard-severity-status-filter-btn:hover,
html[data-theme="dark"] .dashboard-severity-status-filter.is-open .dashboard-severity-status-filter-btn,
html[data-theme="dark"] .dashboard-severity-status-filter-btn:focus-visible {
background: rgba(23, 32, 51, 0.72);
border-color: rgba(96, 165, 250, 0.36);
box-shadow: 0 8px 18px rgba(0,0,0,0.18), inset 0 1px 0 rgba(255,255,255,0.05);
}
html[data-theme="dark"] .dashboard-severity-status-filter-menu {
background: rgba(15, 23, 42, 0.94);
border-color: rgba(71, 85, 105, 0.62);
box-shadow: 0 16px 34px rgba(0,0,0,0.32), inset 0 1px 0 rgba(255,255,255,0.04);
}
html[data-theme="dark"] .dashboard-severity-status-filter-option {
color: var(--text-primary);
}
html[data-theme="dark"] .dashboard-severity-status-filter-option:hover,
html[data-theme="dark"] .dashboard-severity-status-filter-option.is-active {
background: rgba(96, 165, 250, 0.10);
color: #bfdbfe;
}
html[data-theme="dark"] .dashboard-feed-tab {
color: var(--text-secondary);
}
@@ -31490,6 +31694,73 @@ html[data-theme="dark"] .projects-stat-chip--warn {
border-color: rgba(245, 158, 11, 0.34) !important;
}
html[data-theme="dark"] .projects-confidence--confirmed,
html[data-theme="dark"] .projects-fact-badge--ok {
background: rgba(34, 197, 94, 0.12) !important;
color: #6ee7a8 !important;
border-color: rgba(34, 197, 94, 0.22) !important;
}
html[data-theme="dark"] .projects-confidence--tentative,
html[data-theme="dark"] .projects-fact-badge--warn {
background: rgba(245, 158, 11, 0.12) !important;
color: #fbbf24 !important;
border-color: rgba(245, 158, 11, 0.22) !important;
}
html[data-theme="dark"] .projects-confidence--deprecated,
html[data-theme="dark"] .projects-fact-badge--na {
background: rgba(148, 163, 184, 0.11) !important;
color: #aebbd0 !important;
border-color: rgba(148, 163, 184, 0.20) !important;
}
html[data-theme="dark"] .projects-category--target,
html[data-theme="dark"] .project-fact-graph-node-category--target {
background: rgba(59, 130, 246, 0.18) !important;
color: #93c5fd !important;
border-color: rgba(59, 130, 246, 0.36) !important;
}
html[data-theme="dark"] .projects-category--auth,
html[data-theme="dark"] .projects-category--chain,
html[data-theme="dark"] .projects-category--custom,
html[data-theme="dark"] .project-fact-graph-node-category--chain,
html[data-theme="dark"] .project-fact-graph-node-category--vulnerability {
background: rgba(168, 85, 247, 0.16) !important;
color: #d8b4fe !important;
border-color: rgba(168, 85, 247, 0.34) !important;
}
html[data-theme="dark"] .projects-category--infra,
html[data-theme="dark"] .projects-category--note,
html[data-theme="dark"] .project-fact-graph-node-category--infra,
html[data-theme="dark"] .project-fact-graph-node-category--note,
html[data-theme="dark"] .project-fact-graph-node-category--missing {
background: rgba(148, 163, 184, 0.14) !important;
color: #cbd5e1 !important;
border-color: rgba(148, 163, 184, 0.30) !important;
}
html[data-theme="dark"] .projects-category--business,
html[data-theme="dark"] .project-fact-graph-node-category--auth,
html[data-theme="dark"] .project-fact-graph-node-category--business {
background: rgba(20, 184, 166, 0.16) !important;
color: #5eead4 !important;
border-color: rgba(20, 184, 166, 0.34) !important;
}
html[data-theme="dark"] .projects-category--finding,
html[data-theme="dark"] .projects-category--exploit,
html[data-theme="dark"] .projects-category--poc,
html[data-theme="dark"] .project-fact-graph-node-category--finding,
html[data-theme="dark"] .project-fact-graph-node-category--exploit,
html[data-theme="dark"] .project-fact-graph-node-category--poc {
background: rgba(249, 115, 22, 0.16) !important;
color: #fdba74 !important;
border-color: rgba(249, 115, 22, 0.36) !important;
}
html[data-theme="dark"] .hitl-tool-badge {
background: linear-gradient(135deg, #2563eb 0%, #4f46e5 100%) !important;
color: #ffffff !important;
+2
View File
@@ -219,6 +219,8 @@
"statusFixed": "Fixed",
"statusFalsePositive": "False positive",
"statusIgnored": "Ignored",
"statusFilter": "Status filter",
"allStatuses": "All statuses",
"fixRate": "Fix rate",
"dataStale": "Data may be stale — please refresh",
"recommendedActions": "Recommended Actions",
+2
View File
@@ -212,6 +212,8 @@
"statusFixed": "已修复",
"statusFalsePositive": "误报",
"statusIgnored": "已忽略",
"statusFilter": "状态筛选",
"allStatuses": "全部状态",
"fixRate": "修复率",
"dataStale": "数据可能已过期,请手动刷新",
"recommendedActions": "推荐操作",
+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() {