Add files via upload

This commit is contained in:
公明
2025-12-25 22:12:41 +08:00
committed by GitHub
parent 99cf5e78a9
commit 025704cbf7
11 changed files with 1683 additions and 28 deletions
+333
View File
@@ -5645,3 +5645,336 @@ header {
.context-submenu-item.add-group-item:hover {
background: rgba(0, 102, 255, 0.1);
}
/* 漏洞管理页面样式 */
.vulnerability-dashboard {
margin-bottom: 24px;
}
.dashboard-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.stat-card {
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 16px;
text-align: center;
box-shadow: var(--shadow-sm);
transition: all 0.2s ease;
}
.stat-card:hover {
box-shadow: var(--shadow-md);
transform: translateY(-2px);
}
.stat-card.stat-critical {
border-left: 4px solid #dc3545;
}
.stat-card.stat-high {
border-left: 4px solid #fd7e14;
}
.stat-card.stat-medium {
border-left: 4px solid #ffc107;
}
.stat-card.stat-low {
border-left: 4px solid #20c997;
}
.stat-card.stat-info {
border-left: 4px solid #6c757d;
}
.stat-label {
font-size: 0.875rem;
color: var(--text-secondary);
margin-bottom: 8px;
}
.stat-value {
font-size: 2rem;
font-weight: 700;
color: var(--text-primary);
}
.vulnerability-controls {
margin-bottom: 24px;
}
.vulnerability-filters {
display: flex;
gap: 16px;
flex-wrap: wrap;
align-items: flex-end;
}
.vulnerability-filters label {
display: flex;
flex-direction: column;
gap: 4px;
font-size: 0.875rem;
color: var(--text-secondary);
}
.vulnerability-filters input,
.vulnerability-filters select {
padding: 8px 12px;
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: 0.875rem;
min-width: 150px;
}
.vulnerabilities-list {
display: flex;
flex-direction: column;
gap: 16px;
}
.vulnerability-card {
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 20px;
box-shadow: var(--shadow-sm);
transition: all 0.2s ease;
}
.vulnerability-card:hover {
box-shadow: var(--shadow-md);
}
.vulnerability-card.severity-critical {
border-left: 4px solid #dc3545;
}
.vulnerability-card.severity-high {
border-left: 4px solid #fd7e14;
}
.vulnerability-card.severity-medium {
border-left: 4px solid #ffc107;
}
.vulnerability-card.severity-low {
border-left: 4px solid #20c997;
}
.vulnerability-card.severity-info {
border-left: 4px solid #6c757d;
}
.vulnerability-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 0;
padding: 4px 0;
transition: background-color 0.2s ease;
}
.vulnerability-header:hover {
background-color: var(--bg-secondary);
border-radius: 4px;
padding: 4px 8px;
margin: -4px -8px;
}
.vulnerability-title-section {
flex: 1;
}
.vulnerability-title {
font-size: 1.125rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 8px;
flex: 1;
}
.vulnerability-expand-icon {
color: var(--text-secondary);
transition: transform 0.2s ease, color 0.2s ease;
}
.vulnerability-header:hover .vulnerability-expand-icon {
color: var(--accent-color);
}
.vulnerability-meta {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
.severity-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.severity-badge.severity-critical {
background: rgba(220, 53, 69, 0.1);
color: #dc3545;
}
.severity-badge.severity-high {
background: rgba(253, 126, 20, 0.1);
color: #fd7e14;
}
.severity-badge.severity-medium {
background: rgba(255, 193, 7, 0.1);
color: #ffc107;
}
.severity-badge.severity-low {
background: rgba(32, 201, 151, 0.1);
color: #20c997;
}
.severity-badge.severity-info {
background: rgba(108, 117, 125, 0.1);
color: #6c757d;
}
.status-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 500;
background: var(--bg-tertiary);
color: var(--text-secondary);
}
.status-badge.status-open {
background: rgba(0, 102, 255, 0.1);
color: #0066ff;
}
.status-badge.status-confirmed {
background: rgba(40, 167, 69, 0.1);
color: #28a745;
}
.status-badge.status-fixed {
background: rgba(108, 117, 125, 0.1);
color: #6c757d;
}
.status-badge.status-false_positive {
background: rgba(220, 53, 69, 0.1);
color: #dc3545;
}
.vulnerability-date {
font-size: 0.75rem;
color: var(--text-muted);
}
.vulnerability-actions {
display: flex;
gap: 8px;
}
.vulnerability-content {
margin-top: 16px;
padding-left: 24px;
animation: slideDown 0.2s ease;
}
@keyframes slideDown {
from {
opacity: 0;
max-height: 0;
}
to {
opacity: 1;
max-height: 5000px;
}
}
.vulnerability-description {
margin-bottom: 16px;
color: var(--text-primary);
line-height: 1.6;
}
.vulnerability-details {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px;
margin-bottom: 16px;
padding: 12px;
background: var(--bg-secondary);
border-radius: 6px;
}
.detail-item {
font-size: 0.875rem;
}
.detail-item strong {
color: var(--text-secondary);
margin-right: 4px;
}
.detail-item code {
background: var(--bg-tertiary);
padding: 2px 6px;
border-radius: 4px;
font-size: 0.8rem;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
}
.vulnerability-proof,
.vulnerability-impact,
.vulnerability-recommendation {
margin-top: 12px;
padding: 12px;
background: var(--bg-secondary);
border-radius: 6px;
font-size: 0.875rem;
line-height: 1.6;
}
.vulnerability-proof pre {
margin-top: 8px;
padding: 12px;
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 4px;
overflow-x: auto;
font-size: 0.8rem;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
white-space: pre-wrap;
word-wrap: break-word;
}
.vulnerability-proof strong,
.vulnerability-impact strong,
.vulnerability-recommendation strong {
color: var(--text-primary);
display: block;
margin-bottom: 8px;
}
.empty-state {
text-align: center;
padding: 48px;
color: var(--text-secondary);
font-size: 1rem;
}
+59 -7
View File
@@ -4634,15 +4634,31 @@ async function removeConversationFromGroup(convId, groupId) {
async function loadConversationGroupMapping() {
try {
// 获取所有分组,然后获取每个分组的对话
const groups = groupsCache.length > 0 ? groupsCache : await (await apiFetch('/api/groups')).json();
let groups;
if (Array.isArray(groupsCache) && groupsCache.length > 0) {
groups = groupsCache;
} else {
const response = await apiFetch('/api/groups');
groups = await response.json();
}
// 确保groups是有效数组
if (!Array.isArray(groups)) {
console.warn('loadConversationGroupMapping: groups不是有效数组,使用空数组');
groups = [];
}
conversationGroupMappingCache = {};
for (const group of groups) {
const response = await apiFetch(`/api/groups/${group.id}/conversations`);
const conversations = await response.json();
conversations.forEach(conv => {
conversationGroupMappingCache[conv.id] = group.id;
});
// 确保conversations是有效数组
if (Array.isArray(conversations)) {
conversations.forEach(conv => {
conversationGroupMappingCache[conv.id] = group.id;
});
}
}
} catch (error) {
console.error('加载对话分组映射失败:', error);
@@ -4866,7 +4882,19 @@ async function createGroup(event) {
// 前端校验:检查名称是否已存在
try {
const groups = groupsCache.length > 0 ? groupsCache : await (await apiFetch('/api/groups')).json();
let groups;
if (Array.isArray(groupsCache) && groupsCache.length > 0) {
groups = groupsCache;
} else {
const response = await apiFetch('/api/groups');
groups = await response.json();
}
// 确保groups是有效数组
if (!Array.isArray(groups)) {
groups = [];
}
const nameExists = groups.some(g => g.name === name);
if (nameExists) {
alert('分组名称已存在,请使用其他名称');
@@ -5175,7 +5203,19 @@ async function editGroup() {
const trimmedName = newName.trim();
// 前端校验:检查名称是否已存在(排除当前分组)
const groups = groupsCache.length > 0 ? groupsCache : await (await apiFetch('/api/groups')).json();
let groups;
if (Array.isArray(groupsCache) && groupsCache.length > 0) {
groups = groupsCache;
} else {
const response = await apiFetch('/api/groups');
groups = await response.json();
}
// 确保groups是有效数组
if (!Array.isArray(groups)) {
groups = [];
}
const nameExists = groups.some(g => g.name === trimmedName && g.id !== currentGroupId);
if (nameExists) {
alert('分组名称已存在,请使用其他名称');
@@ -5273,7 +5313,19 @@ async function renameGroupFromContext() {
const trimmedName = newName.trim();
// 前端校验:检查名称是否已存在(排除当前分组)
const groups = groupsCache.length > 0 ? groupsCache : await (await apiFetch('/api/groups')).json();
let groups;
if (Array.isArray(groupsCache) && groupsCache.length > 0) {
groups = groupsCache;
} else {
const response = await apiFetch('/api/groups');
groups = await response.json();
}
// 确保groups是有效数组
if (!Array.isArray(groups)) {
groups = [];
}
const nameExists = groups.some(g => g.name === trimmedName && g.id !== groupId);
if (nameExists) {
alert('分组名称已存在,请使用其他名称');
+8 -2
View File
@@ -8,7 +8,7 @@ function initRouter() {
// 从URL hash读取页面(如果有)
const hash = window.location.hash.slice(1);
if (hash && ['chat', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'settings'].includes(hash)) {
if (hash && ['chat', 'vulnerabilities', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'settings'].includes(hash)) {
switchPage(hash);
}
}
@@ -198,6 +198,12 @@ function initPage(pageId) {
loadToolsList(1, '');
}
break;
case 'vulnerabilities':
// 初始化漏洞管理页面
if (typeof initVulnerabilityPage === 'function') {
initVulnerabilityPage();
}
break;
case 'settings':
// 初始化设置页面(不需要加载工具列表)
if (typeof loadConfig === 'function') {
@@ -215,7 +221,7 @@ document.addEventListener('DOMContentLoaded', function() {
// 监听hash变化
window.addEventListener('hashchange', function() {
const hash = window.location.hash.slice(1);
if (hash && ['chat', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'settings'].includes(hash)) {
if (hash && ['chat', 'vulnerabilities', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'settings'].includes(hash)) {
switchPage(hash);
}
});
+380
View File
@@ -0,0 +1,380 @@
// 漏洞管理相关功能
let currentVulnerabilityId = null;
let vulnerabilityFilters = {
conversation_id: '',
severity: '',
status: ''
};
// 初始化漏洞管理页面
function initVulnerabilityPage() {
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() {
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未定义');
}
const params = new URLSearchParams();
params.append('limit', '100');
params.append('offset', '0');
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 vulnerabilities = await response.json();
renderVulnerabilities(vulnerabilities);
} 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>';
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="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">
${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 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();
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();
loadVulnerabilities();
} catch (error) {
console.error('删除漏洞失败:', error);
alert('删除漏洞失败: ' + error.message);
}
}
// 关闭漏洞模态框
function closeVulnerabilityModal() {
document.getElementById('vulnerability-modal').style.display = 'none';
currentVulnerabilityId = null;
}
// 筛选漏洞
function filterVulnerabilities() {
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;
loadVulnerabilityStats();
loadVulnerabilities();
}
// 清除筛选
function clearVulnerabilityFilters() {
document.getElementById('vulnerability-conversation-filter').value = '';
document.getElementById('vulnerability-severity-filter').value = '';
document.getElementById('vulnerability-status-filter').value = '';
vulnerabilityFilters = {
conversation_id: '',
severity: '',
status: ''
};
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;
}
// 点击模态框外部关闭
window.onclick = function(event) {
const modal = document.getElementById('vulnerability-modal');
if (event.target === modal) {
closeVulnerabilityModal();
}
}
+158
View File
@@ -76,6 +76,15 @@
<span>对话</span>
</div>
</div>
<div class="nav-item" data-page="vulnerabilities">
<div class="nav-item-content" data-title="漏洞管理" onclick="switchPage('vulnerabilities')">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 12l2 2 4-4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span>漏洞管理</span>
</div>
</div>
<div class="nav-item nav-item-has-submenu" data-page="mcp">
<div class="nav-item-content" data-title="MCP" onclick="toggleSubmenu('mcp')">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -430,6 +439,86 @@
</div>
</div>
<!-- 漏洞管理页面 -->
<div id="page-vulnerabilities" class="page">
<div class="page-header">
<h2>漏洞管理</h2>
<div class="page-header-actions">
<button class="btn-secondary" onclick="refreshVulnerabilities()">刷新</button>
<button class="btn-primary" onclick="showAddVulnerabilityModal()">添加漏洞</button>
</div>
</div>
<div class="page-content">
<!-- 统计看板 -->
<div class="vulnerability-dashboard" id="vulnerability-dashboard">
<div class="dashboard-stats">
<div class="stat-card">
<div class="stat-label">总漏洞数</div>
<div class="stat-value" id="stat-total">-</div>
</div>
<div class="stat-card stat-critical">
<div class="stat-label">严重</div>
<div class="stat-value" id="stat-critical">-</div>
</div>
<div class="stat-card stat-high">
<div class="stat-label">高危</div>
<div class="stat-value" id="stat-high">-</div>
</div>
<div class="stat-card stat-medium">
<div class="stat-label">中危</div>
<div class="stat-value" id="stat-medium">-</div>
</div>
<div class="stat-card stat-low">
<div class="stat-label">低危</div>
<div class="stat-value" id="stat-low">-</div>
</div>
<div class="stat-card stat-info">
<div class="stat-label">信息</div>
<div class="stat-value" id="stat-info">-</div>
</div>
</div>
</div>
<!-- 筛选和搜索 -->
<div class="vulnerability-controls">
<div class="vulnerability-filters">
<label>
会话ID
<input type="text" id="vulnerability-conversation-filter" placeholder="筛选特定会话" />
</label>
<label>
严重程度
<select id="vulnerability-severity-filter">
<option value="">全部</option>
<option value="critical">严重</option>
<option value="high">高危</option>
<option value="medium">中危</option>
<option value="low">低危</option>
<option value="info">信息</option>
</select>
</label>
<label>
状态
<select id="vulnerability-status-filter">
<option value="">全部</option>
<option value="open">待处理</option>
<option value="confirmed">已确认</option>
<option value="fixed">已修复</option>
<option value="false_positive">误报</option>
</select>
</label>
<button class="btn-secondary" onclick="filterVulnerabilities()">筛选</button>
<button class="btn-secondary" onclick="clearVulnerabilityFilters()">清除</button>
</div>
</div>
<!-- 漏洞列表 -->
<div id="vulnerabilities-list" class="vulnerabilities-list">
<div class="loading-spinner">加载中...</div>
</div>
</div>
</div>
<!-- 系统设置页面 -->
<div id="page-settings" class="page">
<div class="page-header">
@@ -952,12 +1041,81 @@
</div>
</div>
<!-- 漏洞编辑模态框 -->
<div id="vulnerability-modal" class="modal">
<div class="modal-content" style="max-width: 900px;">
<div class="modal-header">
<h2 id="vulnerability-modal-title">添加漏洞</h2>
<span class="modal-close" onclick="closeVulnerabilityModal()">&times;</span>
</div>
<div class="modal-body">
<div class="form-group">
<label for="vulnerability-conversation-id">会话ID <span style="color: red;">*</span></label>
<input type="text" id="vulnerability-conversation-id" placeholder="输入会话ID" required />
</div>
<div class="form-group">
<label for="vulnerability-title">标题 <span style="color: red;">*</span></label>
<input type="text" id="vulnerability-title" placeholder="漏洞标题" required />
</div>
<div class="form-group">
<label for="vulnerability-description">描述</label>
<textarea id="vulnerability-description" rows="5" placeholder="漏洞详细描述"></textarea>
</div>
<div class="form-group">
<label for="vulnerability-severity">严重程度 <span style="color: red;">*</span></label>
<select id="vulnerability-severity" required>
<option value="">请选择</option>
<option value="critical">严重</option>
<option value="high">高危</option>
<option value="medium">中危</option>
<option value="low">低危</option>
<option value="info">信息</option>
</select>
</div>
<div class="form-group">
<label for="vulnerability-status">状态</label>
<select id="vulnerability-status">
<option value="open">待处理</option>
<option value="confirmed">已确认</option>
<option value="fixed">已修复</option>
<option value="false_positive">误报</option>
</select>
</div>
<div class="form-group">
<label for="vulnerability-type">漏洞类型</label>
<input type="text" id="vulnerability-type" placeholder="如:SQL注入、XSS、CSRF等" />
</div>
<div class="form-group">
<label for="vulnerability-target">目标</label>
<input type="text" id="vulnerability-target" placeholder="受影响的目标(URL、IP地址等)" />
</div>
<div class="form-group">
<label for="vulnerability-proof">证明(POC</label>
<textarea id="vulnerability-proof" rows="5" placeholder="漏洞证明,如请求/响应、截图等"></textarea>
</div>
<div class="form-group">
<label for="vulnerability-impact">影响</label>
<textarea id="vulnerability-impact" rows="3" placeholder="漏洞影响说明"></textarea>
</div>
<div class="form-group">
<label for="vulnerability-recommendation">修复建议</label>
<textarea id="vulnerability-recommendation" rows="3" placeholder="修复建议"></textarea>
</div>
</div>
<div class="modal-footer">
<button class="btn-secondary" onclick="closeVulnerabilityModal()">取消</button>
<button class="btn-primary" onclick="saveVulnerability()">保存</button>
</div>
</div>
</div>
<script src="/static/js/auth.js"></script>
<script src="/static/js/router.js"></script>
<script src="/static/js/monitor.js"></script>
<script src="/static/js/chat.js"></script>
<script src="/static/js/settings.js"></script>
<script src="/static/js/knowledge.js"></script>
<script src="/static/js/vulnerability.js?v=4"></script>
</body>
</html>