mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-07-10 14:28:43 +02:00
Add files via upload
This commit is contained in:
+286
-15
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -212,6 +212,8 @@
|
||||
"statusFixed": "已修复",
|
||||
"statusFalsePositive": "误报",
|
||||
"statusIgnored": "已忽略",
|
||||
"statusFilter": "状态筛选",
|
||||
"allStatuses": "全部状态",
|
||||
"fixRate": "修复率",
|
||||
"dataStale": "数据可能已过期,请手动刷新",
|
||||
"recommendedActions": "推荐操作",
|
||||
|
||||
+161
-19
@@ -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', // 接入概览 Tab:c2 | 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;
|
||||
});
|
||||
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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 + '"]');
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -468,9 +468,26 @@
|
||||
<div class="dashboard-grid">
|
||||
<div class="dashboard-main">
|
||||
<section class="dashboard-section dashboard-section-chart">
|
||||
<div class="dashboard-section-header">
|
||||
<div class="dashboard-section-header dashboard-section-header--severity">
|
||||
<h3 class="dashboard-section-title" data-i18n="dashboard.severityDistribution">漏洞严重程度分布</h3>
|
||||
<a class="dashboard-section-link" onclick="switchPage('vulnerabilities')" data-i18n="dashboard.viewAll">查看全部 →</a>
|
||||
<div class="dashboard-section-actions">
|
||||
<div class="dashboard-severity-status-filter" id="dashboard-severity-status-filter" data-value="">
|
||||
<button type="button" class="dashboard-severity-status-filter-btn" id="dashboard-severity-status-filter-btn" aria-haspopup="listbox" aria-expanded="false" aria-controls="dashboard-severity-status-filter-menu" data-i18n="dashboard.statusFilter" data-i18n-attr="title" data-i18n-skip-text="true" title="状态筛选" onclick="toggleDashboardSeverityStatusFilterMenu(event)">
|
||||
<span class="dashboard-severity-status-filter-dot" aria-hidden="true"></span>
|
||||
<span class="dashboard-severity-status-filter-text" id="dashboard-severity-status-filter-text" data-i18n="dashboard.allStatuses">全部状态</span>
|
||||
<span class="dashboard-severity-status-filter-caret" aria-hidden="true"></span>
|
||||
</button>
|
||||
<div class="dashboard-severity-status-filter-menu" id="dashboard-severity-status-filter-menu" role="listbox" aria-labelledby="dashboard-severity-status-filter-btn" hidden>
|
||||
<button type="button" class="dashboard-severity-status-filter-option" role="option" data-status="" onclick="selectDashboardSeverityStatusFilter('', event)"><span data-i18n="dashboard.allStatuses">全部状态</span></button>
|
||||
<button type="button" class="dashboard-severity-status-filter-option" role="option" data-status="open" onclick="selectDashboardSeverityStatusFilter('open', event)"><span data-i18n="dashboard.statusOpen">待处理</span></button>
|
||||
<button type="button" class="dashboard-severity-status-filter-option" role="option" data-status="confirmed" onclick="selectDashboardSeverityStatusFilter('confirmed', event)"><span data-i18n="dashboard.statusConfirmed">已确认</span></button>
|
||||
<button type="button" class="dashboard-severity-status-filter-option" role="option" data-status="fixed" onclick="selectDashboardSeverityStatusFilter('fixed', event)"><span data-i18n="dashboard.statusFixed">已修复</span></button>
|
||||
<button type="button" class="dashboard-severity-status-filter-option" role="option" data-status="ignored" onclick="selectDashboardSeverityStatusFilter('ignored', event)"><span data-i18n="dashboard.statusIgnored">已忽略</span></button>
|
||||
<button type="button" class="dashboard-severity-status-filter-option" role="option" data-status="false_positive" onclick="selectDashboardSeverityStatusFilter('false_positive', event)"><span data-i18n="dashboard.statusFalsePositive">误报</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<a class="dashboard-section-link" onclick="openDashboardSeverityVulnerabilities()" data-i18n="dashboard.viewAll">查看全部 →</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-severity-wrap">
|
||||
<!-- 风险概览卡:填充 donut 左侧留白;提供「结论性」洞察(风险等级/加权分/待处理计数/最新时间),
|
||||
@@ -558,7 +575,7 @@
|
||||
<!-- 处置状态 + 修复进度(利用 by_status 数据,避免下半部分留白) -->
|
||||
<div class="dashboard-severity-status">
|
||||
<div class="dashboard-severity-status-grid">
|
||||
<div class="dashboard-severity-status-cell s-open" role="button" tabindex="0" onclick="switchPage('vulnerabilities')" onkeydown="if(event.key==='Enter'||event.key===' ') { event.preventDefault(); switchPage('vulnerabilities'); }">
|
||||
<div class="dashboard-severity-status-cell s-open" role="button" tabindex="0" onclick="navigateToVulnerabilitiesWithFilter({status:'open'})" onkeydown="if(event.key==='Enter'||event.key===' ') { event.preventDefault(); navigateToVulnerabilitiesWithFilter({status:'open'}); }">
|
||||
<span class="dashboard-severity-status-icon" aria-hidden="true">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
|
||||
</span>
|
||||
@@ -567,7 +584,7 @@
|
||||
<span class="dashboard-severity-status-label" data-i18n="dashboard.statusOpen">待处理</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-severity-status-cell s-confirmed" role="button" tabindex="0" onclick="switchPage('vulnerabilities')" onkeydown="if(event.key==='Enter'||event.key===' ') { event.preventDefault(); switchPage('vulnerabilities'); }">
|
||||
<div class="dashboard-severity-status-cell s-confirmed" role="button" tabindex="0" onclick="navigateToVulnerabilitiesWithFilter({status:'confirmed'})" onkeydown="if(event.key==='Enter'||event.key===' ') { event.preventDefault(); navigateToVulnerabilitiesWithFilter({status:'confirmed'}); }">
|
||||
<span class="dashboard-severity-status-icon" aria-hidden="true">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
|
||||
</span>
|
||||
@@ -576,7 +593,7 @@
|
||||
<span class="dashboard-severity-status-label" data-i18n="dashboard.statusConfirmed">已确认</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-severity-status-cell s-fixed" role="button" tabindex="0" onclick="switchPage('vulnerabilities')" onkeydown="if(event.key==='Enter'||event.key===' ') { event.preventDefault(); switchPage('vulnerabilities'); }">
|
||||
<div class="dashboard-severity-status-cell s-fixed" role="button" tabindex="0" onclick="navigateToVulnerabilitiesWithFilter({status:'fixed'})" onkeydown="if(event.key==='Enter'||event.key===' ') { event.preventDefault(); navigateToVulnerabilitiesWithFilter({status:'fixed'}); }">
|
||||
<span class="dashboard-severity-status-icon" aria-hidden="true">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><polyline points="9 12 11 14 15 10"/></svg>
|
||||
</span>
|
||||
@@ -585,7 +602,16 @@
|
||||
<span class="dashboard-severity-status-label" data-i18n="dashboard.statusFixed">已修复</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-severity-status-cell s-fp" role="button" tabindex="0" onclick="switchPage('vulnerabilities')" onkeydown="if(event.key==='Enter'||event.key===' ') { event.preventDefault(); switchPage('vulnerabilities'); }">
|
||||
<div class="dashboard-severity-status-cell s-ignored" role="button" tabindex="0" onclick="navigateToVulnerabilitiesWithFilter({status:'ignored'})" onkeydown="if(event.key==='Enter'||event.key===' ') { event.preventDefault(); navigateToVulnerabilitiesWithFilter({status:'ignored'}); }">
|
||||
<span class="dashboard-severity-status-icon" aria-hidden="true">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4l16 16"/><path d="M10.58 10.58A3 3 0 0 0 12 15a3 3 0 0 0 2.12-.88"/><path d="M9.88 4.24A9.77 9.77 0 0 1 12 4c7 0 10 8 10 8a18.5 18.5 0 0 1-2.5 3.66"/><path d="M6.61 6.61C3.98 8.38 2 12 2 12s3 8 10 8a9.86 9.86 0 0 0 5.39-1.61"/></svg>
|
||||
</span>
|
||||
<div class="dashboard-severity-status-text">
|
||||
<span class="dashboard-severity-status-value" id="dashboard-status-ignored">0</span>
|
||||
<span class="dashboard-severity-status-label" data-i18n="dashboard.statusIgnored">已忽略</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-severity-status-cell s-fp" role="button" tabindex="0" onclick="navigateToVulnerabilitiesWithFilter({status:'false_positive'})" onkeydown="if(event.key==='Enter'||event.key===' ') { event.preventDefault(); navigateToVulnerabilitiesWithFilter({status:'false_positive'}); }">
|
||||
<span class="dashboard-severity-status-icon" aria-hidden="true">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="4.93" y1="4.93" x2="19.07" y2="19.07"/></svg>
|
||||
</span>
|
||||
@@ -1978,8 +2004,8 @@
|
||||
<option value="open" data-i18n="vulnerabilityPage.statusOpen">待处理</option>
|
||||
<option value="confirmed" data-i18n="vulnerabilityPage.statusConfirmed">已确认</option>
|
||||
<option value="fixed" data-i18n="vulnerabilityPage.statusFixed">已修复</option>
|
||||
<option value="false_positive" data-i18n="vulnerabilityPage.statusFalsePositive">误报</option>
|
||||
<option value="ignored" data-i18n="vulnerabilityPage.statusIgnored">已忽略</option>
|
||||
<option value="false_positive" data-i18n="vulnerabilityPage.statusFalsePositive">误报</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
@@ -2178,8 +2204,8 @@
|
||||
<option value="open" data-i18n="vulnerabilityPage.statusOpen">待处理</option>
|
||||
<option value="confirmed" data-i18n="vulnerabilityPage.statusConfirmed">已确认</option>
|
||||
<option value="fixed" data-i18n="vulnerabilityPage.statusFixed">已修复</option>
|
||||
<option value="false_positive" data-i18n="vulnerabilityPage.statusFalsePositive">误报</option>
|
||||
<option value="ignored" data-i18n="vulnerabilityPage.statusIgnored">已忽略</option>
|
||||
<option value="false_positive" data-i18n="vulnerabilityPage.statusFalsePositive">误报</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="vulnerability-filter-actions">
|
||||
@@ -4983,8 +5009,8 @@
|
||||
<option value="open" data-i18n="vulnerabilityModal.statusOpen">待处理</option>
|
||||
<option value="confirmed" data-i18n="vulnerabilityModal.statusConfirmed">已确认</option>
|
||||
<option value="fixed" data-i18n="vulnerabilityModal.statusFixed">已修复</option>
|
||||
<option value="false_positive" data-i18n="vulnerabilityModal.statusFalsePositive">误报</option>
|
||||
<option value="ignored" data-i18n="vulnerabilityModal.statusIgnored">已忽略</option>
|
||||
<option value="false_positive" data-i18n="vulnerabilityModal.statusFalsePositive">误报</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
||||
Reference in New Issue
Block a user