Add files via upload

This commit is contained in:
公明
2026-05-15 17:49:33 +08:00
committed by GitHub
parent 1c758bb98c
commit 179976ae57
6 changed files with 428 additions and 28 deletions
+112 -10
View File
@@ -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 {
+1
View File
@@ -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",
+1
View File
@@ -147,6 +147,7 @@
"active": "活跃",
"highFreq": "高频",
"noCallData": "暂无调用数据",
"severityClickHint": "点击查看",
"lastUpdated": "上次更新",
"viewAll": "查看全部 →",
"recentVulns": "最近漏洞",
+286 -9
View File
@@ -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 半环(背景轨迹)路径
+17 -1
View File
@@ -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;
}
+11 -8
View File
@@ -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>