mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-05-15 12:58:01 +02:00
Add files via upload
This commit is contained in:
+112
-10
@@ -14768,8 +14768,70 @@ header {
|
||||
stroke: #ffffff;
|
||||
stroke-width: 4;
|
||||
stroke-linejoin: round;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
transform-origin: 240px 215px;
|
||||
transition: opacity 0.22s ease, filter 0.22s ease, transform 0.28s cubic-bezier(0.34, 1.4, 0.64, 1);
|
||||
}
|
||||
|
||||
.dashboard-severity-donut.donut-ready .donut-segment {
|
||||
animation: donut-segment-in 0.55s cubic-bezier(0.22, 1, 0.36, 1) backwards;
|
||||
}
|
||||
|
||||
.dashboard-severity-donut.donut-ready .donut-segment.seg-critical { animation-delay: 0.02s; }
|
||||
.dashboard-severity-donut.donut-ready .donut-segment.seg-high { animation-delay: 0.06s; }
|
||||
.dashboard-severity-donut.donut-ready .donut-segment.seg-medium { animation-delay: 0.10s; }
|
||||
.dashboard-severity-donut.donut-ready .donut-segment.seg-low { animation-delay: 0.14s; }
|
||||
.dashboard-severity-donut.donut-ready .donut-segment.seg-info { animation-delay: 0.18s; }
|
||||
|
||||
@keyframes donut-segment-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.92);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-severity-donut.is-highlighting .donut-segment.is-dimmed,
|
||||
.dashboard-severity-donut.is-highlighting .donut-label-text.is-dimmed,
|
||||
.dashboard-severity-donut.is-highlighting .donut-leader.is-dimmed {
|
||||
opacity: 0.32;
|
||||
}
|
||||
|
||||
.dashboard-severity-donut .donut-segment.is-active {
|
||||
filter: drop-shadow(0 3px 10px rgba(15, 23, 42, 0.18));
|
||||
transform: scale(1.045);
|
||||
stroke-width: 5;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dashboard-severity-donut .donut-segment:focus-visible {
|
||||
outline: 2px solid rgba(0, 102, 255, 0.55);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.dashboard-severity-donut .donut-leader {
|
||||
stroke: rgba(148, 163, 184, 0.55);
|
||||
stroke-width: 1;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.2s ease, stroke 0.2s ease;
|
||||
}
|
||||
|
||||
.dashboard-severity-donut .donut-leader.is-active {
|
||||
stroke: rgba(100, 116, 139, 0.85);
|
||||
stroke-width: 1.5;
|
||||
}
|
||||
|
||||
.dashboard-severity-donut .donut-label-text {
|
||||
pointer-events: none;
|
||||
transition: opacity 0.2s ease;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.dashboard-severity-donut .donut-label-text.is-active {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.dashboard-severity-donut .donut-segment.is-empty {
|
||||
@@ -14799,12 +14861,7 @@ header {
|
||||
.dashboard-severity-donut .donut-label-text.label-low { fill: #14b8a6; }
|
||||
.dashboard-severity-donut .donut-label-text.label-info { fill: #3b82f6; }
|
||||
|
||||
/* 半环形配色:保持原有浅色基调(红→橙→黄→青→蓝) */
|
||||
.dashboard-severity-donut .donut-segment.seg-critical { fill: #f87171; }
|
||||
.dashboard-severity-donut .donut-segment.seg-high { fill: #fb923c; }
|
||||
.dashboard-severity-donut .donut-segment.seg-medium { fill: #facc15; }
|
||||
.dashboard-severity-donut .donut-segment.seg-low { fill: #2dd4bf; }
|
||||
.dashboard-severity-donut .donut-segment.seg-info { fill: #60a5fa; }
|
||||
/* 半环形配色由 SVG linearGradient(#donut-grad-*)提供 */
|
||||
|
||||
.dashboard-severity-center {
|
||||
position: absolute;
|
||||
@@ -14816,6 +14873,17 @@ header {
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
width: 60%;
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
|
||||
.dashboard-severity-center.is-hovering {
|
||||
transform: translate(-50%, -52%) scale(1.04);
|
||||
}
|
||||
|
||||
.dashboard-severity-center-label.is-severity {
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.dashboard-severity-center-value {
|
||||
@@ -14856,12 +14924,46 @@ header {
|
||||
padding: 10px 4px;
|
||||
font-size: 0.9375rem;
|
||||
border-bottom: 1px solid transparent;
|
||||
transition: background 0.2s, border-color 0.2s;
|
||||
transition: background 0.2s, border-color 0.2s, box-shadow 0.2s, opacity 0.2s;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dashboard-severity-legend-item:hover {
|
||||
background: rgba(0, 0, 0, 0.025);
|
||||
.dashboard-severity-legend-item:hover,
|
||||
.dashboard-severity-legend-item.is-active {
|
||||
background: rgba(0, 102, 255, 0.06);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.dashboard-severity-legend-item.is-active {
|
||||
box-shadow: inset 3px 0 0 var(--accent-color, #0066ff);
|
||||
}
|
||||
|
||||
.dashboard-severity-legend-item.is-zero {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.dashboard-severity-legend-item:focus-visible {
|
||||
outline: 2px solid rgba(0, 102, 255, 0.45);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.dashboard-severity-donut-tooltip {
|
||||
display: none;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 10000;
|
||||
max-width: 280px;
|
||||
padding: 8px 12px;
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.45;
|
||||
color: #fff;
|
||||
background: rgba(15, 23, 42, 0.94);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.22);
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dashboard-severity-legend-dot {
|
||||
|
||||
@@ -147,6 +147,7 @@
|
||||
"active": "Active",
|
||||
"highFreq": "High frequency",
|
||||
"noCallData": "No call data",
|
||||
"severityClickHint": "Click to view",
|
||||
"lastUpdated": "Last updated",
|
||||
"viewAll": "View all →",
|
||||
"recentVulns": "Recent vulnerabilities",
|
||||
|
||||
@@ -147,6 +147,7 @@
|
||||
"active": "活跃",
|
||||
"highFreq": "高频",
|
||||
"noCallData": "暂无调用数据",
|
||||
"severityClickHint": "点击查看",
|
||||
"lastUpdated": "上次更新",
|
||||
"viewAll": "查看全部 →",
|
||||
"recentVulns": "最近漏洞",
|
||||
|
||||
+286
-9
@@ -1390,6 +1390,17 @@ function dashboardBarTooltipOnOut(ev) {
|
||||
if (dashboardBarTooltipEl) dashboardBarTooltipEl.style.display = 'none';
|
||||
}
|
||||
|
||||
// 仪表盘 → 漏洞管理:带严重程度/状态筛选跳转
|
||||
function navigateToVulnerabilitiesWithFilter(opts) {
|
||||
opts = opts || {};
|
||||
var params = new URLSearchParams();
|
||||
if (opts.severity) params.set('severity', opts.severity);
|
||||
if (opts.status) params.set('status', opts.status);
|
||||
var qs = params.toString();
|
||||
window.location.hash = qs ? 'vulnerabilities?' + qs : 'vulnerabilities';
|
||||
}
|
||||
window.navigateToVulnerabilitiesWithFilter = navigateToVulnerabilitiesWithFilter;
|
||||
|
||||
// 漏洞严重程度分布:半环形(donut)渲染
|
||||
// 几何参数固定,便于配合 viewBox 0 0 560 320 的 SVG 容器
|
||||
// 段间分隔由 CSS 的白色 stroke 完成,不再使用 gapRad
|
||||
@@ -1402,9 +1413,27 @@ var SEVERITY_DONUT_CFG = {
|
||||
rOuter: 165,
|
||||
rInner: 115, // 环厚 = 50(介于原 90 和上一版 35 之间,自然且有质感)
|
||||
labelOffset: 14,
|
||||
gapRad: 0
|
||||
gapRad: 0.012
|
||||
};
|
||||
|
||||
var SEVERITY_DONUT_GRADIENTS = {
|
||||
critical: ['#fca5a5', '#ef4444'],
|
||||
high: ['#fdba74', '#f97316'],
|
||||
medium: ['#fde047', '#eab308'],
|
||||
low: ['#5eead4', '#14b8a6'],
|
||||
info: ['#93c5fd', '#3b82f6']
|
||||
};
|
||||
|
||||
var severityDonutState = {
|
||||
bySeverity: {},
|
||||
total: 0,
|
||||
hoverId: null,
|
||||
bound: false
|
||||
};
|
||||
|
||||
var severityDonutTooltipEl = null;
|
||||
var severityDonutTooltipTimer = null;
|
||||
|
||||
var SEVERITY_DEFAULT_LABELS = {
|
||||
critical: '严重',
|
||||
high: '高危',
|
||||
@@ -1422,13 +1451,35 @@ function severityLabel(id) {
|
||||
return SEVERITY_DEFAULT_LABELS[id] || id;
|
||||
}
|
||||
|
||||
function ensureSeverityDonutGradients() {
|
||||
var defsEl = document.getElementById('dashboard-severity-donut-defs');
|
||||
if (!defsEl || defsEl.hasChildNodes()) return;
|
||||
var html = '';
|
||||
Object.keys(SEVERITY_DONUT_GRADIENTS).forEach(function (id) {
|
||||
var stops = SEVERITY_DONUT_GRADIENTS[id];
|
||||
html += '<linearGradient id="donut-grad-' + id + '" x1="0%" y1="0%" x2="100%" y2="100%">';
|
||||
html += '<stop offset="0%" stop-color="' + stops[0] + '"/>';
|
||||
html += '<stop offset="100%" stop-color="' + stops[1] + '"/>';
|
||||
html += '</linearGradient>';
|
||||
});
|
||||
defsEl.innerHTML = html;
|
||||
}
|
||||
|
||||
function renderSeverityDonut(bySeverity, total) {
|
||||
var svgEl = document.getElementById('dashboard-severity-donut');
|
||||
var trackEl = document.getElementById('dashboard-severity-donut-track');
|
||||
var leadersEl = document.getElementById('dashboard-severity-donut-leaders');
|
||||
var segmentsEl = document.getElementById('dashboard-severity-donut-segments');
|
||||
var labelsEl = document.getElementById('dashboard-severity-donut-labels');
|
||||
if (!trackEl || !segmentsEl || !labelsEl) return;
|
||||
|
||||
severityDonutState.bySeverity = bySeverity && typeof bySeverity === 'object' ? bySeverity : {};
|
||||
severityDonutState.total = total || 0;
|
||||
severityDonutState.hoverId = null;
|
||||
resetSeverityDonutCenter();
|
||||
|
||||
var cfg = SEVERITY_DONUT_CFG;
|
||||
ensureSeverityDonutGradients();
|
||||
|
||||
// 背景轨迹(完整半环)只渲染一次
|
||||
if (!trackEl.hasChildNodes()) {
|
||||
@@ -1441,9 +1492,12 @@ function renderSeverityDonut(bySeverity, total) {
|
||||
});
|
||||
var visible = severities.filter(function (s) { return s.value > 0; });
|
||||
|
||||
if (svgEl) svgEl.classList.remove('is-highlighting');
|
||||
if (!total || total <= 0 || visible.length === 0) {
|
||||
segmentsEl.innerHTML = '';
|
||||
labelsEl.innerHTML = '';
|
||||
if (leadersEl) leadersEl.innerHTML = '';
|
||||
clearSeverityDonutLegendHighlight();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1457,6 +1511,7 @@ function renderSeverityDonut(bySeverity, total) {
|
||||
|
||||
var segmentsHtml = '';
|
||||
var labelsHtml = '';
|
||||
var leadersHtml = '';
|
||||
var cumRad = 0;
|
||||
|
||||
visible.forEach(function (seg, i) {
|
||||
@@ -1466,17 +1521,19 @@ function renderSeverityDonut(bySeverity, total) {
|
||||
var angleEnd = angleStart - segRad;
|
||||
|
||||
var path = arcSegmentPath(cfg.cx, cfg.cy, cfg.rOuter, cfg.rInner, angleStart, angleEnd);
|
||||
segmentsHtml += '<path class="donut-segment seg-' + seg.id + '" d="' + path + '"/>';
|
||||
var pctOfTotal = (seg.value / total) * 100;
|
||||
var pctRounded = Math.round(pctOfTotal);
|
||||
var name = esc(severityLabel(seg.id));
|
||||
var ariaLabel = name + ' ' + seg.value + ' (' + pctRounded + '%)';
|
||||
segmentsHtml += '<path class="donut-segment seg-' + seg.id + '" data-severity="' + seg.id + '" data-count="' + seg.value + '" data-pct="' + pctRounded + '" fill="url(#donut-grad-' + seg.id + ')" d="' + path + '" tabindex="0" role="button" aria-label="' + ariaLabel + '"/>';
|
||||
|
||||
// 仅当占比 >= 5% 时显示外置标签,避免小段标签互相重叠
|
||||
var pctOfTotal = (seg.value / total) * 100;
|
||||
if (pctOfTotal >= 5) {
|
||||
var midAngle = (angleStart + angleEnd) / 2;
|
||||
var labelR = cfg.rOuter + cfg.labelOffset;
|
||||
var labelR = cfg.rOuter + cfg.labelOffset + 6;
|
||||
var sinMid = Math.sin(midAngle);
|
||||
var cosMid = Math.cos(midAngle);
|
||||
var lx = cfg.cx + labelR * cosMid;
|
||||
// 顶部区域标签整体向上抬一些,避免与外弧贴住;侧边标签则不调整
|
||||
var topLift = sinMid > 0.4 ? Math.round((sinMid - 0.3) * 10) : 0;
|
||||
var ly = cfg.cy - labelR * sinMid - topLift;
|
||||
|
||||
@@ -1484,11 +1541,15 @@ function renderSeverityDonut(bySeverity, total) {
|
||||
if (cosMid < -0.15) anchor = 'end';
|
||||
else if (cosMid > 0.15) anchor = 'start';
|
||||
|
||||
var pctText = Math.round(pctOfTotal) + '%';
|
||||
var name = esc(severityLabel(seg.id));
|
||||
var pctText = pctRounded + '%';
|
||||
var arcR = cfg.rOuter + 4;
|
||||
var lineX1 = cfg.cx + arcR * cosMid;
|
||||
var lineY1 = cfg.cy - arcR * sinMid;
|
||||
var lineX2 = cfg.cx + (cfg.rOuter + cfg.labelOffset - 2) * cosMid;
|
||||
var lineY2 = cfg.cy - (cfg.rOuter + cfg.labelOffset - 2) * sinMid;
|
||||
leadersHtml += '<line class="donut-leader label-' + seg.id + '" data-severity="' + seg.id + '" x1="' + lineX1.toFixed(1) + '" y1="' + lineY1.toFixed(1) + '" x2="' + lineX2.toFixed(1) + '" y2="' + lineY2.toFixed(1) + '"/>';
|
||||
|
||||
// 两行:第一行 "数量 (百分比)"(弧色),第二行 "严重度名称"(同色但稍小)
|
||||
labelsHtml += '<text class="donut-label-text label-' + seg.id + '" text-anchor="' + anchor + '" x="' + lx.toFixed(1) + '" y="' + ly.toFixed(1) + '">';
|
||||
labelsHtml += '<text class="donut-label-text label-' + seg.id + '" data-severity="' + seg.id + '" text-anchor="' + anchor + '" x="' + lx.toFixed(1) + '" y="' + ly.toFixed(1) + '">';
|
||||
labelsHtml += '<tspan x="' + lx.toFixed(1) + '" dy="0">' + seg.value + ' <tspan class="donut-label-pct">(' + pctText + ')</tspan></tspan>';
|
||||
labelsHtml += '<tspan class="donut-label-name" x="' + lx.toFixed(1) + '" dy="14">' + name + '</tspan>';
|
||||
labelsHtml += '</text>';
|
||||
@@ -1498,8 +1559,224 @@ function renderSeverityDonut(bySeverity, total) {
|
||||
if (i < visibleCount - 1) cumRad += cfg.gapRad;
|
||||
});
|
||||
|
||||
if (leadersEl) leadersEl.innerHTML = leadersHtml;
|
||||
segmentsEl.innerHTML = segmentsHtml;
|
||||
labelsEl.innerHTML = labelsHtml;
|
||||
if (svgEl) svgEl.classList.add('donut-ready');
|
||||
attachSeverityDonutInteractivity();
|
||||
}
|
||||
|
||||
function resetSeverityDonutCenter() {
|
||||
var totalEl = document.getElementById('dashboard-severity-total');
|
||||
var labelEl = document.getElementById('dashboard-severity-center-label');
|
||||
var centerEl = document.getElementById('dashboard-severity-center');
|
||||
if (totalEl) totalEl.textContent = String(severityDonutState.total || 0);
|
||||
if (labelEl) {
|
||||
labelEl.textContent = (typeof window.t === 'function' ? window.t('dashboard.totalVulns') : '总漏洞数');
|
||||
labelEl.classList.remove('is-severity');
|
||||
}
|
||||
if (centerEl) centerEl.classList.remove('is-hovering');
|
||||
}
|
||||
|
||||
function setSeverityDonutHover(severityId) {
|
||||
var svgEl = document.getElementById('dashboard-severity-donut');
|
||||
var centerEl = document.getElementById('dashboard-severity-center');
|
||||
var totalEl = document.getElementById('dashboard-severity-total');
|
||||
var labelEl = document.getElementById('dashboard-severity-center-label');
|
||||
if (!severityId) {
|
||||
severityDonutState.hoverId = null;
|
||||
if (svgEl) svgEl.classList.remove('is-highlighting');
|
||||
clearSeverityDonutLegendHighlight();
|
||||
resetSeverityDonutCenter();
|
||||
return;
|
||||
}
|
||||
var count = (severityDonutState.bySeverity && severityDonutState.bySeverity[severityId]) || 0;
|
||||
severityDonutState.hoverId = severityId;
|
||||
if (svgEl) svgEl.classList.add('is-highlighting');
|
||||
highlightSeverityDonutParts(severityId);
|
||||
highlightSeverityLegendItem(severityId);
|
||||
if (totalEl) totalEl.textContent = String(count);
|
||||
if (labelEl) {
|
||||
labelEl.textContent = severityLabel(severityId);
|
||||
labelEl.classList.add('is-severity');
|
||||
}
|
||||
if (centerEl) centerEl.classList.add('is-hovering');
|
||||
}
|
||||
|
||||
function highlightSeverityDonutParts(severityId) {
|
||||
var svgEl = document.getElementById('dashboard-severity-donut');
|
||||
if (!svgEl) return;
|
||||
svgEl.querySelectorAll('[data-severity]').forEach(function (el) {
|
||||
var match = el.getAttribute('data-severity') === severityId;
|
||||
el.classList.toggle('is-active', match);
|
||||
el.classList.toggle('is-dimmed', !match);
|
||||
});
|
||||
}
|
||||
|
||||
function highlightSeverityLegendItem(severityId) {
|
||||
var legend = document.getElementById('dashboard-vuln-bars');
|
||||
if (!legend) return;
|
||||
legend.querySelectorAll('.dashboard-severity-legend-item').forEach(function (item) {
|
||||
var match = item.getAttribute('data-severity') === severityId;
|
||||
item.classList.toggle('is-active', match);
|
||||
});
|
||||
}
|
||||
|
||||
function clearSeverityDonutLegendHighlight() {
|
||||
var legend = document.getElementById('dashboard-vuln-bars');
|
||||
if (legend) {
|
||||
legend.querySelectorAll('.dashboard-severity-legend-item.is-active').forEach(function (el) {
|
||||
el.classList.remove('is-active');
|
||||
});
|
||||
}
|
||||
var svgEl = document.getElementById('dashboard-severity-donut');
|
||||
if (svgEl) {
|
||||
svgEl.querySelectorAll('.is-active, .is-dimmed').forEach(function (el) {
|
||||
el.classList.remove('is-active', 'is-dimmed');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function severityDonutTooltipText(severityId) {
|
||||
var count = (severityDonutState.bySeverity && severityDonutState.bySeverity[severityId]) || 0;
|
||||
var pct = severityDonutState.total > 0 ? Math.round((count / severityDonutState.total) * 100) : 0;
|
||||
var hint = (typeof window.t === 'function' ? window.t('dashboard.severityClickHint') : '点击查看');
|
||||
return severityLabel(severityId) + ' · ' + count + ' (' + pct + '%) — ' + hint;
|
||||
}
|
||||
|
||||
function showSeverityDonutTooltip(ev, severityId) {
|
||||
if (!severityDonutTooltipEl) {
|
||||
severityDonutTooltipEl = document.createElement('div');
|
||||
severityDonutTooltipEl.className = 'dashboard-severity-donut-tooltip';
|
||||
severityDonutTooltipEl.setAttribute('role', 'tooltip');
|
||||
document.body.appendChild(severityDonutTooltipEl);
|
||||
}
|
||||
clearTimeout(severityDonutTooltipTimer);
|
||||
severityDonutTooltipTimer = setTimeout(function () {
|
||||
severityDonutTooltipEl.textContent = severityDonutTooltipText(severityId);
|
||||
severityDonutTooltipEl.style.display = 'block';
|
||||
requestAnimationFrame(function () {
|
||||
var x = ev.clientX;
|
||||
var y = ev.clientY;
|
||||
var ttRect = severityDonutTooltipEl.getBoundingClientRect();
|
||||
var left = x - ttRect.width / 2;
|
||||
var top = y - ttRect.height - 12;
|
||||
if (top < 8) top = y + 16;
|
||||
var pad = 8;
|
||||
if (left < pad) left = pad;
|
||||
if (left + ttRect.width > window.innerWidth - pad) left = window.innerWidth - ttRect.width - pad;
|
||||
severityDonutTooltipEl.style.left = left + 'px';
|
||||
severityDonutTooltipEl.style.top = top + 'px';
|
||||
});
|
||||
}, 120);
|
||||
}
|
||||
|
||||
function hideSeverityDonutTooltip() {
|
||||
clearTimeout(severityDonutTooltipTimer);
|
||||
severityDonutTooltipTimer = null;
|
||||
if (severityDonutTooltipEl) severityDonutTooltipEl.style.display = 'none';
|
||||
}
|
||||
|
||||
function attachSeverityDonutInteractivity() {
|
||||
var svgEl = document.getElementById('dashboard-severity-donut');
|
||||
var legend = document.getElementById('dashboard-vuln-bars');
|
||||
if (!svgEl) return;
|
||||
|
||||
if (!severityDonutState.bound) {
|
||||
severityDonutState.bound = true;
|
||||
svgEl.addEventListener('mouseover', severityDonutPointerOver);
|
||||
svgEl.addEventListener('mouseout', severityDonutPointerOut);
|
||||
svgEl.addEventListener('click', severityDonutClick);
|
||||
svgEl.addEventListener('keydown', severityDonutKeydown);
|
||||
if (legend) {
|
||||
legend.addEventListener('mouseover', severityLegendPointerOver);
|
||||
legend.addEventListener('mouseout', severityLegendPointerOut);
|
||||
legend.addEventListener('click', severityLegendClick);
|
||||
legend.addEventListener('keydown', severityLegendKeydown);
|
||||
}
|
||||
}
|
||||
|
||||
legend && legend.querySelectorAll('.dashboard-severity-legend-item').forEach(function (item) {
|
||||
if (!item.getAttribute('data-severity')) return;
|
||||
var sev = item.getAttribute('data-severity');
|
||||
var count = (severityDonutState.bySeverity && severityDonutState.bySeverity[sev]) || 0;
|
||||
item.classList.toggle('is-zero', count === 0);
|
||||
item.setAttribute('aria-label', severityDonutTooltipText(sev));
|
||||
});
|
||||
}
|
||||
|
||||
function severityDonutTarget(el) {
|
||||
return el && el.closest && el.closest('[data-severity]');
|
||||
}
|
||||
|
||||
function severityDonutPointerOver(ev) {
|
||||
var target = severityDonutTarget(ev.target);
|
||||
if (!target || !target.classList.contains('donut-segment')) return;
|
||||
var id = target.getAttribute('data-severity');
|
||||
if (!id) return;
|
||||
setSeverityDonutHover(id);
|
||||
showSeverityDonutTooltip(ev, id);
|
||||
}
|
||||
|
||||
function severityDonutPointerOut(ev) {
|
||||
var from = severityDonutTarget(ev.target);
|
||||
var to = ev.relatedTarget && severityDonutTarget(ev.relatedTarget);
|
||||
if (from && from === to) return;
|
||||
setSeverityDonutHover(null);
|
||||
hideSeverityDonutTooltip();
|
||||
}
|
||||
|
||||
function severityDonutClick(ev) {
|
||||
var target = severityDonutTarget(ev.target);
|
||||
if (!target || !target.classList.contains('donut-segment')) return;
|
||||
var id = target.getAttribute('data-severity');
|
||||
if (!id) return;
|
||||
ev.preventDefault();
|
||||
navigateToVulnerabilitiesWithFilter({ severity: id });
|
||||
}
|
||||
|
||||
function severityDonutKeydown(ev) {
|
||||
if (ev.key !== 'Enter' && ev.key !== ' ') return;
|
||||
var target = severityDonutTarget(ev.target);
|
||||
if (!target || !target.classList.contains('donut-segment')) return;
|
||||
ev.preventDefault();
|
||||
var id = target.getAttribute('data-severity');
|
||||
if (id) navigateToVulnerabilitiesWithFilter({ severity: id });
|
||||
}
|
||||
|
||||
function severityLegendPointerOver(ev) {
|
||||
var item = ev.target && ev.target.closest && ev.target.closest('.dashboard-severity-legend-item[data-severity]');
|
||||
if (!item) return;
|
||||
var id = item.getAttribute('data-severity');
|
||||
if (!id) return;
|
||||
setSeverityDonutHover(id);
|
||||
showSeverityDonutTooltip(ev, id);
|
||||
}
|
||||
|
||||
function severityLegendPointerOut(ev) {
|
||||
var item = ev.target && ev.target.closest && ev.target.closest('.dashboard-severity-legend-item[data-severity]');
|
||||
var related = ev.relatedTarget && ev.relatedTarget.closest && ev.relatedTarget.closest('.dashboard-severity-legend-item[data-severity]');
|
||||
if (item && item === related) return;
|
||||
setSeverityDonutHover(null);
|
||||
hideSeverityDonutTooltip();
|
||||
}
|
||||
|
||||
function severityLegendClick(ev) {
|
||||
var item = ev.target && ev.target.closest && ev.target.closest('.dashboard-severity-legend-item[data-severity]');
|
||||
if (!item) return;
|
||||
var id = item.getAttribute('data-severity');
|
||||
if (!id) return;
|
||||
ev.preventDefault();
|
||||
navigateToVulnerabilitiesWithFilter({ severity: id });
|
||||
}
|
||||
|
||||
function severityLegendKeydown(ev) {
|
||||
if (ev.key !== 'Enter' && ev.key !== ' ') return;
|
||||
var item = ev.target && ev.target.closest && ev.target.closest('.dashboard-severity-legend-item[data-severity]');
|
||||
if (!item) return;
|
||||
ev.preventDefault();
|
||||
var id = item.getAttribute('data-severity');
|
||||
if (id) navigateToVulnerabilitiesWithFilter({ severity: id });
|
||||
}
|
||||
|
||||
// SVG 半环(背景轨迹)路径
|
||||
|
||||
@@ -72,19 +72,27 @@ function syncVulnerabilityFiltersFromLocationHash() {
|
||||
const vid = (params.get('id') || '').trim();
|
||||
const cid = (params.get('conversation_id') || '').trim();
|
||||
const tid = (params.get('task_id') || '').trim();
|
||||
if (!vid && !cid && !tid) {
|
||||
const sev = (params.get('severity') || '').trim();
|
||||
const st = (params.get('status') || '').trim();
|
||||
if (!vid && !cid && !tid && !sev && !st) {
|
||||
return;
|
||||
}
|
||||
|
||||
vulnerabilityFilters.id = '';
|
||||
vulnerabilityFilters.conversation_id = '';
|
||||
vulnerabilityFilters.task_id = '';
|
||||
vulnerabilityFilters.severity = '';
|
||||
vulnerabilityFilters.status = '';
|
||||
const idEl = document.getElementById('vulnerability-id-filter');
|
||||
const convEl = document.getElementById('vulnerability-conversation-filter');
|
||||
const taskEl = document.getElementById('vulnerability-task-filter');
|
||||
const sevEl = document.getElementById('vulnerability-severity-filter');
|
||||
const stEl = document.getElementById('vulnerability-status-filter');
|
||||
if (idEl) idEl.value = '';
|
||||
if (convEl) convEl.value = '';
|
||||
if (taskEl) taskEl.value = '';
|
||||
if (sevEl) sevEl.value = '';
|
||||
if (stEl) stEl.value = '';
|
||||
|
||||
if (vid) {
|
||||
vulnerabilityFilters.id = vid;
|
||||
@@ -98,6 +106,14 @@ function syncVulnerabilityFiltersFromLocationHash() {
|
||||
vulnerabilityFilters.task_id = tid;
|
||||
if (taskEl) taskEl.value = tid;
|
||||
}
|
||||
if (sev) {
|
||||
vulnerabilityFilters.severity = sev;
|
||||
if (sevEl) sevEl.value = sev;
|
||||
}
|
||||
if (st) {
|
||||
vulnerabilityFilters.status = st;
|
||||
if (stEl) stEl.value = st;
|
||||
}
|
||||
vulnerabilityPagination.currentPage = 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -449,42 +449,45 @@
|
||||
</div>
|
||||
</aside>
|
||||
<div class="dashboard-severity-chart">
|
||||
<svg class="dashboard-severity-donut" id="dashboard-severity-donut" viewBox="0 0 480 260" preserveAspectRatio="xMidYMid meet" aria-hidden="true">
|
||||
<svg class="dashboard-severity-donut" id="dashboard-severity-donut" viewBox="0 0 480 260" preserveAspectRatio="xMidYMid meet" role="img" aria-labelledby="dashboard-severity-donut-title">
|
||||
<title id="dashboard-severity-donut-title" data-i18n="dashboard.severityDistribution">漏洞严重程度分布</title>
|
||||
<defs id="dashboard-severity-donut-defs"></defs>
|
||||
<g id="dashboard-severity-donut-track"></g>
|
||||
<g id="dashboard-severity-donut-leaders"></g>
|
||||
<g id="dashboard-severity-donut-segments"></g>
|
||||
<g id="dashboard-severity-donut-labels"></g>
|
||||
</svg>
|
||||
<div class="dashboard-severity-center">
|
||||
<div class="dashboard-severity-center" id="dashboard-severity-center">
|
||||
<div class="dashboard-severity-center-value" id="dashboard-severity-total">0</div>
|
||||
<div class="dashboard-severity-center-label" data-i18n="dashboard.totalVulns">总漏洞数</div>
|
||||
<div class="dashboard-severity-center-label" id="dashboard-severity-center-label" data-i18n="dashboard.totalVulns">总漏洞数</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-severity-legend" id="dashboard-vuln-bars">
|
||||
<div class="dashboard-severity-legend-item">
|
||||
<div class="dashboard-severity-legend-item" data-severity="critical" role="button" tabindex="0">
|
||||
<span class="dashboard-severity-legend-dot critical"></span>
|
||||
<span class="dashboard-severity-legend-label" data-i18n="dashboard.severityCritical">严重</span>
|
||||
<span class="dashboard-severity-legend-value" id="dashboard-severity-critical">0</span>
|
||||
<span class="dashboard-severity-legend-pct" id="dashboard-severity-critical-pct">0%</span>
|
||||
</div>
|
||||
<div class="dashboard-severity-legend-item">
|
||||
<div class="dashboard-severity-legend-item" data-severity="high" role="button" tabindex="0">
|
||||
<span class="dashboard-severity-legend-dot high"></span>
|
||||
<span class="dashboard-severity-legend-label" data-i18n="dashboard.severityHigh">高危</span>
|
||||
<span class="dashboard-severity-legend-value" id="dashboard-severity-high">0</span>
|
||||
<span class="dashboard-severity-legend-pct" id="dashboard-severity-high-pct">0%</span>
|
||||
</div>
|
||||
<div class="dashboard-severity-legend-item">
|
||||
<div class="dashboard-severity-legend-item" data-severity="medium" role="button" tabindex="0">
|
||||
<span class="dashboard-severity-legend-dot medium"></span>
|
||||
<span class="dashboard-severity-legend-label" data-i18n="dashboard.severityMedium">中危</span>
|
||||
<span class="dashboard-severity-legend-value" id="dashboard-severity-medium">0</span>
|
||||
<span class="dashboard-severity-legend-pct" id="dashboard-severity-medium-pct">0%</span>
|
||||
</div>
|
||||
<div class="dashboard-severity-legend-item">
|
||||
<div class="dashboard-severity-legend-item" data-severity="low" role="button" tabindex="0">
|
||||
<span class="dashboard-severity-legend-dot low"></span>
|
||||
<span class="dashboard-severity-legend-label" data-i18n="dashboard.severityLow">低危</span>
|
||||
<span class="dashboard-severity-legend-value" id="dashboard-severity-low">0</span>
|
||||
<span class="dashboard-severity-legend-pct" id="dashboard-severity-low-pct">0%</span>
|
||||
</div>
|
||||
<div class="dashboard-severity-legend-item">
|
||||
<div class="dashboard-severity-legend-item" data-severity="info" role="button" tabindex="0">
|
||||
<span class="dashboard-severity-legend-dot info"></span>
|
||||
<span class="dashboard-severity-legend-label" data-i18n="dashboard.severityInfo">信息</span>
|
||||
<span class="dashboard-severity-legend-value" id="dashboard-severity-info">0</span>
|
||||
|
||||
Reference in New Issue
Block a user