mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-08-10 05:00:41 +02:00
Add files via upload
This commit is contained in:
+198
-95
@@ -1,5 +1,43 @@
|
||||
// 漏洞管理相关功能
|
||||
|
||||
function vulnT(key, opts) {
|
||||
if (typeof window.t === 'function') {
|
||||
return window.t(key, opts);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
function vulnDateLocale() {
|
||||
try {
|
||||
const lang = (window.__locale || '').toLowerCase();
|
||||
if (lang.indexOf('zh') === 0) {
|
||||
return 'zh-CN';
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
return 'en-US';
|
||||
}
|
||||
|
||||
function vulnSeverityLabel(code) {
|
||||
const m = {
|
||||
critical: 'dashboard.severityCritical',
|
||||
high: 'dashboard.severityHigh',
|
||||
medium: 'dashboard.severityMedium',
|
||||
low: 'dashboard.severityLow',
|
||||
info: 'dashboard.severityInfo'
|
||||
};
|
||||
return m[code] ? vulnT(m[code]) : code;
|
||||
}
|
||||
|
||||
function vulnStatusLabel(code) {
|
||||
const m = {
|
||||
open: 'vulnerabilityPage.statusOpen',
|
||||
confirmed: 'vulnerabilityPage.statusConfirmed',
|
||||
fixed: 'vulnerabilityPage.statusFixed',
|
||||
false_positive: 'vulnerabilityPage.statusFalsePositive'
|
||||
};
|
||||
return m[code] ? vulnT(m[code]) : code;
|
||||
}
|
||||
|
||||
// 从localStorage读取每页显示数量,默认为20
|
||||
const getVulnerabilityPageSize = () => {
|
||||
const saved = localStorage.getItem('vulnerabilityPageSize');
|
||||
@@ -85,7 +123,7 @@ function updateVulnerabilityStats(stats) {
|
||||
// 加载漏洞列表
|
||||
async function loadVulnerabilities(page = null) {
|
||||
const listContainer = document.getElementById('vulnerabilities-list');
|
||||
listContainer.innerHTML = '<div class="loading-spinner">加载中...</div>';
|
||||
listContainer.innerHTML = `<div class="loading-spinner">${escapeHtml(vulnT('vulnerabilityPage.loading'))}</div>`;
|
||||
|
||||
try {
|
||||
// 检查apiFetch是否可用
|
||||
@@ -160,7 +198,7 @@ async function loadVulnerabilities(page = null) {
|
||||
renderVulnerabilityPagination();
|
||||
} catch (error) {
|
||||
console.error('加载漏洞列表失败:', error);
|
||||
listContainer.innerHTML = `<div class="error-message">加载失败: ${error.message}</div>`;
|
||||
listContainer.innerHTML = `<div class="error-message">${escapeHtml(vulnT('vulnerabilityPage.loadListFailed'))}: ${escapeHtml(error.message)}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,22 +230,12 @@ function renderVulnerabilities(vulnerabilities) {
|
||||
|
||||
const html = vulnerabilities.map(vuln => {
|
||||
const severityClass = `severity-${vuln.severity}`;
|
||||
const severityText = {
|
||||
'critical': '严重',
|
||||
'high': '高危',
|
||||
'medium': '中危',
|
||||
'low': '低危',
|
||||
'info': '信息'
|
||||
}[vuln.severity] || vuln.severity;
|
||||
|
||||
const statusText = {
|
||||
'open': '待处理',
|
||||
'confirmed': '已确认',
|
||||
'fixed': '已修复',
|
||||
'false_positive': '误报'
|
||||
}[vuln.status] || vuln.status;
|
||||
|
||||
const createdDate = new Date(vuln.created_at).toLocaleString('zh-CN');
|
||||
const severityText = vulnSeverityLabel(vuln.severity);
|
||||
const statusText = vulnStatusLabel(vuln.status);
|
||||
const createdDate = new Date(vuln.created_at).toLocaleString(vulnDateLocale());
|
||||
const dlTitle = escapeHtml(vulnT('vulnerabilityPage.downloadMarkdownTitle'));
|
||||
const editTitle = escapeHtml(vulnT('common.edit'));
|
||||
const deleteTitle = escapeHtml(vulnT('common.delete'));
|
||||
|
||||
return `
|
||||
<div class="vulnerability-card ${severityClass}">
|
||||
@@ -226,20 +254,20 @@ function renderVulnerabilities(vulnerabilities) {
|
||||
</div>
|
||||
</div>
|
||||
<div class="vulnerability-actions" onclick="event.stopPropagation();">
|
||||
<button class="btn-ghost" onclick="downloadVulnerabilityAsMarkdown('${vuln.id}', event)" title="下载Markdown">
|
||||
<button class="btn-ghost" onclick="downloadVulnerabilityAsMarkdown('${vuln.id}', event)" title="${dlTitle}">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<polyline points="7 10 12 15 17 10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<line x1="12" y1="15" x2="12" y2="3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn-ghost" onclick="editVulnerability('${vuln.id}')" title="编辑">
|
||||
<button class="btn-ghost" onclick="editVulnerability('${vuln.id}')" title="${editTitle}">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn-ghost" onclick="deleteVulnerability('${vuln.id}')" title="删除">
|
||||
<button class="btn-ghost" onclick="deleteVulnerability('${vuln.id}')" title="${deleteTitle}">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6h14z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
@@ -249,24 +277,27 @@ function renderVulnerabilities(vulnerabilities) {
|
||||
<div class="vulnerability-content" id="content-${vuln.id}" style="display: none;">
|
||||
${vuln.description ? `<div class="vulnerability-description">${escapeHtml(vuln.description)}</div>` : ''}
|
||||
<div class="vulnerability-details">
|
||||
<div class="detail-item"><strong>漏洞ID:</strong> <code>${escapeHtml(vuln.id)}</code></div>
|
||||
${vuln.type ? `<div class="detail-item"><strong>类型:</strong> ${escapeHtml(vuln.type)}</div>` : ''}
|
||||
${vuln.target ? `<div class="detail-item"><strong>目标:</strong> ${escapeHtml(vuln.target)}</div>` : ''}
|
||||
<div class="detail-item"><strong>会话ID:</strong> <code>${escapeHtml(vuln.conversation_id)}</code></div>
|
||||
${vuln.task_id ? `<div class="detail-item"><strong>任务ID:</strong> <code>${escapeHtml(vuln.task_id)}</code></div>` : ''}
|
||||
${vuln.task_queue_id ? `<div class="detail-item"><strong>任务队列ID:</strong> <code>${escapeHtml(vuln.task_queue_id)}</code></div>` : ''}
|
||||
${vuln.conversation_tag ? `<div class="detail-item"><strong>对话标签:</strong> ${escapeHtml(vuln.conversation_tag)}</div>` : ''}
|
||||
${vuln.task_tag ? `<div class="detail-item"><strong>任务标签:</strong> ${escapeHtml(vuln.task_tag)}</div>` : ''}
|
||||
${vulnDetailField(vulnT('vulnerabilityPage.detailVulnId'), vuln.id, true)}
|
||||
${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)}
|
||||
${vuln.task_id ? vulnDetailField(vulnT('vulnerabilityPage.detailTaskId'), vuln.task_id, true) : ''}
|
||||
${vuln.task_queue_id ? vulnDetailField(vulnT('vulnerabilityPage.detailTaskQueueId'), vuln.task_queue_id, true) : ''}
|
||||
${vuln.conversation_tag ? vulnDetailField(vulnT('vulnerabilityPage.detailConversationTag'), vuln.conversation_tag, false) : ''}
|
||||
${vuln.task_tag ? vulnDetailField(vulnT('vulnerabilityPage.detailTaskTag'), vuln.task_tag, false) : ''}
|
||||
</div>
|
||||
${vuln.proof ? `<div class="vulnerability-proof"><strong>证明:</strong><pre>${escapeHtml(vuln.proof)}</pre></div>` : ''}
|
||||
${vuln.impact ? `<div class="vulnerability-impact"><strong>影响:</strong> ${escapeHtml(vuln.impact)}</div>` : ''}
|
||||
${vuln.recommendation ? `<div class="vulnerability-recommendation"><strong>修复建议:</strong> ${escapeHtml(vuln.recommendation)}</div>` : ''}
|
||||
${vuln.proof ? `<div class="vulnerability-proof"><strong>${escapeHtml(vulnT('vulnerabilityPage.detailProof'))}:</strong><pre>${escapeHtml(vuln.proof)}</pre></div>` : ''}
|
||||
${vuln.impact ? `<div class="vulnerability-impact"><strong>${escapeHtml(vulnT('vulnerabilityPage.detailImpact'))}:</strong> ${escapeHtml(vuln.impact)}</div>` : ''}
|
||||
${vuln.recommendation ? `<div class="vulnerability-recommendation"><strong>${escapeHtml(vulnT('vulnerabilityPage.detailRecommendation'))}:</strong> ${escapeHtml(vuln.recommendation)}</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
listContainer.innerHTML = html;
|
||||
if (typeof window.applyTranslations === 'function') {
|
||||
window.applyTranslations(listContainer);
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染分页控件
|
||||
@@ -293,9 +324,9 @@ function renderVulnerabilityPagination() {
|
||||
// 左侧:显示范围信息和每页数量选择器(参考Skills样式)
|
||||
paginationHTML += `
|
||||
<div class="pagination-info">
|
||||
<span>显示 ${start}-${end} / 共 ${total} 条</span>
|
||||
<span>${escapeHtml(vulnT('skillsPage.paginationShow', { start, end, total }))}</span>
|
||||
<label class="pagination-page-size">
|
||||
每页显示
|
||||
${escapeHtml(vulnT('skillsPage.perPageLabel'))}
|
||||
<select id="vulnerability-page-size-pagination" onchange="changeVulnerabilityPageSize()">
|
||||
<option value="10" ${pageSize === 10 ? 'selected' : ''}>10</option>
|
||||
<option value="20" ${pageSize === 20 ? 'selected' : ''}>20</option>
|
||||
@@ -309,17 +340,20 @@ function renderVulnerabilityPagination() {
|
||||
// 右侧:分页按钮(参考Skills样式:首页、上一页、第X/Y页、下一页、末页)
|
||||
paginationHTML += `
|
||||
<div class="pagination-controls">
|
||||
<button class="btn-secondary" onclick="loadVulnerabilities(1)" ${currentPage === 1 || total === 0 ? 'disabled' : ''}>首页</button>
|
||||
<button class="btn-secondary" onclick="loadVulnerabilities(${currentPage - 1})" ${currentPage === 1 || total === 0 ? 'disabled' : ''}>上一页</button>
|
||||
<span class="pagination-page">第 ${currentPage} / ${totalPages || 1} 页</span>
|
||||
<button class="btn-secondary" onclick="loadVulnerabilities(${currentPage + 1})" ${currentPage >= totalPages || total === 0 ? 'disabled' : ''}>下一页</button>
|
||||
<button class="btn-secondary" onclick="loadVulnerabilities(${totalPages || 1})" ${currentPage >= totalPages || total === 0 ? 'disabled' : ''}>末页</button>
|
||||
<button class="btn-secondary" onclick="loadVulnerabilities(1)" ${currentPage === 1 || total === 0 ? 'disabled' : ''}>${escapeHtml(vulnT('skillsPage.firstPage'))}</button>
|
||||
<button class="btn-secondary" onclick="loadVulnerabilities(${currentPage - 1})" ${currentPage === 1 || total === 0 ? 'disabled' : ''}>${escapeHtml(vulnT('skillsPage.prevPage'))}</button>
|
||||
<span class="pagination-page">${escapeHtml(vulnT('skillsPage.pageOf', { current: currentPage, total: totalPages || 1 }))}</span>
|
||||
<button class="btn-secondary" onclick="loadVulnerabilities(${currentPage + 1})" ${currentPage >= totalPages || total === 0 ? 'disabled' : ''}>${escapeHtml(vulnT('skillsPage.nextPage'))}</button>
|
||||
<button class="btn-secondary" onclick="loadVulnerabilities(${totalPages || 1})" ${currentPage >= totalPages || total === 0 ? 'disabled' : ''}>${escapeHtml(vulnT('skillsPage.lastPage'))}</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
paginationHTML += '</div>';
|
||||
|
||||
paginationContainer.innerHTML = paginationHTML;
|
||||
if (typeof window.applyTranslations === 'function') {
|
||||
window.applyTranslations(paginationContainer);
|
||||
}
|
||||
}
|
||||
|
||||
// 改变每页显示数量
|
||||
@@ -350,7 +384,7 @@ async function changeVulnerabilityPageSize() {
|
||||
// 显示添加漏洞模态框
|
||||
function showAddVulnerabilityModal() {
|
||||
currentVulnerabilityId = null;
|
||||
document.getElementById('vulnerability-modal-title').textContent = (typeof window.t === 'function' ? window.t('vulnerability.addVuln') : '添加漏洞');
|
||||
document.getElementById('vulnerability-modal-title').textContent = vulnT('vulnerability.addVuln');
|
||||
|
||||
// 清空表单
|
||||
document.getElementById('vulnerability-conversation-id').value = '';
|
||||
@@ -373,11 +407,11 @@ function showAddVulnerabilityModal() {
|
||||
async function editVulnerability(id) {
|
||||
try {
|
||||
const response = await apiFetch(`/api/vulnerabilities/${id}`);
|
||||
if (!response.ok) throw new Error('获取漏洞失败');
|
||||
if (!response.ok) throw new Error(vulnT('vulnerabilityPage.fetchFailed'));
|
||||
|
||||
const vuln = await response.json();
|
||||
currentVulnerabilityId = id;
|
||||
document.getElementById('vulnerability-modal-title').textContent = (typeof window.t === 'function' ? window.t('vulnerability.editVuln') : '编辑漏洞');
|
||||
document.getElementById('vulnerability-modal-title').textContent = vulnT('vulnerability.editVuln');
|
||||
|
||||
// 填充表单
|
||||
document.getElementById('vulnerability-conversation-id').value = vuln.conversation_id || '';
|
||||
@@ -396,7 +430,7 @@ async function editVulnerability(id) {
|
||||
document.getElementById('vulnerability-modal').style.display = 'block';
|
||||
} catch (error) {
|
||||
console.error('加载漏洞失败:', error);
|
||||
alert('加载漏洞失败: ' + error.message);
|
||||
alert(vulnT('vulnerability.loadFailed') + ': ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,7 +441,7 @@ async function saveVulnerability() {
|
||||
const severity = document.getElementById('vulnerability-severity').value;
|
||||
|
||||
if (!conversationId || !title || !severity) {
|
||||
alert('请填写必填字段:会话ID、标题和严重程度');
|
||||
alert(vulnT('vulnerabilityPage.saveRequiredFields'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -442,7 +476,7 @@ async function saveVulnerability() {
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || '保存失败');
|
||||
throw new Error(error.error || vulnT('vulnerabilityPage.saveFailed'));
|
||||
}
|
||||
|
||||
closeVulnerabilityModal();
|
||||
@@ -452,13 +486,13 @@ async function saveVulnerability() {
|
||||
loadVulnerabilities();
|
||||
} catch (error) {
|
||||
console.error('保存漏洞失败:', error);
|
||||
alert('保存漏洞失败: ' + error.message);
|
||||
alert(vulnT('vulnerabilityPage.saveFailed') + ': ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 删除漏洞
|
||||
async function deleteVulnerability(id) {
|
||||
if (!confirm('确定要删除此漏洞吗?')) {
|
||||
if (!confirm(vulnT('vulnerability.deleteConfirm'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -467,7 +501,7 @@ async function deleteVulnerability(id) {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('删除失败');
|
||||
if (!response.ok) throw new Error(vulnT('vulnerabilityPage.deleteFailed'));
|
||||
|
||||
loadVulnerabilityStats();
|
||||
// 删除后,如果当前页没有数据了,回到上一页
|
||||
@@ -480,7 +514,7 @@ async function deleteVulnerability(id) {
|
||||
loadVulnerabilities();
|
||||
} catch (error) {
|
||||
console.error('删除漏洞失败:', error);
|
||||
alert('删除漏洞失败: ' + error.message);
|
||||
alert(vulnT('vulnerabilityPage.deleteFailed') + ': ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,68 +597,130 @@ function escapeHtml(text) {
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// 将漏洞格式化为Markdown
|
||||
/** 复制详情字段(编码由 encodeURIComponent 传入,避免引号截断) */
|
||||
function vulnerabilityCopyEncoded(evt, encoded) {
|
||||
if (evt && evt.stopPropagation) {
|
||||
evt.stopPropagation();
|
||||
}
|
||||
let text = '';
|
||||
try {
|
||||
text = decodeURIComponent(encoded);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
const done = () => {
|
||||
if (evt && evt.target && evt.target.closest) {
|
||||
const btn = evt.target.closest('.vuln-detail-field__copy');
|
||||
if (btn) {
|
||||
const t0 = btn.getAttribute('title') || '';
|
||||
btn.setAttribute('title', vulnT('common.copied'));
|
||||
setTimeout(() => btn.setAttribute('title', t0), 1600);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
|
||||
navigator.clipboard.writeText(text).then(done).catch(() => {
|
||||
try {
|
||||
const ta = document.createElement('textarea');
|
||||
ta.value = text;
|
||||
ta.style.position = 'fixed';
|
||||
ta.style.left = '-9999px';
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(ta);
|
||||
done();
|
||||
} catch (err) {
|
||||
console.error('copy failed', err);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
const ta = document.createElement('textarea');
|
||||
ta.value = text;
|
||||
ta.style.position = 'fixed';
|
||||
ta.style.left = '-9999px';
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(ta);
|
||||
done();
|
||||
} catch (err) {
|
||||
console.error('copy failed', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function vulnDetailField(label, value, asCode) {
|
||||
if (value === undefined || value === null || String(value) === '') {
|
||||
return '';
|
||||
}
|
||||
const s = String(value);
|
||||
const enc = encodeURIComponent(s);
|
||||
const copyTitle = escapeHtml(vulnT('common.copy'));
|
||||
const valueEl = asCode
|
||||
? `<code class="vuln-detail-field-value">${escapeHtml(s)}</code>`
|
||||
: `<span class="vuln-detail-field-value">${escapeHtml(s)}</span>`;
|
||||
const copyBtn = `<button type="button" class="vuln-detail-field__copy" onclick="vulnerabilityCopyEncoded(event, '${enc}')" title="${copyTitle}" aria-label="${copyTitle}">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
||||
</button>`;
|
||||
return `<div class="vuln-detail-field">
|
||||
<div class="vuln-detail-field__label">${escapeHtml(label)}</div>
|
||||
<div class="vuln-detail-field__row">${valueEl}${copyBtn}</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// 将漏洞格式化为Markdown(章节标题随界面语言)
|
||||
function formatVulnerabilityAsMarkdown(vuln) {
|
||||
const severityText = {
|
||||
'critical': '严重',
|
||||
'high': '高危',
|
||||
'medium': '中危',
|
||||
'low': '低危',
|
||||
'info': '信息'
|
||||
}[vuln.severity] || vuln.severity;
|
||||
|
||||
const statusText = {
|
||||
'open': '待处理',
|
||||
'confirmed': '已确认',
|
||||
'fixed': '已修复',
|
||||
'false_positive': '误报'
|
||||
}[vuln.status] || vuln.status;
|
||||
|
||||
const createdDate = new Date(vuln.created_at).toLocaleString('zh-CN');
|
||||
const updatedDate = new Date(vuln.updated_at).toLocaleString('zh-CN');
|
||||
const severityText = vulnSeverityLabel(vuln.severity);
|
||||
const statusText = vulnStatusLabel(vuln.status);
|
||||
const loc = vulnDateLocale();
|
||||
const createdDate = new Date(vuln.created_at).toLocaleString(loc);
|
||||
const updatedDate = new Date(vuln.updated_at).toLocaleString(loc);
|
||||
const L = (k) => vulnT('vulnerabilityMd.' + k);
|
||||
|
||||
let markdown = `# ${vuln.title}\n\n`;
|
||||
|
||||
markdown += `## 基本信息\n\n`;
|
||||
markdown += `- **漏洞ID**: \`${vuln.id}\`\n`;
|
||||
markdown += `- **严重程度**: ${severityText}\n`;
|
||||
markdown += `- **状态**: ${statusText}\n`;
|
||||
|
||||
markdown += `## ${L('headingBasic')}\n\n`;
|
||||
markdown += `- **${L('labelId')}**: \`${vuln.id}\`\n`;
|
||||
markdown += `- **${L('labelSeverity')}**: ${severityText}\n`;
|
||||
markdown += `- **${L('labelStatus')}**: ${statusText}\n`;
|
||||
if (vuln.type) {
|
||||
markdown += `- **类型**: ${vuln.type}\n`;
|
||||
markdown += `- **${L('labelType')}**: ${vuln.type}\n`;
|
||||
}
|
||||
if (vuln.target) {
|
||||
markdown += `- **目标**: ${vuln.target}\n`;
|
||||
markdown += `- **${L('labelTarget')}**: ${vuln.target}\n`;
|
||||
}
|
||||
markdown += `- **会话ID**: \`${vuln.conversation_id}\`\n`;
|
||||
markdown += `- **${L('labelConversationId')}**: \`${vuln.conversation_id}\`\n`;
|
||||
if (vuln.task_id) {
|
||||
markdown += `- **任务ID**: \`${vuln.task_id}\`\n`;
|
||||
markdown += `- **${L('labelTaskId')}**: \`${vuln.task_id}\`\n`;
|
||||
}
|
||||
if (vuln.task_queue_id) {
|
||||
markdown += `- **任务队列ID**: \`${vuln.task_queue_id}\`\n`;
|
||||
markdown += `- **${L('labelTaskQueueId')}**: \`${vuln.task_queue_id}\`\n`;
|
||||
}
|
||||
if (vuln.conversation_tag) {
|
||||
markdown += `- **对话标签**: ${vuln.conversation_tag}\n`;
|
||||
markdown += `- **${L('labelConversationTag')}**: ${vuln.conversation_tag}\n`;
|
||||
}
|
||||
if (vuln.task_tag) {
|
||||
markdown += `- **任务标签**: ${vuln.task_tag}\n`;
|
||||
markdown += `- **${L('labelTaskTag')}**: ${vuln.task_tag}\n`;
|
||||
}
|
||||
markdown += `- **创建时间**: ${createdDate}\n`;
|
||||
markdown += `- **更新时间**: ${updatedDate}\n\n`;
|
||||
markdown += `- **${L('labelCreated')}**: ${createdDate}\n`;
|
||||
markdown += `- **${L('labelUpdated')}**: ${updatedDate}\n\n`;
|
||||
|
||||
if (vuln.description) {
|
||||
markdown += `## 描述\n\n${vuln.description}\n\n`;
|
||||
markdown += `## ${L('headingDescription')}\n\n${vuln.description}\n\n`;
|
||||
}
|
||||
|
||||
if (vuln.proof) {
|
||||
markdown += `## 证明(POC)\n\n\`\`\`\n${vuln.proof}\n\`\`\`\n\n`;
|
||||
markdown += `## ${L('headingProof')}\n\n\`\`\`\n${vuln.proof}\n\`\`\`\n\n`;
|
||||
}
|
||||
|
||||
if (vuln.impact) {
|
||||
markdown += `## 影响\n\n${vuln.impact}\n\n`;
|
||||
markdown += `## ${L('headingImpact')}\n\n${vuln.impact}\n\n`;
|
||||
}
|
||||
|
||||
if (vuln.recommendation) {
|
||||
markdown += `## 修复建议\n\n${vuln.recommendation}\n\n`;
|
||||
markdown += `## ${L('headingRecommendation')}\n\n${vuln.recommendation}\n\n`;
|
||||
}
|
||||
|
||||
return markdown;
|
||||
@@ -660,24 +756,24 @@ async function exportVulnerabilityReports() {
|
||||
params.set('group_by', 'conversation');
|
||||
const response = await apiFetch(`/api/vulnerabilities/export?${params.toString()}`);
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ error: '导出失败' }));
|
||||
throw new Error(error.error || '导出失败');
|
||||
const error = await response.json().catch(() => ({ error: vulnT('vulnerabilityPage.exportFailedMessage') }));
|
||||
throw new Error(error.error || vulnT('vulnerabilityPage.exportFailedMessage'));
|
||||
}
|
||||
const data = await response.json();
|
||||
const files = Array.isArray(data.files) ? data.files : [];
|
||||
if (!files.length) {
|
||||
alert('当前筛选条件下无可导出漏洞');
|
||||
alert(vulnT('vulnerabilityPage.exportNoResults'));
|
||||
return;
|
||||
}
|
||||
files.forEach((file, idx) => {
|
||||
setTimeout(() => triggerTextDownload(file.filename || `vulnerability-export-${idx + 1}.md`, file.content || ''), idx * 120);
|
||||
});
|
||||
if (files.length > 1) {
|
||||
alert(`已开始下载 ${files.length} 份报告`);
|
||||
alert(vulnT('vulnerabilityPage.exportStarted', { count: files.length }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('导出漏洞报告失败:', error);
|
||||
alert('导出漏洞报告失败: ' + error.message);
|
||||
alert(vulnT('vulnerabilityPage.exportFailed') + ': ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -687,7 +783,7 @@ async function downloadVulnerabilityAsMarkdown(id, event) {
|
||||
try {
|
||||
const response = await apiFetch(`/api/vulnerabilities/${id}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('获取漏洞失败');
|
||||
throw new Error(vulnT('vulnerabilityPage.fetchFailed'));
|
||||
}
|
||||
|
||||
const vuln = await response.json();
|
||||
@@ -721,8 +817,8 @@ async function downloadVulnerabilityAsMarkdown(id, event) {
|
||||
if (event && event.target) {
|
||||
const button = event.target.closest('button');
|
||||
if (button) {
|
||||
const originalTitle = button.title || '下载Markdown';
|
||||
button.title = '下载成功!';
|
||||
const originalTitle = button.title || vulnT('vulnerabilityPage.downloadMarkdownTitle');
|
||||
button.title = vulnT('vulnerabilityPage.downloadOkTitle');
|
||||
setTimeout(() => {
|
||||
button.title = originalTitle;
|
||||
}, 2000);
|
||||
@@ -730,7 +826,7 @@ async function downloadVulnerabilityAsMarkdown(id, event) {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('下载失败:', error);
|
||||
alert('下载失败: ' + error.message);
|
||||
alert(vulnT('vulnerabilityPage.downloadFailed') + ': ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -740,5 +836,12 @@ window.onclick = function(event) {
|
||||
if (event.target === modal) {
|
||||
closeVulnerabilityModal();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('languagechange', function () {
|
||||
const page = document.getElementById('page-vulnerabilities');
|
||||
if (page && page.classList.contains('active')) {
|
||||
loadVulnerabilities();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user