mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-07-07 04:58:03 +02:00
Add files via upload
This commit is contained in:
+518
-35
@@ -61,6 +61,24 @@ let vulnerabilityPagination = {
|
||||
totalPages: 1
|
||||
};
|
||||
|
||||
const VULN_STAT_SEVERITIES = ['critical', 'high', 'medium', 'low', 'info'];
|
||||
let vulnerabilityStatCardsBound = false;
|
||||
let vulnerabilityFilterPanelBound = false;
|
||||
let vulnerabilityFilterOptionsCache = null;
|
||||
const VULNERABILITY_ADVANCED_OPEN_KEY = 'vulnerabilityAdvancedFiltersOpen';
|
||||
const VULNERABILITY_DATALIST_MAX = 8;
|
||||
const VULNERABILITY_DATALIST_MIN_QUERY = 2;
|
||||
|
||||
const VULN_FILTER_CHIP_FIELDS = [
|
||||
{ key: 'id', labelKey: 'vulnerabilityPage.vulnId' },
|
||||
{ key: 'status', labelKey: null, format: 'status' },
|
||||
{ key: 'severity', labelKey: null, format: 'severity' },
|
||||
{ key: 'conversation_id', labelKey: 'vulnerabilityPage.conversationId' },
|
||||
{ key: 'task_id', labelKey: 'vulnerabilityPage.taskOrQueueId' },
|
||||
{ key: 'conversation_tag', labelKey: 'vulnerabilityPage.conversationTag' },
|
||||
{ key: 'task_tag', labelKey: 'vulnerabilityPage.taskTag' }
|
||||
];
|
||||
|
||||
// 从地址栏 #vulnerabilities?conversation_id= / ?task_id= / ?id= 同步筛选(通知/对话菜单/任务管理联动)
|
||||
function syncVulnerabilityFiltersFromLocationHash() {
|
||||
const hash = window.location.hash.slice(1);
|
||||
@@ -74,23 +92,31 @@ function syncVulnerabilityFiltersFromLocationHash() {
|
||||
const tid = (params.get('task_id') || '').trim();
|
||||
const sev = (params.get('severity') || '').trim();
|
||||
const st = (params.get('status') || '').trim();
|
||||
if (!vid && !cid && !tid && !sev && !st) {
|
||||
const convTag = (params.get('conversation_tag') || '').trim();
|
||||
const taskTag = (params.get('task_tag') || '').trim();
|
||||
if (!vid && !cid && !tid && !sev && !st && !convTag && !taskTag) {
|
||||
return;
|
||||
}
|
||||
|
||||
vulnerabilityFilters.id = '';
|
||||
vulnerabilityFilters.conversation_id = '';
|
||||
vulnerabilityFilters.task_id = '';
|
||||
vulnerabilityFilters.conversation_tag = '';
|
||||
vulnerabilityFilters.task_tag = '';
|
||||
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 convTagEl = document.getElementById('vulnerability-conversation-tag-filter');
|
||||
const taskTagEl = document.getElementById('vulnerability-task-tag-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 (convTagEl) convTagEl.value = '';
|
||||
if (taskTagEl) taskTagEl.value = '';
|
||||
if (sevEl) sevEl.value = '';
|
||||
if (stEl) stEl.value = '';
|
||||
|
||||
@@ -106,6 +132,14 @@ function syncVulnerabilityFiltersFromLocationHash() {
|
||||
vulnerabilityFilters.task_id = tid;
|
||||
if (taskEl) taskEl.value = tid;
|
||||
}
|
||||
if (convTag) {
|
||||
vulnerabilityFilters.conversation_tag = convTag;
|
||||
if (convTagEl) convTagEl.value = convTag;
|
||||
}
|
||||
if (taskTag) {
|
||||
vulnerabilityFilters.task_tag = taskTag;
|
||||
if (taskTagEl) taskTagEl.value = taskTag;
|
||||
}
|
||||
if (sev) {
|
||||
vulnerabilityFilters.severity = sev;
|
||||
if (sevEl) sevEl.value = sev;
|
||||
@@ -115,17 +149,457 @@ function syncVulnerabilityFiltersFromLocationHash() {
|
||||
if (stEl) stEl.value = st;
|
||||
}
|
||||
vulnerabilityPagination.currentPage = 1;
|
||||
if (hasVulnerabilityAdvancedFiltersActive()) {
|
||||
setVulnerabilityAdvancedFiltersOpen(true, false);
|
||||
}
|
||||
syncVulnerabilityStatCardActiveState();
|
||||
updateVulnerabilityFilterPanelState();
|
||||
renderVulnerabilityFilterChips();
|
||||
}
|
||||
|
||||
// 初始化漏洞管理页面
|
||||
function initVulnerabilityPage() {
|
||||
// 从localStorage加载每页条数设置
|
||||
vulnerabilityPagination.pageSize = getVulnerabilityPageSize();
|
||||
initVulnerabilityStatCards();
|
||||
initVulnerabilityFilterPanel();
|
||||
syncVulnerabilityFiltersFromLocationHash();
|
||||
updateVulnerabilityFilterPanelState();
|
||||
renderVulnerabilityFilterChips();
|
||||
loadVulnerabilityFilterOptions();
|
||||
loadVulnerabilityStats();
|
||||
loadVulnerabilities();
|
||||
}
|
||||
|
||||
function initVulnerabilityStatCards() {
|
||||
if (vulnerabilityStatCardsBound) {
|
||||
syncVulnerabilityStatCardActiveState();
|
||||
return;
|
||||
}
|
||||
const root = document.getElementById('vulnerability-stat-cards');
|
||||
if (!root) return;
|
||||
vulnerabilityStatCardsBound = true;
|
||||
root.addEventListener('click', onVulnerabilityStatCardClick);
|
||||
root.addEventListener('keydown', onVulnerabilityStatCardKeydown);
|
||||
}
|
||||
|
||||
function onVulnerabilityStatCardClick(ev) {
|
||||
const totalCard = ev.target.closest('.stat-card.stat-card-total');
|
||||
if (totalCard) {
|
||||
applyVulnerabilitySeverityFilter('');
|
||||
return;
|
||||
}
|
||||
const card = ev.target.closest('.stat-card.is-clickable[data-severity]');
|
||||
if (!card) return;
|
||||
const sev = card.getAttribute('data-severity');
|
||||
if (!sev) return;
|
||||
const sevEl = document.getElementById('vulnerability-severity-filter');
|
||||
const current = sevEl ? sevEl.value : vulnerabilityFilters.severity;
|
||||
applyVulnerabilitySeverityFilter(current === sev ? '' : sev);
|
||||
}
|
||||
|
||||
function onVulnerabilityStatCardKeydown(ev) {
|
||||
if (ev.key !== 'Enter' && ev.key !== ' ') return;
|
||||
const card = ev.target.closest('.stat-card.is-clickable');
|
||||
if (!card || !card.contains(ev.target)) return;
|
||||
ev.preventDefault();
|
||||
card.click();
|
||||
}
|
||||
|
||||
function applyVulnerabilitySeverityFilter(severity) {
|
||||
const sevEl = document.getElementById('vulnerability-severity-filter');
|
||||
if (sevEl) sevEl.value = severity || '';
|
||||
applyVulnerabilityFilters();
|
||||
}
|
||||
|
||||
function readVulnerabilityFiltersFromForm() {
|
||||
vulnerabilityFilters.id = (document.getElementById('vulnerability-id-filter')?.value || '').trim();
|
||||
vulnerabilityFilters.conversation_id = (document.getElementById('vulnerability-conversation-filter')?.value || '').trim();
|
||||
vulnerabilityFilters.task_id = (document.getElementById('vulnerability-task-filter')?.value || '').trim();
|
||||
vulnerabilityFilters.conversation_tag = (document.getElementById('vulnerability-conversation-tag-filter')?.value || '').trim();
|
||||
vulnerabilityFilters.task_tag = (document.getElementById('vulnerability-task-tag-filter')?.value || '').trim();
|
||||
vulnerabilityFilters.severity = document.getElementById('vulnerability-severity-filter')?.value || '';
|
||||
vulnerabilityFilters.status = document.getElementById('vulnerability-status-filter')?.value || '';
|
||||
return vulnerabilityFilters;
|
||||
}
|
||||
|
||||
function hasVulnerabilityAdvancedFiltersActive() {
|
||||
const f = vulnerabilityFilters;
|
||||
return Boolean(f.conversation_id || f.task_id || f.conversation_tag || f.task_tag);
|
||||
}
|
||||
|
||||
function hasAnyVulnerabilityFilterActive() {
|
||||
const f = vulnerabilityFilters;
|
||||
return Boolean(
|
||||
f.id || f.conversation_id || f.task_id || f.conversation_tag || f.task_tag || f.severity || f.status
|
||||
);
|
||||
}
|
||||
|
||||
function applyVulnerabilityFilters() {
|
||||
readVulnerabilityFiltersFromForm();
|
||||
vulnerabilityPagination.currentPage = 1;
|
||||
syncVulnerabilityStatCardActiveState();
|
||||
updateVulnerabilityLocationHashFromFilters();
|
||||
updateVulnerabilityFilterPanelState();
|
||||
renderVulnerabilityFilterChips();
|
||||
loadVulnerabilityStats();
|
||||
loadVulnerabilities();
|
||||
}
|
||||
|
||||
function updateVulnerabilityLocationHashFromFilters() {
|
||||
const hash = window.location.hash.slice(1);
|
||||
const hashParts = hash.split('?');
|
||||
if (hashParts[0] !== 'vulnerabilities') return;
|
||||
const params = new URLSearchParams(hashParts.length >= 2 ? hashParts.slice(1).join('?') : '');
|
||||
const f = vulnerabilityFilters;
|
||||
const pairs = [
|
||||
['id', f.id],
|
||||
['conversation_id', f.conversation_id],
|
||||
['task_id', f.task_id],
|
||||
['conversation_tag', f.conversation_tag],
|
||||
['task_tag', f.task_tag],
|
||||
['severity', f.severity],
|
||||
['status', f.status]
|
||||
];
|
||||
pairs.forEach(function (pair) {
|
||||
if (pair[1]) {
|
||||
params.set(pair[0], pair[1]);
|
||||
} else {
|
||||
params.delete(pair[0]);
|
||||
}
|
||||
});
|
||||
const qs = params.toString();
|
||||
const newHash = qs ? 'vulnerabilities?' + qs : 'vulnerabilities';
|
||||
if (window.location.hash.slice(1) === newHash) return;
|
||||
const newFull = '#' + newHash;
|
||||
if (typeof history.replaceState === 'function') {
|
||||
history.replaceState(null, '', window.location.pathname + window.location.search + newFull);
|
||||
} else {
|
||||
window.location.hash = newHash;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleVulnerabilityAdvancedFilters(ev) {
|
||||
if (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
const toggleBtn = document.getElementById('vulnerability-advanced-toggle');
|
||||
if (!toggleBtn) return;
|
||||
const expanded = toggleBtn.getAttribute('aria-expanded') === 'true';
|
||||
setVulnerabilityAdvancedFiltersOpen(!expanded, true);
|
||||
}
|
||||
window.toggleVulnerabilityAdvancedFilters = toggleVulnerabilityAdvancedFilters;
|
||||
|
||||
function initVulnerabilityFilterPanel() {
|
||||
const panel = document.getElementById('vulnerability-filter-panel');
|
||||
if (!panel) return;
|
||||
|
||||
if (vulnerabilityFilterPanelBound) {
|
||||
updateVulnerabilityFilterPanelState();
|
||||
return;
|
||||
}
|
||||
vulnerabilityFilterPanelBound = true;
|
||||
|
||||
let savedOpen = false;
|
||||
try {
|
||||
savedOpen = localStorage.getItem(VULNERABILITY_ADVANCED_OPEN_KEY) === 'true';
|
||||
} catch (e) { /* ignore */ }
|
||||
setVulnerabilityAdvancedFiltersOpen(savedOpen, false);
|
||||
|
||||
const stEl = document.getElementById('vulnerability-status-filter');
|
||||
if (stEl) stEl.addEventListener('change', applyVulnerabilityFilters);
|
||||
|
||||
const textIds = [
|
||||
'vulnerability-id-filter',
|
||||
'vulnerability-conversation-filter',
|
||||
'vulnerability-task-filter',
|
||||
'vulnerability-conversation-tag-filter',
|
||||
'vulnerability-task-tag-filter'
|
||||
];
|
||||
textIds.forEach(function (id) {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
el.addEventListener('keydown', function (ev) {
|
||||
if (ev.key === 'Enter') {
|
||||
ev.preventDefault();
|
||||
applyVulnerabilityFilters();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
bindVulnerabilityFilterTypeaheads();
|
||||
}
|
||||
|
||||
function setVulnerabilityAdvancedFiltersOpen(open, persist) {
|
||||
const toggleBtn = document.getElementById('vulnerability-advanced-toggle');
|
||||
const advanced = document.getElementById('vulnerability-advanced-filters');
|
||||
const wrap = document.querySelector('#vulnerability-filter-panel .vulnerability-filter-advanced-wrap');
|
||||
if (!toggleBtn || !advanced) return;
|
||||
toggleBtn.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||||
advanced.hidden = !open;
|
||||
advanced.classList.toggle('is-open', open);
|
||||
if (wrap) wrap.classList.toggle('is-expanded', open);
|
||||
if (persist) {
|
||||
try {
|
||||
localStorage.setItem(VULNERABILITY_ADVANCED_OPEN_KEY, open ? 'true' : 'false');
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
}
|
||||
|
||||
function countVulnerabilityAdvancedFiltersActive() {
|
||||
const f = vulnerabilityFilters;
|
||||
let n = 0;
|
||||
if (f.conversation_id) n++;
|
||||
if (f.task_id) n++;
|
||||
if (f.conversation_tag) n++;
|
||||
if (f.task_tag) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
function updateVulnerabilityAdvancedBadge() {
|
||||
const badge = document.getElementById('vulnerability-advanced-badge');
|
||||
if (!badge) return;
|
||||
readVulnerabilityFiltersFromForm();
|
||||
const n = countVulnerabilityAdvancedFiltersActive();
|
||||
if (n > 0) {
|
||||
badge.hidden = false;
|
||||
badge.textContent = '(' + n + ')';
|
||||
badge.setAttribute('aria-label', String(n));
|
||||
} else {
|
||||
badge.hidden = true;
|
||||
badge.textContent = '';
|
||||
badge.removeAttribute('aria-label');
|
||||
}
|
||||
}
|
||||
|
||||
function updateVulnerabilityFilterPanelState() {
|
||||
const panel = document.getElementById('vulnerability-filter-panel');
|
||||
if (!panel) return;
|
||||
readVulnerabilityFiltersFromForm();
|
||||
panel.classList.toggle('is-filtered', hasAnyVulnerabilityFilterActive());
|
||||
updateVulnerabilityAdvancedBadge();
|
||||
}
|
||||
|
||||
function formatVulnerabilityFilterChipValue(key, value) {
|
||||
if (key === 'severity') return vulnSeverityLabel(value);
|
||||
if (key === 'status') return vulnStatusLabel(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
function renderVulnerabilityFilterChips() {
|
||||
const wrap = document.getElementById('vulnerability-filter-chips');
|
||||
const list = document.getElementById('vulnerability-filter-chips-list');
|
||||
if (!wrap || !list) return;
|
||||
|
||||
readVulnerabilityFiltersFromForm();
|
||||
const chips = [];
|
||||
VULN_FILTER_CHIP_FIELDS.forEach(function (field) {
|
||||
const val = vulnerabilityFilters[field.key];
|
||||
if (!val) return;
|
||||
const label = field.labelKey ? vulnT(field.labelKey) : '';
|
||||
const displayVal = formatVulnerabilityFilterChipValue(field.key, val);
|
||||
const text = label ? label + ': ' + displayVal : displayVal;
|
||||
chips.push({ key: field.key, text: text });
|
||||
});
|
||||
|
||||
if (!chips.length) {
|
||||
wrap.hidden = true;
|
||||
list.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
wrap.hidden = false;
|
||||
const removeLabel = vulnT('vulnerabilityPage.chipRemove');
|
||||
list.innerHTML = chips.map(function (chip) {
|
||||
return (
|
||||
'<button type="button" class="vulnerability-filter-chip" role="listitem" data-filter-key="' +
|
||||
escapeHtml(chip.key) + '" title="' + escapeHtml(removeLabel) + '">' +
|
||||
'<span>' + escapeHtml(chip.text) + '</span>' +
|
||||
'<span class="vulnerability-filter-chip-remove" aria-hidden="true">×</span>' +
|
||||
'</button>'
|
||||
);
|
||||
}).join('');
|
||||
|
||||
list.querySelectorAll('.vulnerability-filter-chip').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
const key = btn.getAttribute('data-filter-key');
|
||||
if (key) removeVulnerabilityFilterByKey(key);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function removeVulnerabilityFilterByKey(key) {
|
||||
const map = {
|
||||
id: 'vulnerability-id-filter',
|
||||
conversation_id: 'vulnerability-conversation-filter',
|
||||
task_id: 'vulnerability-task-filter',
|
||||
conversation_tag: 'vulnerability-conversation-tag-filter',
|
||||
task_tag: 'vulnerability-task-tag-filter',
|
||||
severity: 'vulnerability-severity-filter',
|
||||
status: 'vulnerability-status-filter'
|
||||
};
|
||||
const elId = map[key];
|
||||
if (elId) {
|
||||
const el = document.getElementById(elId);
|
||||
if (el) el.value = '';
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(vulnerabilityFilters, key)) {
|
||||
vulnerabilityFilters[key] = '';
|
||||
}
|
||||
applyVulnerabilityFilters();
|
||||
}
|
||||
|
||||
async function loadVulnerabilityFilterOptions() {
|
||||
if (typeof apiFetch === 'undefined') return;
|
||||
try {
|
||||
const response = await apiFetch('/api/vulnerabilities/filter-options');
|
||||
if (!response.ok) return;
|
||||
vulnerabilityFilterOptionsCache = await response.json();
|
||||
populateVulnerabilityDatalist(
|
||||
'vulnerability-conversation-tag-suggestions',
|
||||
vulnerabilityFilterOptionsCache.conversation_tags,
|
||||
{ max: 20 }
|
||||
);
|
||||
populateVulnerabilityDatalist(
|
||||
'vulnerability-task-tag-suggestions',
|
||||
vulnerabilityFilterOptionsCache.task_tags,
|
||||
{ max: 20 }
|
||||
);
|
||||
clearVulnerabilityDatalist('vulnerability-conversation-suggestions');
|
||||
clearVulnerabilityDatalist('vulnerability-task-suggestions');
|
||||
} catch (e) {
|
||||
console.warn('加载漏洞筛选建议失败', e);
|
||||
}
|
||||
}
|
||||
|
||||
function clearVulnerabilityDatalist(listId) {
|
||||
const list = document.getElementById(listId);
|
||||
if (list) list.innerHTML = '';
|
||||
}
|
||||
|
||||
function populateVulnerabilityDatalist(listId, values, opts) {
|
||||
const list = document.getElementById(listId);
|
||||
if (!list || !Array.isArray(values)) return;
|
||||
const max = (opts && opts.max) || VULNERABILITY_DATALIST_MAX;
|
||||
const seen = new Set();
|
||||
const unique = [];
|
||||
values.forEach(function (v) {
|
||||
const s = String(v || '').trim();
|
||||
if (!s || seen.has(s)) return;
|
||||
seen.add(s);
|
||||
unique.push(s);
|
||||
if (unique.length >= max) return;
|
||||
});
|
||||
list.innerHTML = unique.slice(0, max).map(function (v) {
|
||||
return '<option value="' + escapeHtml(v) + '"></option>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function filterVulnerabilitySuggestionPool(pool, query) {
|
||||
if (!Array.isArray(pool) || !query) return [];
|
||||
const q = query.toLowerCase();
|
||||
const out = [];
|
||||
for (let i = 0; i < pool.length && out.length < VULNERABILITY_DATALIST_MAX; i++) {
|
||||
const s = String(pool[i] || '').trim();
|
||||
if (s && s.toLowerCase().indexOf(q) !== -1) out.push(s);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function updateVulnerabilityTypeaheadDatalist(inputId, listId, poolKey) {
|
||||
const el = document.getElementById(inputId);
|
||||
if (!el || !vulnerabilityFilterOptionsCache) return;
|
||||
const q = el.value.trim();
|
||||
if (q.length < VULNERABILITY_DATALIST_MIN_QUERY) {
|
||||
clearVulnerabilityDatalist(listId);
|
||||
return;
|
||||
}
|
||||
let pool = vulnerabilityFilterOptionsCache[poolKey] || [];
|
||||
if (poolKey === 'task_ids') {
|
||||
pool = (vulnerabilityFilterOptionsCache.task_ids || []).concat(vulnerabilityFilterOptionsCache.queue_ids || []);
|
||||
}
|
||||
populateVulnerabilityDatalist(listId, filterVulnerabilitySuggestionPool(pool, q));
|
||||
}
|
||||
|
||||
function bindVulnerabilityFilterTypeaheads() {
|
||||
const pairs = [
|
||||
{ inputId: 'vulnerability-conversation-filter', listId: 'vulnerability-conversation-suggestions', poolKey: 'conversation_ids' },
|
||||
{ inputId: 'vulnerability-task-filter', listId: 'vulnerability-task-suggestions', poolKey: 'task_ids' }
|
||||
];
|
||||
pairs.forEach(function (pair) {
|
||||
const el = document.getElementById(pair.inputId);
|
||||
if (!el) return;
|
||||
el.addEventListener('input', function () {
|
||||
updateVulnerabilityTypeaheadDatalist(pair.inputId, pair.listId, pair.poolKey);
|
||||
});
|
||||
el.addEventListener('blur', function () {
|
||||
setTimeout(function () { clearVulnerabilityDatalist(pair.listId); }, 150);
|
||||
});
|
||||
});
|
||||
|
||||
['vulnerability-conversation-tag-filter', 'vulnerability-task-tag-filter'].forEach(function (inputId) {
|
||||
const el = document.getElementById(inputId);
|
||||
if (!el) return;
|
||||
el.addEventListener('focus', function () {
|
||||
if (!vulnerabilityFilterOptionsCache) return;
|
||||
const listId = inputId === 'vulnerability-conversation-tag-filter'
|
||||
? 'vulnerability-conversation-tag-suggestions'
|
||||
: 'vulnerability-task-tag-suggestions';
|
||||
const key = inputId === 'vulnerability-conversation-tag-filter' ? 'conversation_tags' : 'task_tags';
|
||||
const q = el.value.trim();
|
||||
if (q.length >= VULNERABILITY_DATALIST_MIN_QUERY) {
|
||||
populateVulnerabilityDatalist(listId, filterVulnerabilitySuggestionPool(vulnerabilityFilterOptionsCache[key], q), { max: 20 });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function syncVulnerabilityStatCardActiveState() {
|
||||
const sevEl = document.getElementById('vulnerability-severity-filter');
|
||||
const sev = (sevEl && sevEl.value) || vulnerabilityFilters.severity || '';
|
||||
const root = document.getElementById('vulnerability-stat-cards');
|
||||
if (!root) return;
|
||||
root.querySelectorAll('.stat-card.is-clickable').forEach(function (card) {
|
||||
if (card.classList.contains('stat-card-total')) {
|
||||
card.classList.toggle('is-active', !sev);
|
||||
card.setAttribute('aria-pressed', sev ? 'false' : 'true');
|
||||
} else {
|
||||
const cardSev = card.getAttribute('data-severity');
|
||||
const active = Boolean(sev && cardSev === sev);
|
||||
card.classList.toggle('is-active', active);
|
||||
card.setAttribute('aria-pressed', active ? 'true' : 'false');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateVulnerabilityStatStackedBar(bySeverity, total) {
|
||||
const bar = document.getElementById('stat-stacked-bar');
|
||||
if (!bar) return;
|
||||
const segs = bar.querySelectorAll('.stat-stacked-seg');
|
||||
if (!total) {
|
||||
bar.classList.add('is-empty');
|
||||
segs.forEach(function (seg) {
|
||||
seg.style.flex = '0 0 0';
|
||||
seg.style.display = 'none';
|
||||
});
|
||||
return;
|
||||
}
|
||||
bar.classList.remove('is-empty');
|
||||
segs.forEach(function (seg) {
|
||||
const sev = seg.getAttribute('data-sev');
|
||||
const count = bySeverity[sev] || 0;
|
||||
if (count <= 0) {
|
||||
seg.style.display = 'none';
|
||||
seg.style.flex = '0 0 0';
|
||||
return;
|
||||
}
|
||||
seg.style.display = '';
|
||||
const pct = Math.max((count / total) * 100, 0);
|
||||
seg.style.flex = '1 1 ' + pct + '%';
|
||||
});
|
||||
}
|
||||
|
||||
// 加载漏洞统计
|
||||
async function loadVulnerabilityStats() {
|
||||
try {
|
||||
@@ -169,15 +643,33 @@ function updateVulnerabilityStats(stats) {
|
||||
by_status: {}
|
||||
};
|
||||
}
|
||||
|
||||
document.getElementById('stat-total').textContent = stats.total || 0;
|
||||
|
||||
|
||||
const total = stats.total || 0;
|
||||
const bySeverity = stats.by_severity || {};
|
||||
document.getElementById('stat-critical').textContent = bySeverity.critical || 0;
|
||||
document.getElementById('stat-high').textContent = bySeverity.high || 0;
|
||||
document.getElementById('stat-medium').textContent = bySeverity.medium || 0;
|
||||
document.getElementById('stat-low').textContent = bySeverity.low || 0;
|
||||
document.getElementById('stat-info').textContent = bySeverity.info || 0;
|
||||
|
||||
const totalEl = document.getElementById('stat-total');
|
||||
if (totalEl) {
|
||||
totalEl.textContent = String(total);
|
||||
totalEl.classList.toggle('is-zero', total === 0);
|
||||
}
|
||||
|
||||
VULN_STAT_SEVERITIES.forEach(function (sev) {
|
||||
const count = bySeverity[sev] || 0;
|
||||
const valEl = document.getElementById('stat-' + sev);
|
||||
const pctEl = document.getElementById('stat-' + sev + '-pct');
|
||||
if (valEl) {
|
||||
valEl.textContent = String(count);
|
||||
valEl.classList.toggle('is-zero', count === 0);
|
||||
}
|
||||
if (pctEl) {
|
||||
const pct = total > 0 ? Math.round((count / total) * 100) : 0;
|
||||
pctEl.textContent = pct + '%';
|
||||
pctEl.setAttribute('aria-hidden', total === 0 ? 'true' : 'false');
|
||||
}
|
||||
});
|
||||
|
||||
updateVulnerabilityStatStackedBar(bySeverity, total);
|
||||
syncVulnerabilityStatCardActiveState();
|
||||
}
|
||||
|
||||
// 加载漏洞列表
|
||||
@@ -591,32 +1083,26 @@ function closeVulnerabilityModal() {
|
||||
currentVulnerabilityId = null;
|
||||
}
|
||||
|
||||
// 筛选漏洞
|
||||
// 筛选漏洞(应用当前表单条件)
|
||||
function filterVulnerabilities() {
|
||||
vulnerabilityFilters.id = document.getElementById('vulnerability-id-filter').value.trim();
|
||||
vulnerabilityFilters.conversation_id = document.getElementById('vulnerability-conversation-filter').value.trim();
|
||||
vulnerabilityFilters.task_id = document.getElementById('vulnerability-task-filter').value.trim();
|
||||
vulnerabilityFilters.conversation_tag = document.getElementById('vulnerability-conversation-tag-filter').value.trim();
|
||||
vulnerabilityFilters.task_tag = document.getElementById('vulnerability-task-tag-filter').value.trim();
|
||||
vulnerabilityFilters.severity = document.getElementById('vulnerability-severity-filter').value;
|
||||
vulnerabilityFilters.status = document.getElementById('vulnerability-status-filter').value;
|
||||
|
||||
// 重置到第一页
|
||||
vulnerabilityPagination.currentPage = 1;
|
||||
|
||||
loadVulnerabilityStats();
|
||||
loadVulnerabilities();
|
||||
applyVulnerabilityFilters();
|
||||
}
|
||||
|
||||
// 清除筛选
|
||||
function clearVulnerabilityFilters() {
|
||||
document.getElementById('vulnerability-id-filter').value = '';
|
||||
document.getElementById('vulnerability-conversation-filter').value = '';
|
||||
document.getElementById('vulnerability-task-filter').value = '';
|
||||
document.getElementById('vulnerability-conversation-tag-filter').value = '';
|
||||
document.getElementById('vulnerability-task-tag-filter').value = '';
|
||||
document.getElementById('vulnerability-severity-filter').value = '';
|
||||
document.getElementById('vulnerability-status-filter').value = '';
|
||||
const fields = [
|
||||
'vulnerability-id-filter',
|
||||
'vulnerability-conversation-filter',
|
||||
'vulnerability-task-filter',
|
||||
'vulnerability-conversation-tag-filter',
|
||||
'vulnerability-task-tag-filter',
|
||||
'vulnerability-severity-filter',
|
||||
'vulnerability-status-filter'
|
||||
];
|
||||
fields.forEach(function (id) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.value = '';
|
||||
});
|
||||
|
||||
vulnerabilityFilters = {
|
||||
id: '',
|
||||
@@ -628,11 +1114,7 @@ function clearVulnerabilityFilters() {
|
||||
status: ''
|
||||
};
|
||||
|
||||
// 重置到第一页
|
||||
vulnerabilityPagination.currentPage = 1;
|
||||
|
||||
loadVulnerabilityStats();
|
||||
loadVulnerabilities();
|
||||
applyVulnerabilityFilters();
|
||||
}
|
||||
|
||||
// 刷新漏洞
|
||||
@@ -908,6 +1390,7 @@ window.onclick = function(event) {
|
||||
document.addEventListener('languagechange', function () {
|
||||
const page = document.getElementById('page-vulnerabilities');
|
||||
if (page && page.classList.contains('active')) {
|
||||
renderVulnerabilityFilterChips();
|
||||
loadVulnerabilities();
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user