mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-03-31 16:20:28 +02:00
691 lines
28 KiB
JavaScript
691 lines
28 KiB
JavaScript
// 漏洞管理相关功能
|
||
|
||
// 从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 = '<div class="loading-spinner">加载中...</div>';
|
||
|
||
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 = `<div class="error-message">加载失败: ${error.message}</div>`;
|
||
}
|
||
}
|
||
|
||
// 渲染漏洞列表
|
||
function renderVulnerabilities(vulnerabilities) {
|
||
const listContainer = document.getElementById('vulnerabilities-list');
|
||
|
||
// 处理空值情况
|
||
if (!vulnerabilities || !Array.isArray(vulnerabilities)) {
|
||
listContainer.innerHTML = '<div class="empty-state">暂无漏洞记录</div>';
|
||
return;
|
||
}
|
||
|
||
if (vulnerabilities.length === 0) {
|
||
listContainer.innerHTML = '<div class="empty-state">暂无漏洞记录</div>';
|
||
// 清空分页信息
|
||
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 `
|
||
<div class="vulnerability-card ${severityClass}">
|
||
<div class="vulnerability-header" onclick="toggleVulnerabilityDetails('${vuln.id}')" style="cursor: pointer;">
|
||
<div class="vulnerability-title-section">
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
<svg class="vulnerability-expand-icon" id="expand-icon-${vuln.id}" width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="transition: transform 0.2s ease; flex-shrink: 0;">
|
||
<path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||
</svg>
|
||
<h3 class="vulnerability-title">${escapeHtml(vuln.title)}</h3>
|
||
</div>
|
||
<div class="vulnerability-meta">
|
||
<span class="severity-badge ${severityClass}">${severityText}</span>
|
||
<span class="status-badge status-${vuln.status}">${statusText}</span>
|
||
<span class="vulnerability-date">${createdDate}</span>
|
||
</div>
|
||
</div>
|
||
<div class="vulnerability-actions" onclick="event.stopPropagation();">
|
||
<button class="btn-ghost" onclick="downloadVulnerabilityAsMarkdown('${vuln.id}', event)" title="下载Markdown">
|
||
<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="编辑">
|
||
<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="删除">
|
||
<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>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<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>
|
||
</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>` : ''}
|
||
</div>
|
||
</div>
|
||
`;
|
||
}).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;
|
||
}
|
||
|
||
// 计算显示的页码范围
|
||
let startPage = Math.max(1, currentPage - 2);
|
||
let endPage = Math.min(totalPages, currentPage + 2);
|
||
|
||
// 确保显示5个页码(如果可能)
|
||
if (endPage - startPage < 4) {
|
||
if (startPage === 1) {
|
||
endPage = Math.min(totalPages, startPage + 4);
|
||
} else if (endPage === totalPages) {
|
||
startPage = Math.max(1, endPage - 4);
|
||
}
|
||
}
|
||
|
||
let paginationHTML = '<div class="pagination">';
|
||
|
||
// 显示总数和当前范围
|
||
const startItem = (currentPage - 1) * pageSize + 1;
|
||
const endItem = Math.min(currentPage * pageSize, total);
|
||
paginationHTML += `<div class="pagination-info">显示 ${startItem}-${endItem} / 共 ${total} 条</div>`;
|
||
|
||
// 每页条数选择器(始终显示)
|
||
const savedPageSize = getVulnerabilityPageSize();
|
||
paginationHTML += `
|
||
<div class="pagination-page-size">
|
||
<label for="vulnerability-page-size-pagination">每页:</label>
|
||
<select id="vulnerability-page-size-pagination" onchange="changeVulnerabilityPageSize()">
|
||
<option value="10" ${savedPageSize === 10 ? 'selected' : ''}>10</option>
|
||
<option value="20" ${savedPageSize === 20 ? 'selected' : ''}>20</option>
|
||
<option value="50" ${savedPageSize === 50 ? 'selected' : ''}>50</option>
|
||
<option value="100" ${savedPageSize === 100 ? 'selected' : ''}>100</option>
|
||
</select>
|
||
</div>
|
||
`;
|
||
|
||
// 只有当有多页时才显示页码导航
|
||
if (totalPages > 1) {
|
||
paginationHTML += '<div class="pagination-controls">';
|
||
|
||
// 上一页按钮
|
||
if (currentPage > 1) {
|
||
paginationHTML += `<button class="pagination-btn" onclick="loadVulnerabilities(${currentPage - 1})" title="上一页">‹</button>`;
|
||
} else {
|
||
paginationHTML += '<button class="pagination-btn disabled" disabled>‹</button>';
|
||
}
|
||
|
||
// 第一页
|
||
if (startPage > 1) {
|
||
paginationHTML += `<button class="pagination-btn" onclick="loadVulnerabilities(1)">1</button>`;
|
||
if (startPage > 2) {
|
||
paginationHTML += '<span class="pagination-ellipsis">...</span>';
|
||
}
|
||
}
|
||
|
||
// 页码按钮
|
||
for (let i = startPage; i <= endPage; i++) {
|
||
if (i === currentPage) {
|
||
paginationHTML += `<button class="pagination-btn active">${i}</button>`;
|
||
} else {
|
||
paginationHTML += `<button class="pagination-btn" onclick="loadVulnerabilities(${i})">${i}</button>`;
|
||
}
|
||
}
|
||
|
||
// 最后一页
|
||
if (endPage < totalPages) {
|
||
if (endPage < totalPages - 1) {
|
||
paginationHTML += '<span class="pagination-ellipsis">...</span>';
|
||
}
|
||
paginationHTML += `<button class="pagination-btn" onclick="loadVulnerabilities(${totalPages})">${totalPages}</button>`;
|
||
}
|
||
|
||
// 下一页按钮
|
||
if (currentPage < totalPages) {
|
||
paginationHTML += `<button class="pagination-btn" onclick="loadVulnerabilities(${currentPage + 1})" title="下一页">›</button>`;
|
||
} else {
|
||
paginationHTML += '<button class="pagination-btn disabled" disabled>›</button>';
|
||
}
|
||
|
||
paginationHTML += '</div>';
|
||
}
|
||
|
||
paginationHTML += '</div>';
|
||
|
||
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 = '添加漏洞';
|
||
|
||
// 清空表单
|
||
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 = '编辑漏洞';
|
||
|
||
// 填充表单
|
||
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();
|
||
}
|
||
}
|
||
|