mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-08-28 05:40:33 +02:00
Add files via upload
This commit is contained in:
@@ -48,6 +48,7 @@ let currentVulnerabilityId = null;
|
||||
let vulnerabilityFilters = {
|
||||
q: '',
|
||||
id: '',
|
||||
project_id: '',
|
||||
conversation_id: '',
|
||||
task_id: '',
|
||||
conversation_tag: '',
|
||||
@@ -77,6 +78,7 @@ const VULN_FILTER_CHIP_FIELDS = [
|
||||
{ key: 'id', labelKey: 'vulnerabilityPage.vulnId' },
|
||||
{ key: 'status', labelKey: null, format: 'status' },
|
||||
{ key: 'severity', labelKey: null, format: 'severity' },
|
||||
{ key: 'project_id', labelKey: null },
|
||||
{ key: 'conversation_id', labelKey: 'vulnerabilityPage.conversationId' },
|
||||
{ key: 'task_id', labelKey: 'vulnerabilityPage.taskOrQueueId' },
|
||||
{ key: 'conversation_tag', labelKey: 'vulnerabilityPage.conversationTag' },
|
||||
@@ -98,13 +100,15 @@ function syncVulnerabilityFiltersFromLocationHash() {
|
||||
const st = (params.get('status') || '').trim();
|
||||
const convTag = (params.get('conversation_tag') || '').trim();
|
||||
const taskTag = (params.get('task_tag') || '').trim();
|
||||
const pid = (params.get('project_id') || '').trim();
|
||||
const q = (params.get('q') || params.get('search') || '').trim();
|
||||
if (!vid && !cid && !tid && !sev && !st && !convTag && !taskTag && !q) {
|
||||
if (!vid && !cid && !tid && !sev && !st && !convTag && !taskTag && !q && !pid) {
|
||||
return;
|
||||
}
|
||||
|
||||
vulnerabilityFilters.q = '';
|
||||
vulnerabilityFilters.id = '';
|
||||
vulnerabilityFilters.project_id = '';
|
||||
vulnerabilityFilters.conversation_id = '';
|
||||
vulnerabilityFilters.task_id = '';
|
||||
vulnerabilityFilters.conversation_tag = '';
|
||||
@@ -117,6 +121,7 @@ function syncVulnerabilityFiltersFromLocationHash() {
|
||||
const taskEl = document.getElementById('vulnerability-task-filter');
|
||||
const convTagEl = document.getElementById('vulnerability-conversation-tag-filter');
|
||||
const taskTagEl = document.getElementById('vulnerability-task-tag-filter');
|
||||
const projEl = document.getElementById('vulnerability-project-filter');
|
||||
const sevEl = document.getElementById('vulnerability-severity-filter');
|
||||
const stEl = document.getElementById('vulnerability-status-filter');
|
||||
if (searchEl) searchEl.value = '';
|
||||
@@ -125,6 +130,7 @@ function syncVulnerabilityFiltersFromLocationHash() {
|
||||
if (taskEl) taskEl.value = '';
|
||||
if (convTagEl) convTagEl.value = '';
|
||||
if (taskTagEl) taskTagEl.value = '';
|
||||
if (projEl) projEl.value = '';
|
||||
if (sevEl) sevEl.value = '';
|
||||
if (stEl) stEl.value = '';
|
||||
|
||||
@@ -132,6 +138,10 @@ function syncVulnerabilityFiltersFromLocationHash() {
|
||||
vulnerabilityFilters.q = q;
|
||||
if (searchEl) searchEl.value = q;
|
||||
}
|
||||
if (pid) {
|
||||
vulnerabilityFilters.project_id = pid;
|
||||
if (projEl) projEl.value = pid;
|
||||
}
|
||||
if (vid) {
|
||||
vulnerabilityFilters.id = vid;
|
||||
if (exactIdEl) exactIdEl.value = vid;
|
||||
@@ -167,12 +177,13 @@ function syncVulnerabilityFiltersFromLocationHash() {
|
||||
}
|
||||
|
||||
// 初始化漏洞管理页面
|
||||
function initVulnerabilityPage() {
|
||||
async function initVulnerabilityPage() {
|
||||
// 从localStorage加载每页条数设置
|
||||
vulnerabilityPagination.pageSize = getVulnerabilityPageSize();
|
||||
initVulnerabilityStatCards();
|
||||
initVulnerabilityFilterPanel();
|
||||
syncVulnerabilityFiltersFromLocationHash();
|
||||
await refreshVulnerabilityProjectFilter();
|
||||
updateVulnerabilityFilterPanelState();
|
||||
renderVulnerabilityFilterChips();
|
||||
loadVulnerabilityFilterOptions();
|
||||
@@ -224,6 +235,7 @@ function applyVulnerabilitySeverityFilter(severity) {
|
||||
function readVulnerabilityFiltersFromForm() {
|
||||
vulnerabilityFilters.q = (document.getElementById('vulnerability-search-filter')?.value || '').trim();
|
||||
vulnerabilityFilters.id = (document.getElementById('vulnerability-exact-id-filter')?.value || '').trim();
|
||||
vulnerabilityFilters.project_id = (document.getElementById('vulnerability-project-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();
|
||||
@@ -241,7 +253,7 @@ function hasVulnerabilityAdvancedFiltersActive() {
|
||||
function hasAnyVulnerabilityFilterActive() {
|
||||
const f = vulnerabilityFilters;
|
||||
return Boolean(
|
||||
f.q || f.id || f.conversation_id || f.task_id || f.conversation_tag || f.task_tag || f.severity || f.status
|
||||
f.q || f.id || f.project_id || f.conversation_id || f.task_id || f.conversation_tag || f.task_tag || f.severity || f.status
|
||||
);
|
||||
}
|
||||
|
||||
@@ -265,6 +277,7 @@ function updateVulnerabilityLocationHashFromFilters() {
|
||||
const pairs = [
|
||||
['q', f.q],
|
||||
['id', f.id],
|
||||
['project_id', f.project_id],
|
||||
['conversation_id', f.conversation_id],
|
||||
['task_id', f.task_id],
|
||||
['conversation_tag', f.conversation_tag],
|
||||
@@ -476,6 +489,10 @@ function updateVulnerabilityFilterPanelState() {
|
||||
function formatVulnerabilityFilterChipValue(key, value) {
|
||||
if (key === 'severity') return vulnSeverityLabel(value);
|
||||
if (key === 'status') return vulnStatusLabel(value);
|
||||
if (key === 'project_id') {
|
||||
const name = typeof getProjectName === 'function' ? getProjectName(value) : '';
|
||||
return name && name !== value ? name : value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -489,7 +506,7 @@ function renderVulnerabilityFilterChips() {
|
||||
VULN_FILTER_CHIP_FIELDS.forEach(function (field) {
|
||||
const val = vulnerabilityFilters[field.key];
|
||||
if (!val) return;
|
||||
const label = field.labelKey ? vulnT(field.labelKey) : '';
|
||||
const label = field.labelKey ? vulnT(field.labelKey) : (field.key === 'project_id' ? '项目' : '');
|
||||
const displayVal = formatVulnerabilityFilterChipValue(field.key, val);
|
||||
const text = label ? label + ': ' + displayVal : displayVal;
|
||||
chips.push({ key: field.key, text: text });
|
||||
@@ -529,6 +546,7 @@ function removeVulnerabilityFilterByKey(key) {
|
||||
task_id: 'vulnerability-task-filter',
|
||||
conversation_tag: 'vulnerability-conversation-tag-filter',
|
||||
task_tag: 'vulnerability-task-tag-filter',
|
||||
project_id: 'vulnerability-project-filter',
|
||||
severity: 'vulnerability-severity-filter',
|
||||
status: 'vulnerability-status-filter'
|
||||
};
|
||||
@@ -850,6 +868,12 @@ function renderVulnerabilities(vulnerabilities) {
|
||||
const severityText = vulnSeverityLabel(vuln.severity);
|
||||
const statusText = vulnStatusLabel(vuln.status);
|
||||
const createdDate = new Date(vuln.created_at).toLocaleString(vulnDateLocale());
|
||||
const projectLabel = vuln.project_id
|
||||
? escapeHtml(typeof getProjectName === 'function' ? getProjectName(vuln.project_id) : vuln.project_id)
|
||||
: escapeHtml(vulnT('vulnerabilityPage.projectUnbound'));
|
||||
const projectBadge = vuln.project_id
|
||||
? `<span class="vulnerability-project-badge" title="${escapeHtml(vuln.project_id)}">${escapeHtml(vulnT('vulnerabilityPage.detailProject'))}: ${projectLabel}</span>`
|
||||
: `<span class="vulnerability-project-badge vulnerability-project-badge--unbound">${escapeHtml(vulnT('vulnerabilityPage.projectUnbound'))}</span>`;
|
||||
const dlTitle = escapeHtml(vulnT('vulnerabilityPage.downloadMarkdownTitle'));
|
||||
const editTitle = escapeHtml(vulnT('common.edit'));
|
||||
const deleteTitle = escapeHtml(vulnT('common.delete'));
|
||||
@@ -867,6 +891,7 @@ function renderVulnerabilities(vulnerabilities) {
|
||||
<div class="vulnerability-meta">
|
||||
<span class="severity-badge ${severityClass}">${severityText}</span>
|
||||
<span class="status-badge status-${vuln.status}">${statusText}</span>
|
||||
${projectBadge}
|
||||
<span class="vulnerability-date">${createdDate}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -895,6 +920,7 @@ function renderVulnerabilities(vulnerabilities) {
|
||||
${vuln.description ? `<div class="vulnerability-description">${escapeHtml(vuln.description)}</div>` : ''}
|
||||
<div class="vulnerability-details">
|
||||
${vulnDetailField(vulnT('vulnerabilityPage.detailVulnId'), vuln.id, true)}
|
||||
${vulnDetailProjectField(vuln)}
|
||||
${vuln.type ? vulnDetailField(vulnT('vulnerabilityPage.detailType'), vuln.type, false) : ''}
|
||||
${vuln.target ? vulnDetailField(vulnT('vulnerabilityPage.detailTarget'), vuln.target, false) : ''}
|
||||
${vulnDetailField(vulnT('vulnerabilityPage.detailConversationId'), vuln.conversation_id, true)}
|
||||
@@ -1005,11 +1031,50 @@ async function changeVulnerabilityPageSize() {
|
||||
await loadVulnerabilities();
|
||||
}
|
||||
|
||||
function buildVulnerabilityProjectOptionsHtml(selectedId) {
|
||||
const sel = (selectedId || '').trim();
|
||||
let html = `<option value="">${escapeHtml(vulnT('vulnerabilityModal.projectNone'))}</option>`;
|
||||
const entries = typeof projectNameById !== 'undefined' ? Object.entries(projectNameById) : [];
|
||||
entries.sort((a, b) => (a[1] || '').localeCompare(b[1] || '', undefined, { sensitivity: 'base' }));
|
||||
entries.forEach(([id, name]) => {
|
||||
if (!id) return;
|
||||
const selected = id === sel ? ' selected' : '';
|
||||
html += `<option value="${escapeHtml(id)}"${selected}>${escapeHtml(name || id)}</option>`;
|
||||
});
|
||||
if (sel && !entries.some(([id]) => id === sel)) {
|
||||
html += `<option value="${escapeHtml(sel)}" selected>${escapeHtml(sel)}</option>`;
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
async function populateVulnerabilityModalProjectSelect(selectedId) {
|
||||
const sel = document.getElementById('vulnerability-project-id');
|
||||
if (!sel) return;
|
||||
try {
|
||||
const res = await apiFetch('/api/projects?limit=200');
|
||||
if (res.ok) {
|
||||
const list = await res.json();
|
||||
if (typeof rebuildProjectNameMap === 'function') {
|
||||
rebuildProjectNameMap(list);
|
||||
} else if (typeof projectNameById !== 'undefined') {
|
||||
(list || []).forEach((p) => { if (p.id) projectNameById[p.id] = p.name || p.id; });
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('加载项目列表失败', e);
|
||||
}
|
||||
sel.innerHTML = buildVulnerabilityProjectOptionsHtml(selectedId || '');
|
||||
sel.value = selectedId || '';
|
||||
}
|
||||
|
||||
// 显示添加漏洞模态框
|
||||
function showAddVulnerabilityModal() {
|
||||
async function showAddVulnerabilityModal() {
|
||||
currentVulnerabilityId = null;
|
||||
document.getElementById('vulnerability-modal-title').textContent = vulnT('vulnerability.addVuln');
|
||||
|
||||
const defaultProject = vulnerabilityFilters.project_id || '';
|
||||
await populateVulnerabilityModalProjectSelect(defaultProject);
|
||||
|
||||
// 清空表单
|
||||
document.getElementById('vulnerability-conversation-id').value = '';
|
||||
document.getElementById('vulnerability-conversation-tag').value = '';
|
||||
@@ -1051,6 +1116,8 @@ async function editVulnerability(id) {
|
||||
document.getElementById('vulnerability-impact').value = vuln.impact || '';
|
||||
document.getElementById('vulnerability-recommendation').value = vuln.recommendation || '';
|
||||
|
||||
await populateVulnerabilityModalProjectSelect(vuln.project_id || '');
|
||||
|
||||
document.getElementById('vulnerability-modal').style.display = 'block';
|
||||
} catch (error) {
|
||||
console.error('加载漏洞失败:', error);
|
||||
@@ -1069,8 +1136,11 @@ async function saveVulnerability() {
|
||||
return;
|
||||
}
|
||||
|
||||
const projectId = (document.getElementById('vulnerability-project-id')?.value || '').trim();
|
||||
|
||||
const data = {
|
||||
conversation_id: conversationId,
|
||||
project_id: projectId,
|
||||
conversation_tag: document.getElementById('vulnerability-conversation-tag').value.trim(),
|
||||
task_tag: document.getElementById('vulnerability-task-tag').value.trim(),
|
||||
title: title,
|
||||
@@ -1090,12 +1160,30 @@ async function saveVulnerability() {
|
||||
: '/api/vulnerabilities';
|
||||
const method = currentVulnerabilityId ? 'PUT' : 'POST';
|
||||
|
||||
let body = data;
|
||||
if (currentVulnerabilityId) {
|
||||
body = {
|
||||
project_id: projectId,
|
||||
conversation_tag: data.conversation_tag,
|
||||
task_tag: data.task_tag,
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
severity: data.severity,
|
||||
status: data.status,
|
||||
type: data.type,
|
||||
target: data.target,
|
||||
proof: data.proof,
|
||||
impact: data.impact,
|
||||
recommendation: data.recommendation,
|
||||
};
|
||||
}
|
||||
|
||||
const response = await apiFetch(url, {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -1167,6 +1255,7 @@ function clearVulnerabilityFilters() {
|
||||
'vulnerability-task-filter',
|
||||
'vulnerability-conversation-tag-filter',
|
||||
'vulnerability-task-tag-filter',
|
||||
'vulnerability-project-filter',
|
||||
'vulnerability-severity-filter',
|
||||
'vulnerability-status-filter'
|
||||
];
|
||||
@@ -1178,6 +1267,7 @@ function clearVulnerabilityFilters() {
|
||||
vulnerabilityFilters = {
|
||||
q: '',
|
||||
id: '',
|
||||
project_id: '',
|
||||
conversation_id: '',
|
||||
task_id: '',
|
||||
conversation_tag: '',
|
||||
@@ -1272,6 +1362,21 @@ function vulnerabilityCopyEncoded(evt, encoded) {
|
||||
}
|
||||
}
|
||||
|
||||
function vulnDetailProjectField(vuln) {
|
||||
const label = vulnT('vulnerabilityPage.detailProject');
|
||||
const hint = escapeHtml(vulnT('vulnerabilityPage.projectBindHint'));
|
||||
return `<div class="vuln-detail-field">
|
||||
<div class="vuln-detail-field__label">${escapeHtml(label)}</div>
|
||||
<div class="vuln-detail-field__row">
|
||||
<select class="vuln-detail-field-select vulnerability-project-bind-select" data-vuln-id="${escapeHtml(vuln.id)}"
|
||||
onchange="bindVulnerabilityProject(this.dataset.vulnId, this.value, true)" onclick="event.stopPropagation();"
|
||||
title="${hint}" aria-label="${escapeHtml(label)}">
|
||||
${buildVulnerabilityProjectOptionsHtml(vuln.project_id || '')}
|
||||
</select>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function vulnDetailField(label, value, asCode) {
|
||||
if (value === undefined || value === null || String(value) === '') {
|
||||
return '';
|
||||
@@ -1352,7 +1457,7 @@ function buildVulnerabilityFilterParams() {
|
||||
if (vulnerabilityFilters.q) {
|
||||
params.append('q', vulnerabilityFilters.q);
|
||||
}
|
||||
const keys = ['id', 'conversation_id', 'task_id', 'conversation_tag', 'task_tag', 'severity', 'status'];
|
||||
const keys = ['id', 'project_id', 'conversation_id', 'task_id', 'conversation_tag', 'task_tag', 'severity', 'status'];
|
||||
keys.forEach(function (k) {
|
||||
if (vulnerabilityFilters[k]) {
|
||||
params.append(k, vulnerabilityFilters[k]);
|
||||
@@ -1470,3 +1575,80 @@ document.addEventListener('languagechange', function () {
|
||||
}
|
||||
});
|
||||
|
||||
async function bindVulnerabilityProject(vulnId, projectId, silent) {
|
||||
if (!vulnId) return;
|
||||
try {
|
||||
const response = await apiFetch(`/api/vulnerabilities/${encodeURIComponent(vulnId)}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ project_id: projectId || '' }),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const err = await response.json().catch(() => ({}));
|
||||
throw new Error(err.error || vulnT('vulnerabilityPage.projectBindFailed'));
|
||||
}
|
||||
if (!silent) {
|
||||
alert(vulnT('vulnerabilityPage.projectBindOk'));
|
||||
}
|
||||
loadVulnerabilityStats();
|
||||
loadVulnerabilities();
|
||||
} catch (error) {
|
||||
console.error('绑定项目失败:', error);
|
||||
alert(vulnT('vulnerabilityPage.projectBindFailed') + ': ' + error.message);
|
||||
loadVulnerabilities();
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshVulnerabilityProjectFilter() {
|
||||
const sel = document.getElementById('vulnerability-project-filter');
|
||||
if (!sel) return;
|
||||
try {
|
||||
const res = await apiFetch('/api/projects?limit=200');
|
||||
if (!res.ok) return;
|
||||
const list = await res.json();
|
||||
if (typeof rebuildProjectNameMap === 'function') {
|
||||
rebuildProjectNameMap(list);
|
||||
} else if (typeof projectNameById !== 'undefined') {
|
||||
list.forEach((p) => { if (p.id) projectNameById[p.id] = p.name || p.id; });
|
||||
}
|
||||
const cur = vulnerabilityFilters.project_id || sel.value || '';
|
||||
let html = '<option value="">全部项目</option>';
|
||||
(list || []).forEach((p) => {
|
||||
if (!p.id) return;
|
||||
const selected = p.id === cur ? ' selected' : '';
|
||||
const arch = p.status === 'archived' ? ' [归档]' : '';
|
||||
html += `<option value="${escapeHtml(p.id)}"${selected}>${escapeHtml(p.name || p.id)}${arch}</option>`;
|
||||
});
|
||||
sel.innerHTML = html;
|
||||
if (cur) sel.value = cur;
|
||||
const modalSel = document.getElementById('vulnerability-project-id');
|
||||
if (modalSel && document.getElementById('vulnerability-modal')?.style.display === 'block') {
|
||||
const modalCur = modalSel.value || '';
|
||||
modalSel.innerHTML = buildVulnerabilityProjectOptionsHtml(modalCur);
|
||||
modalSel.value = modalCur;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('加载项目筛选列表失败', e);
|
||||
}
|
||||
}
|
||||
|
||||
function setVulnerabilityProjectFilter(projectId) {
|
||||
vulnerabilityFilters.project_id = projectId || '';
|
||||
const sel = document.getElementById('vulnerability-project-filter');
|
||||
if (sel) sel.value = projectId || '';
|
||||
applyVulnerabilityFilters();
|
||||
}
|
||||
|
||||
function setVulnerabilityIdFilter(vulnId) {
|
||||
vulnerabilityFilters.id = vulnId || '';
|
||||
const el = document.getElementById('vulnerability-exact-id-filter');
|
||||
if (el) el.value = vulnId || '';
|
||||
applyVulnerabilityFilters();
|
||||
}
|
||||
|
||||
window.refreshVulnerabilityProjectFilter = refreshVulnerabilityProjectFilter;
|
||||
window.setVulnerabilityProjectFilter = setVulnerabilityProjectFilter;
|
||||
window.setVulnerabilityIdFilter = setVulnerabilityIdFilter;
|
||||
window.bindVulnerabilityProject = bindVulnerabilityProject;
|
||||
window.buildVulnerabilityProjectOptionsHtml = buildVulnerabilityProjectOptionsHtml;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user