mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-05-15 21:08:01 +02:00
Add files via upload
This commit is contained in:
@@ -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
@@ -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,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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()">×</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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user