// 漏洞管理相关功能 // 从localStorage读取每页显示数量,默认为20 const getVulnerabilityPageSize = () => { const saved = localStorage.getItem('vulnerabilityPageSize'); return saved ? parseInt(saved, 10) : 20; }; let currentVulnerabilityId = null; let vulnerabilityFilters = { id: '', conversation_id: '', severity: '', status: '' }; let vulnerabilityPagination = { currentPage: 1, pageSize: getVulnerabilityPageSize(), total: 0, totalPages: 1 }; // 初始化漏洞管理页面 function initVulnerabilityPage() { // 从localStorage加载每页条数设置 vulnerabilityPagination.pageSize = getVulnerabilityPageSize(); loadVulnerabilityStats(); loadVulnerabilities(); } // 加载漏洞统计 async function loadVulnerabilityStats() { try { // 检查apiFetch是否可用 if (typeof apiFetch === 'undefined') { console.error('apiFetch未定义,请确保auth.js已加载'); throw new Error('apiFetch未定义'); } const params = new URLSearchParams(); if (vulnerabilityFilters.conversation_id) { params.append('conversation_id', vulnerabilityFilters.conversation_id); } const response = await apiFetch(`/api/vulnerabilities/stats?${params.toString()}`); if (!response.ok) { const errorText = await response.text(); console.error('获取统计失败:', response.status, errorText); throw new Error(`获取统计失败: ${response.status}`); } const stats = await response.json(); updateVulnerabilityStats(stats); } catch (error) { console.error('加载漏洞统计失败:', error); // 统计失败不影响列表显示,只重置统计为0 updateVulnerabilityStats(null); } } // 更新漏洞统计显示 function updateVulnerabilityStats(stats) { // 处理空值情况 if (!stats) { stats = { total: 0, by_severity: {}, by_status: {} }; } document.getElementById('stat-total').textContent = 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; } // 加载漏洞列表 async function loadVulnerabilities(page = null) { const listContainer = document.getElementById('vulnerabilities-list'); listContainer.innerHTML = '
加载中...
'; try { // 检查apiFetch是否可用 if (typeof apiFetch === 'undefined') { console.error('apiFetch未定义,请确保auth.js已加载'); throw new Error('apiFetch未定义'); } // 如果指定了页码,使用页码;否则使用当前页码 if (page !== null) { vulnerabilityPagination.currentPage = page; } const params = new URLSearchParams(); params.append('page', vulnerabilityPagination.currentPage.toString()); params.append('limit', vulnerabilityPagination.pageSize.toString()); if (vulnerabilityFilters.id) { params.append('id', vulnerabilityFilters.id); } if (vulnerabilityFilters.conversation_id) { params.append('conversation_id', vulnerabilityFilters.conversation_id); } if (vulnerabilityFilters.severity) { params.append('severity', vulnerabilityFilters.severity); } if (vulnerabilityFilters.status) { params.append('status', vulnerabilityFilters.status); } const response = await apiFetch(`/api/vulnerabilities?${params.toString()}`); if (!response.ok) { const errorText = await response.text(); console.error('获取漏洞列表失败:', response.status, errorText); throw new Error(`获取漏洞列表失败: ${response.status}`); } const data = await response.json(); // 判断响应格式:新格式(有total字段)还是旧格式(直接是数组) let vulnerabilities; if (Array.isArray(data)) { // 旧格式:直接是数组 vulnerabilities = data; // 使用数组长度作为总数(可能不准确,但至少能显示分页控件) vulnerabilityPagination.total = data.length; vulnerabilityPagination.totalPages = Math.max(1, Math.ceil(data.length / vulnerabilityPagination.pageSize)); console.warn('后端返回的是旧格式(数组),建议更新后端API以支持分页'); } else if ('vulnerabilities' in data) { // 新格式:包含分页信息的对象(vulnerabilities可能为null或数组) vulnerabilities = Array.isArray(data.vulnerabilities) ? data.vulnerabilities : []; vulnerabilityPagination.total = data.total || 0; vulnerabilityPagination.currentPage = data.page || vulnerabilityPagination.currentPage; vulnerabilityPagination.pageSize = data.page_size || vulnerabilityPagination.pageSize; vulnerabilityPagination.totalPages = data.total_pages || 1; } else { // 未知格式,尝试作为数组处理 vulnerabilities = []; console.error('未知的响应格式:', data); } renderVulnerabilities(vulnerabilities); renderVulnerabilityPagination(); } catch (error) { console.error('加载漏洞列表失败:', error); listContainer.innerHTML = `
加载失败: ${error.message}
`; } } // 渲染漏洞列表 function renderVulnerabilities(vulnerabilities) { const listContainer = document.getElementById('vulnerabilities-list'); // 处理空值情况(使用 data-i18n 以便语言切换时自动更新) if (!vulnerabilities || !Array.isArray(vulnerabilities)) { listContainer.innerHTML = '
暂无漏洞记录
'; if (typeof window.applyTranslations === 'function') { window.applyTranslations(listContainer); } return; } if (vulnerabilities.length === 0) { listContainer.innerHTML = '
暂无漏洞记录
'; if (typeof window.applyTranslations === 'function') { window.applyTranslations(listContainer); } // 清空分页信息 const paginationContainer = document.getElementById('vulnerability-pagination'); if (paginationContainer) { paginationContainer.innerHTML = ''; } return; } 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'); return `

${escapeHtml(vuln.title)}

${severityText} ${statusText} ${createdDate}
`; }).join(''); listContainer.innerHTML = html; } // 渲染分页控件 function renderVulnerabilityPagination() { const paginationContainer = document.getElementById('vulnerability-pagination'); if (!paginationContainer) { return; } const { currentPage, totalPages, total, pageSize } = vulnerabilityPagination; // 如果没有数据,不显示分页控件 if (total === 0) { paginationContainer.innerHTML = ''; return; } // 计算显示范围 const start = total === 0 ? 0 : (currentPage - 1) * pageSize + 1; const end = total === 0 ? 0 : Math.min(currentPage * pageSize, total); let paginationHTML = ''; paginationContainer.innerHTML = paginationHTML; } // 改变每页显示数量 async function changeVulnerabilityPageSize() { const pageSizeSelect = document.getElementById('vulnerability-page-size-pagination'); if (!pageSizeSelect) return; const newPageSize = parseInt(pageSizeSelect.value, 10); if (isNaN(newPageSize) || newPageSize < 1) { return; } // 保存到localStorage localStorage.setItem('vulnerabilityPageSize', newPageSize.toString()); // 更新分页配置 vulnerabilityPagination.pageSize = newPageSize; // 重新计算当前页(保持显示的数据范围尽可能接近) const currentStartItem = (vulnerabilityPagination.currentPage - 1) * vulnerabilityPagination.pageSize + 1; const newPage = Math.max(1, Math.floor((currentStartItem - 1) / newPageSize) + 1); vulnerabilityPagination.currentPage = newPage; // 重新加载数据 await loadVulnerabilities(); } // 显示添加漏洞模态框 function showAddVulnerabilityModal() { currentVulnerabilityId = null; document.getElementById('vulnerability-modal-title').textContent = (typeof window.t === 'function' ? window.t('vulnerability.addVuln') : '添加漏洞'); // 清空表单 document.getElementById('vulnerability-conversation-id').value = ''; document.getElementById('vulnerability-title').value = ''; document.getElementById('vulnerability-description').value = ''; document.getElementById('vulnerability-severity').value = ''; document.getElementById('vulnerability-status').value = 'open'; document.getElementById('vulnerability-type').value = ''; document.getElementById('vulnerability-target').value = ''; document.getElementById('vulnerability-proof').value = ''; document.getElementById('vulnerability-impact').value = ''; document.getElementById('vulnerability-recommendation').value = ''; document.getElementById('vulnerability-modal').style.display = 'block'; } // 编辑漏洞 async function editVulnerability(id) { try { const response = await apiFetch(`/api/vulnerabilities/${id}`); if (!response.ok) throw new Error('获取漏洞失败'); const vuln = await response.json(); currentVulnerabilityId = id; document.getElementById('vulnerability-modal-title').textContent = (typeof window.t === 'function' ? window.t('vulnerability.editVuln') : '编辑漏洞'); // 填充表单 document.getElementById('vulnerability-conversation-id').value = vuln.conversation_id || ''; document.getElementById('vulnerability-title').value = vuln.title || ''; document.getElementById('vulnerability-description').value = vuln.description || ''; document.getElementById('vulnerability-severity').value = vuln.severity || ''; document.getElementById('vulnerability-status').value = vuln.status || 'open'; document.getElementById('vulnerability-type').value = vuln.type || ''; document.getElementById('vulnerability-target').value = vuln.target || ''; document.getElementById('vulnerability-proof').value = vuln.proof || ''; document.getElementById('vulnerability-impact').value = vuln.impact || ''; document.getElementById('vulnerability-recommendation').value = vuln.recommendation || ''; document.getElementById('vulnerability-modal').style.display = 'block'; } catch (error) { console.error('加载漏洞失败:', error); alert('加载漏洞失败: ' + error.message); } } // 保存漏洞 async function saveVulnerability() { const conversationId = document.getElementById('vulnerability-conversation-id').value.trim(); const title = document.getElementById('vulnerability-title').value.trim(); const severity = document.getElementById('vulnerability-severity').value; if (!conversationId || !title || !severity) { alert('请填写必填字段:会话ID、标题和严重程度'); return; } const data = { conversation_id: conversationId, title: title, description: document.getElementById('vulnerability-description').value.trim(), severity: severity, status: document.getElementById('vulnerability-status').value, type: document.getElementById('vulnerability-type').value.trim(), target: document.getElementById('vulnerability-target').value.trim(), proof: document.getElementById('vulnerability-proof').value.trim(), impact: document.getElementById('vulnerability-impact').value.trim(), recommendation: document.getElementById('vulnerability-recommendation').value.trim() }; try { const url = currentVulnerabilityId ? `/api/vulnerabilities/${currentVulnerabilityId}` : '/api/vulnerabilities'; const method = currentVulnerabilityId ? 'PUT' : 'POST'; const response = await apiFetch(url, { method: method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || '保存失败'); } closeVulnerabilityModal(); loadVulnerabilityStats(); // 保存/更新后,重置到第一页 vulnerabilityPagination.currentPage = 1; loadVulnerabilities(); } catch (error) { console.error('保存漏洞失败:', error); alert('保存漏洞失败: ' + error.message); } } // 删除漏洞 async function deleteVulnerability(id) { if (!confirm('确定要删除此漏洞吗?')) { return; } try { const response = await apiFetch(`/api/vulnerabilities/${id}`, { method: 'DELETE' }); if (!response.ok) throw new Error('删除失败'); loadVulnerabilityStats(); // 删除后,如果当前页没有数据了,回到上一页 if (vulnerabilityPagination.currentPage > 1 && vulnerabilityPagination.total > 0) { const itemsOnCurrentPage = vulnerabilityPagination.total - (vulnerabilityPagination.currentPage - 1) * vulnerabilityPagination.pageSize; if (itemsOnCurrentPage <= 1) { vulnerabilityPagination.currentPage--; } } loadVulnerabilities(); } catch (error) { console.error('删除漏洞失败:', error); alert('删除漏洞失败: ' + error.message); } } // 关闭漏洞模态框 function closeVulnerabilityModal() { document.getElementById('vulnerability-modal').style.display = 'none'; currentVulnerabilityId = null; } // 筛选漏洞 function filterVulnerabilities() { vulnerabilityFilters.id = document.getElementById('vulnerability-id-filter').value.trim(); vulnerabilityFilters.conversation_id = document.getElementById('vulnerability-conversation-filter').value.trim(); vulnerabilityFilters.severity = document.getElementById('vulnerability-severity-filter').value; vulnerabilityFilters.status = document.getElementById('vulnerability-status-filter').value; // 重置到第一页 vulnerabilityPagination.currentPage = 1; loadVulnerabilityStats(); loadVulnerabilities(); } // 清除筛选 function clearVulnerabilityFilters() { document.getElementById('vulnerability-id-filter').value = ''; document.getElementById('vulnerability-conversation-filter').value = ''; document.getElementById('vulnerability-severity-filter').value = ''; document.getElementById('vulnerability-status-filter').value = ''; vulnerabilityFilters = { id: '', conversation_id: '', severity: '', status: '' }; // 重置到第一页 vulnerabilityPagination.currentPage = 1; loadVulnerabilityStats(); loadVulnerabilities(); } // 刷新漏洞 function refreshVulnerabilities() { loadVulnerabilityStats(); loadVulnerabilities(); } // 切换漏洞详情展开/折叠 function toggleVulnerabilityDetails(id) { const content = document.getElementById(`content-${id}`); const icon = document.getElementById(`expand-icon-${id}`); if (!content || !icon) return; if (content.style.display === 'none') { content.style.display = 'block'; icon.style.transform = 'rotate(90deg)'; } else { content.style.display = 'none'; icon.style.transform = 'rotate(0deg)'; } } // HTML转义 function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // 将漏洞格式化为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'); let markdown = `# ${vuln.title}\n\n`; markdown += `## 基本信息\n\n`; markdown += `- **漏洞ID**: \`${vuln.id}\`\n`; markdown += `- **严重程度**: ${severityText}\n`; markdown += `- **状态**: ${statusText}\n`; if (vuln.type) { markdown += `- **类型**: ${vuln.type}\n`; } if (vuln.target) { markdown += `- **目标**: ${vuln.target}\n`; } markdown += `- **会话ID**: \`${vuln.conversation_id}\`\n`; markdown += `- **创建时间**: ${createdDate}\n`; markdown += `- **更新时间**: ${updatedDate}\n\n`; if (vuln.description) { markdown += `## 描述\n\n${vuln.description}\n\n`; } if (vuln.proof) { markdown += `## 证明(POC)\n\n\`\`\`\n${vuln.proof}\n\`\`\`\n\n`; } if (vuln.impact) { markdown += `## 影响\n\n${vuln.impact}\n\n`; } if (vuln.recommendation) { markdown += `## 修复建议\n\n${vuln.recommendation}\n\n`; } return markdown; } // 下载漏洞为Markdown格式 async function downloadVulnerabilityAsMarkdown(id, event) { try { const response = await apiFetch(`/api/vulnerabilities/${id}`); if (!response.ok) { throw new Error('获取漏洞失败'); } const vuln = await response.json(); const markdown = formatVulnerabilityAsMarkdown(vuln); // 创建Blob对象 const blob = new Blob([markdown], { type: 'text/markdown;charset=utf-8' }); // 创建下载链接 const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; // 生成文件名(使用漏洞标题,清理特殊字符,保留中文) const cleanTitle = vuln.title .replace(/[<>:"/\\|?*]/g, '') // 移除Windows不允许的字符 .replace(/\s+/g, '_') // 空格替换为下划线 .substring(0, 50); // 限制长度 const fileName = `${cleanTitle}_${vuln.id.substring(0, 8)}.md`; link.download = fileName; // 触发下载 document.body.appendChild(link); link.click(); // 清理 document.body.removeChild(link); URL.revokeObjectURL(url); // 显示成功提示 if (event && event.target) { const button = event.target.closest('button'); if (button) { const originalTitle = button.title || '下载Markdown'; button.title = '下载成功!'; setTimeout(() => { button.title = originalTitle; }, 2000); } } } catch (error) { console.error('下载失败:', error); alert('下载失败: ' + error.message); } } // 点击模态框外部关闭 window.onclick = function(event) { const modal = document.getElementById('vulnerability-modal'); if (event.target === modal) { closeVulnerabilityModal(); } }