From 2acf43c454039a65a42bffa7981b2462669d4ad4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=85=AC=E6=98=8E?=
<83812544+Ed1s0nZ@users.noreply.github.com>
Date: Tue, 28 Apr 2026 01:19:01 +0800
Subject: [PATCH] Add files via upload
---
web/static/css/style.css | 78 +++++++--
web/static/i18n/en-US.json | 55 ++++++-
web/static/i18n/zh-CN.json | 55 ++++++-
web/static/js/vulnerability.js | 293 ++++++++++++++++++++++-----------
web/templates/index.html | 24 +--
5 files changed, 386 insertions(+), 119 deletions(-)
diff --git a/web/static/css/style.css b/web/static/css/style.css
index ada4457d..1ef67f03 100644
--- a/web/static/css/style.css
+++ b/web/static/css/style.css
@@ -13575,29 +13575,87 @@ header {
.vulnerability-details {
display: grid;
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
- gap: 12px;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 14px 16px;
margin-bottom: 16px;
padding: 12px;
background: var(--bg-secondary);
border-radius: 6px;
+ align-items: start;
}
-.detail-item {
+/* 元数据条数为奇数时,最后一项占满一行,长 URL/队列 ID 更易读 */
+.vulnerability-details .vuln-detail-field:last-child:nth-child(odd) {
+ grid-column: 1 / -1;
+}
+
+@media (max-width: 768px) {
+ .vulnerability-details {
+ grid-template-columns: 1fr;
+ }
+
+ .vulnerability-details .vuln-detail-field:last-child:nth-child(odd) {
+ grid-column: auto;
+ }
+}
+
+/* 漏洞详情字段:标签与值分行,长 ID/URL 可换行、可选中复制 */
+.vuln-detail-field {
+ min-width: 0;
font-size: 0.875rem;
}
-.detail-item strong {
+.vuln-detail-field__label {
color: var(--text-secondary);
- margin-right: 4px;
+ font-weight: 600;
+ font-size: 0.75rem;
+ margin-bottom: 6px;
+ text-transform: none;
+ letter-spacing: normal;
}
-.detail-item code {
+.vuln-detail-field__row {
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
+ min-width: 0;
+}
+
+.vuln-detail-field-value {
+ flex: 1;
+ min-width: 0;
+ margin: 0;
+ padding: 8px 10px;
+ border-radius: 6px;
background: var(--bg-tertiary);
- padding: 2px 6px;
- border-radius: 4px;
- font-size: 0.8rem;
- font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+ border: 1px solid var(--border-color);
+ font-size: 0.8125rem;
+ line-height: 1.45;
+ word-break: break-word;
+ overflow-wrap: anywhere;
+ white-space: pre-wrap;
+ user-select: text;
+ -webkit-user-select: text;
+ color: var(--text-primary);
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
+}
+
+.vuln-detail-field__copy {
+ flex-shrink: 0;
+ margin-top: 2px;
+ padding: 6px;
+ line-height: 0;
+ border-radius: 6px;
+ color: var(--text-secondary);
+ border: 1px solid transparent;
+ background: transparent;
+ cursor: pointer;
+}
+
+.vuln-detail-field__copy:hover {
+ color: var(--accent-color);
+ background: var(--bg-primary);
+ border-color: var(--border-color);
}
.vulnerability-proof,
diff --git a/web/static/i18n/en-US.json b/web/static/i18n/en-US.json
index c91db344..7a59ffae 100644
--- a/web/static/i18n/en-US.json
+++ b/web/static/i18n/en-US.json
@@ -1312,6 +1312,12 @@
"clear": "Clear",
"vulnId": "Vuln ID",
"conversationId": "Conversation ID",
+ "taskOrQueueId": "Task / queue ID",
+ "filterTaskOrQueue": "Filter by task or queue ID",
+ "conversationTag": "Conversation tag",
+ "filterConversationTag": "Filter by conversation tag",
+ "taskTag": "Task tag",
+ "filterTaskTag": "Filter by task tag",
"severity": "Severity",
"status": "Status",
"statusOpen": "Open",
@@ -1321,7 +1327,31 @@
"searchVulnId": "Search vuln ID",
"filterConversation": "Filter by conversation",
"loading": "Loading...",
- "noRecords": "No vulnerability records"
+ "loadListFailed": "Failed to load",
+ "noRecords": "No vulnerability records",
+ "batchExport": "Batch export",
+ "downloadMarkdownTitle": "Download Markdown",
+ "exportNoResults": "No vulnerabilities match the current filters",
+ "exportStarted": "Started downloading {{count}} file(s)",
+ "exportFailed": "Export failed",
+ "saveRequiredFields": "Please fill in conversation ID, title, and severity",
+ "saveFailed": "Save failed",
+ "fetchFailed": "Failed to fetch vulnerability",
+ "deleteFailed": "Delete failed",
+ "detailVulnId": "Vuln ID",
+ "detailType": "Type",
+ "detailTarget": "Target",
+ "detailConversationId": "Conversation ID",
+ "detailTaskId": "Task ID",
+ "detailTaskQueueId": "Task queue ID",
+ "detailConversationTag": "Conversation tag",
+ "detailTaskTag": "Task tag",
+ "detailProof": "Proof",
+ "detailImpact": "Impact",
+ "detailRecommendation": "Remediation",
+ "downloadOkTitle": "Downloaded",
+ "exportFailedMessage": "Export failed",
+ "downloadFailed": "Download failed"
},
"tasksPage": {
"statusFilter": "Status filter",
@@ -1767,6 +1797,10 @@
"vulnerabilityModal": {
"conversationId": "Conversation ID",
"conversationIdPlaceholder": "Enter conversation ID",
+ "conversationTag": "Conversation tag",
+ "conversationTagPlaceholder": "e.g. engagement A, weekly report",
+ "taskTag": "Task tag",
+ "taskTagPlaceholder": "e.g. batch scan Q2, retest",
"title": "Title",
"titlePlaceholder": "Vulnerability title",
"description": "Description",
@@ -1794,6 +1828,25 @@
"recommendation": "Recommendation",
"recommendationPlaceholder": "Remediation"
},
+ "vulnerabilityMd": {
+ "headingBasic": "Basic information",
+ "labelId": "Vulnerability ID",
+ "labelSeverity": "Severity",
+ "labelStatus": "Status",
+ "labelType": "Type",
+ "labelTarget": "Target",
+ "labelConversationId": "Conversation ID",
+ "labelTaskId": "Task ID",
+ "labelTaskQueueId": "Task queue ID",
+ "labelConversationTag": "Conversation tag",
+ "labelTaskTag": "Task tag",
+ "labelCreated": "Created at",
+ "labelUpdated": "Updated at",
+ "headingDescription": "Description",
+ "headingProof": "Proof (POC)",
+ "headingImpact": "Impact",
+ "headingRecommendation": "Remediation"
+ },
"roleModal": {
"addRole": "Add role",
"editRole": "Edit role",
diff --git a/web/static/i18n/zh-CN.json b/web/static/i18n/zh-CN.json
index 2da9a2e8..abc79e61 100644
--- a/web/static/i18n/zh-CN.json
+++ b/web/static/i18n/zh-CN.json
@@ -1312,6 +1312,12 @@
"clear": "清除",
"vulnId": "漏洞ID",
"conversationId": "会话ID",
+ "taskOrQueueId": "任务ID/队列ID",
+ "filterTaskOrQueue": "筛选任务ID或队列ID",
+ "conversationTag": "对话标签",
+ "filterConversationTag": "筛选对话标签",
+ "taskTag": "任务标签",
+ "filterTaskTag": "筛选任务标签",
"severity": "严重程度",
"status": "状态",
"statusOpen": "待处理",
@@ -1321,7 +1327,31 @@
"searchVulnId": "搜索漏洞ID",
"filterConversation": "筛选特定会话",
"loading": "加载中...",
- "noRecords": "暂无漏洞记录"
+ "loadListFailed": "加载失败",
+ "noRecords": "暂无漏洞记录",
+ "batchExport": "批量导出",
+ "downloadMarkdownTitle": "下载 Markdown",
+ "exportNoResults": "当前筛选条件下无可导出漏洞",
+ "exportStarted": "已开始下载 {{count}} 份报告",
+ "exportFailed": "导出失败",
+ "saveRequiredFields": "请填写必填字段:会话ID、标题和严重程度",
+ "saveFailed": "保存失败",
+ "fetchFailed": "获取漏洞失败",
+ "deleteFailed": "删除失败",
+ "detailVulnId": "漏洞ID",
+ "detailType": "类型",
+ "detailTarget": "目标",
+ "detailConversationId": "会话ID",
+ "detailTaskId": "任务ID",
+ "detailTaskQueueId": "任务队列ID",
+ "detailConversationTag": "对话标签",
+ "detailTaskTag": "任务标签",
+ "detailProof": "证明",
+ "detailImpact": "影响",
+ "detailRecommendation": "修复建议",
+ "downloadOkTitle": "下载成功",
+ "exportFailedMessage": "导出失败",
+ "downloadFailed": "下载失败"
},
"tasksPage": {
"statusFilter": "状态筛选",
@@ -1767,6 +1797,10 @@
"vulnerabilityModal": {
"conversationId": "会话ID",
"conversationIdPlaceholder": "输入会话ID",
+ "conversationTag": "对话标签",
+ "conversationTagPlaceholder": "如:红队演练A、客户A周报",
+ "taskTag": "任务标签",
+ "taskTagPlaceholder": "如:批量扫描Q2、专项复测",
"title": "标题",
"titlePlaceholder": "漏洞标题",
"description": "描述",
@@ -1794,6 +1828,25 @@
"recommendation": "修复建议",
"recommendationPlaceholder": "修复建议"
},
+ "vulnerabilityMd": {
+ "headingBasic": "基本信息",
+ "labelId": "漏洞ID",
+ "labelSeverity": "严重程度",
+ "labelStatus": "状态",
+ "labelType": "类型",
+ "labelTarget": "目标",
+ "labelConversationId": "会话ID",
+ "labelTaskId": "任务ID",
+ "labelTaskQueueId": "任务队列ID",
+ "labelConversationTag": "对话标签",
+ "labelTaskTag": "任务标签",
+ "labelCreated": "创建时间",
+ "labelUpdated": "更新时间",
+ "headingDescription": "描述",
+ "headingProof": "证明(POC)",
+ "headingImpact": "影响",
+ "headingRecommendation": "修复建议"
+ },
"roleModal": {
"addRole": "添加角色",
"editRole": "编辑角色",
diff --git a/web/static/js/vulnerability.js b/web/static/js/vulnerability.js
index 1628c447..99f94f1f 100644
--- a/web/static/js/vulnerability.js
+++ b/web/static/js/vulnerability.js
@@ -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 = '
加载中...
';
+ listContainer.innerHTML = `${escapeHtml(vulnT('vulnerabilityPage.loading'))}
`;
try {
// 检查apiFetch是否可用
@@ -160,7 +198,7 @@ async function loadVulnerabilities(page = null) {
renderVulnerabilityPagination();
} catch (error) {
console.error('加载漏洞列表失败:', error);
- listContainer.innerHTML = `加载失败: ${error.message}
`;
+ listContainer.innerHTML = `${escapeHtml(vulnT('vulnerabilityPage.loadListFailed'))}: ${escapeHtml(error.message)}
`;
}
}
@@ -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 `
@@ -226,20 +254,20 @@ function renderVulnerabilities(vulnerabilities) {
-
`;
}).join('');
listContainer.innerHTML = html;
+ if (typeof window.applyTranslations === 'function') {
+ window.applyTranslations(listContainer);
+ }
}
// 渲染分页控件
@@ -293,9 +324,9 @@ function renderVulnerabilityPagination() {
// 左侧:显示范围信息和每页数量选择器(参考Skills样式)
paginationHTML += `
';
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
+ ? `${escapeHtml(s)}`
+ : `${escapeHtml(s)}`;
+ const copyBtn = `
+
+`;
+ return `
+
${escapeHtml(label)}
+
${valueEl}${copyBtn}
+
`;
+}
+
+// 将漏洞格式化为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();
+ }
+});
diff --git a/web/templates/index.html b/web/templates/index.html
index cacc3193..a9a0f895 100644
--- a/web/templates/index.html
+++ b/web/templates/index.html
@@ -1098,16 +1098,16 @@
筛选
清除
- 导出报告
+ 批量导出
@@ -2613,12 +2613,12 @@
-
-
+
+
-
-
+
+
@@ -2838,7 +2838,7 @@
-
+