Files
CyberStrikeAI/web/templates/index.html
2026-03-13 22:34:42 +08:00

2322 lines
196 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CyberStrikeAI</title>
<link rel="icon" type="image/png" href="/static/logo.png">
<link rel="shortcut icon" type="image/png" href="/static/favicon.ico">
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@4.19.0/css/xterm.css">
</head>
<body>
<div id="login-overlay" class="login-overlay" style="display: none;">
<div class="login-card">
<div class="login-brand">
<h2 data-i18n="login.title">登录 CyberStrikeAI</h2>
<p class="login-subtitle" data-i18n="login.subtitle">请输入配置中的访问密码</p>
</div>
<form id="login-form" class="login-form">
<div class="form-group">
<label for="login-password" data-i18n="login.passwordLabel">密码</label>
<input type="password" id="login-password" placeholder="输入登录密码" required autocomplete="current-password" data-i18n="login.passwordPlaceholder" data-i18n-attr="placeholder" />
</div>
<div id="login-error" class="login-error" role="alert" style="display: none;"></div>
<button type="submit" class="btn-primary login-submit">
<span data-i18n="login.submit">登录</span>
</button>
</form>
</div>
</div>
<div class="container">
<header>
<div class="header-content">
<div class="logo header-logo-link" onclick="switchPage('dashboard')" role="button" data-i18n="header.backToDashboard" data-i18n-attr="title" data-i18n-skip-text="true" title="返回仪表盘">
<img src="/static/logo.png" alt="CyberStrikeAI Logo" style="width: 32px; height: 32px; margin-right: 8px;">
<h1>CyberStrikeAI</h1>
<span class="version-badge" data-i18n="header.version" data-i18n-attr="title" data-i18n-skip-text="true" title="当前版本">{{.Version}}</span>
</div>
<div class="header-right">
<div class="header-actions">
<button class="openapi-doc-btn" onclick="window.open('/api-docs', '_blank')" data-i18n="header.apiDocs" data-i18n-attr="title" data-i18n-skip-text="true" title="API 文档">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<polyline points="14 2 14 8 20 8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16 13H8M16 17H8M10 9H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span data-i18n="header.apiDocs">API 文档</span>
</button>
<div class="lang-switcher">
<button class="btn-secondary lang-switcher-btn" onclick="toggleLangDropdown()" data-i18n="header.language" data-i18n-attr="title" data-i18n-skip-text="true" title="界面语言">
<span class="lang-switcher-icon">🌐</span>
<span id="current-lang-label">中文</span>
</button>
<div id="lang-dropdown" class="lang-dropdown" style="display: none;">
<div class="lang-option" data-lang="zh-CN" onclick="onLanguageSelect('zh-CN')">中文</div>
<div class="lang-option" data-lang="en-US" onclick="onLanguageSelect('en-US')">English</div>
</div>
</div>
<div class="user-menu-container">
<button class="user-avatar-btn" onclick="toggleUserMenu()" data-i18n="header.userMenu" data-i18n-attr="title" data-i18n-skip-text="true" title="用户菜单">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="12" cy="7" r="4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<div id="user-menu-dropdown" class="user-menu-dropdown" style="display: none;">
<div class="user-menu-item" onclick="logout()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<polyline points="16 17 21 12 16 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<line x1="21" y1="12" x2="9" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span data-i18n="header.logout">退出登录</span>
</div>
</div>
</div>
</div>
</div>
</div>
</header>
<div class="main-layout">
<!-- 主侧边栏 -->
<aside class="main-sidebar" id="main-sidebar">
<div class="sidebar-collapse-btn" onclick="toggleSidebar()" data-i18n="header.toggleSidebar" data-i18n-attr="title" data-i18n-skip-text="true" title="折叠/展开侧边栏">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 18l-6-6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<nav class="main-sidebar-nav">
<div class="nav-item" data-page="dashboard">
<div class="nav-item-content" data-title="仪表盘" onclick="switchPage('dashboard')" data-i18n="nav.dashboard" data-i18n-attr="data-title" data-i18n-skip-text="true">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="7" height="9"></rect>
<rect x="14" y="3" width="7" height="5"></rect>
<rect x="14" y="12" width="7" height="9"></rect>
<rect x="3" y="16" width="7" height="5"></rect>
</svg>
<span data-i18n="nav.dashboard">仪表盘</span>
</div>
</div>
<div class="nav-item" data-page="chat">
<div class="nav-item-content" data-title="对话" onclick="switchPage('chat')" data-i18n="nav.chat" data-i18n-attr="data-title" data-i18n-skip-text="true">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
</svg>
<span data-i18n="nav.chat">对话</span>
</div>
</div>
<div class="nav-item" data-page="info-collect">
<div class="nav-item-content" data-title="信息收集" onclick="switchPage('info-collect')" data-i18n="nav.infoCollect" data-i18n-attr="data-title" data-i18n-skip-text="true">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<span data-i18n="nav.infoCollect">信息收集</span>
</div>
</div>
<div class="nav-item" data-page="tasks">
<div class="nav-item-content" data-title="任务管理" onclick="switchPage('tasks')" data-i18n="nav.tasks" data-i18n-attr="data-title" data-i18n-skip-text="true">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 11l3 3L22 4"></path>
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
</svg>
<span data-i18n="nav.tasks">任务管理</span>
</div>
</div>
<div class="nav-item" data-page="vulnerabilities">
<div class="nav-item-content" data-title="漏洞管理" onclick="switchPage('vulnerabilities')" data-i18n="nav.vulnerabilities" data-i18n-attr="data-title" data-i18n-skip-text="true">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
<path d="M9 12l2 2 4-4"></path>
</svg>
<span data-i18n="nav.vulnerabilities">漏洞管理</span>
</div>
</div>
<div class="nav-item" data-page="webshell">
<div class="nav-item-content" data-title="WebShell管理" onclick="switchPage('webshell')" data-i18n="nav.webshell" data-i18n-attr="data-title" data-i18n-skip-text="true">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="4 17 10 11 4 5"></polyline>
<line x1="12" y1="19" x2="20" y2="19"></line>
</svg>
<span data-i18n="nav.webshell">WebShell管理</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')" data-i18n="nav.mcp" data-i18n-attr="data-title" data-i18n-skip-text="true">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"></path>
</svg>
<span data-i18n="nav.mcp">MCP</span>
<svg class="submenu-arrow" width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="nav-submenu">
<div class="nav-submenu-item" data-page="mcp-monitor" onclick="switchPage('mcp-monitor')">
<span data-i18n="nav.mcpMonitor">MCP状态监控</span>
</div>
<div class="nav-submenu-item" data-page="mcp-management" onclick="switchPage('mcp-management')">
<span data-i18n="nav.mcpManagement">MCP管理</span>
</div>
</div>
</div>
<div class="nav-item nav-item-has-submenu" data-page="knowledge">
<div class="nav-item-content" data-title="知识" onclick="toggleSubmenu('knowledge')" data-i18n="nav.knowledge" data-i18n-attr="data-title" data-i18n-skip-text="true">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>
</svg>
<span data-i18n="nav.knowledge">知识</span>
<svg class="submenu-arrow" width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="nav-submenu">
<div class="nav-submenu-item" data-page="knowledge-retrieval-logs" onclick="switchPage('knowledge-retrieval-logs')">
<span data-i18n="nav.knowledgeRetrievalLogs">检索历史</span>
</div>
<div class="nav-submenu-item" data-page="knowledge-management" onclick="switchPage('knowledge-management')">
<span data-i18n="nav.knowledgeManagement">知识管理</span>
</div>
</div>
</div>
<div class="nav-item nav-item-has-submenu" data-page="skills">
<div class="nav-item-content" data-title="Skills" onclick="toggleSubmenu('skills')" data-i18n="nav.skills" data-i18n-attr="data-title" data-i18n-skip-text="true">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
<span data-i18n="nav.skills">Skills</span>
<svg class="submenu-arrow" width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="nav-submenu">
<div class="nav-submenu-item" data-page="skills-monitor" onclick="switchPage('skills-monitor')">
<span data-i18n="nav.skillsMonitor">Skills状态监控</span>
</div>
<div class="nav-submenu-item" data-page="skills-management" onclick="switchPage('skills-management')">
<span data-i18n="nav.skillsManagement">Skills管理</span>
</div>
</div>
</div>
<div class="nav-item nav-item-has-submenu" data-page="roles">
<div class="nav-item-content" data-title="角色" onclick="toggleSubmenu('roles')" data-i18n="nav.roles" data-i18n-attr="data-title" data-i18n-skip-text="true">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
</svg>
<span data-i18n="nav.roles">角色</span>
<svg class="submenu-arrow" width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="nav-submenu">
<div class="nav-submenu-item" data-page="roles-management" onclick="switchPage('roles-management')">
<span data-i18n="nav.rolesManagement">角色管理</span>
</div>
</div>
</div>
<div class="nav-item" data-page="settings">
<div class="nav-item-content" data-title="系统设置" onclick="switchPage('settings')" data-i18n="nav.settings" data-i18n-attr="data-title" data-i18n-skip-text="true">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"></circle>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
</svg>
<span data-i18n="nav.settings">系统设置</span>
</div>
</div>
</nav>
</aside>
<!-- 内容区域 -->
<div class="content-area">
<!-- 仪表盘页面 -->
<div id="page-dashboard" class="page active">
<div class="dashboard-page">
<div class="page-header">
<h2 data-i18n="dashboard.title">仪表盘</h2>
<div class="page-header-actions">
<button class="btn-secondary" onclick="refreshDashboard()" data-i18n="dashboard.refreshData" data-i18n-attr="title" title="刷新数据"><span data-i18n="common.refresh">刷新</span></button>
</div>
</div>
<div class="dashboard-content">
<!-- 第一行:核心 KPI仪表盘最佳实践关键指标置顶 -->
<div class="dashboard-kpi-row" id="dashboard-cards">
<div class="dashboard-kpi-card" role="button" tabindex="0" onclick="switchPage('tasks')" onkeydown="if(event.key==='Enter'||event.key===' ') { event.preventDefault(); switchPage('tasks'); }" data-i18n="dashboard.clickToViewTasks" data-i18n-attr="title" title="点击查看任务管理"> <div class="dashboard-kpi-value" id="dashboard-running-tasks">-</div><div class="dashboard-kpi-label" data-i18n="dashboard.runningTasks">运行中任务</div></div>
<div class="dashboard-kpi-card" role="button" tabindex="0" onclick="switchPage('vulnerabilities')" onkeydown="if(event.key==='Enter'||event.key===' ') { event.preventDefault(); switchPage('vulnerabilities'); }" data-i18n="dashboard.clickToViewVuln" data-i18n-attr="title" title="点击查看漏洞管理"><div class="dashboard-kpi-value" id="dashboard-vuln-total">-</div><div class="dashboard-kpi-label" data-i18n="dashboard.vulnTotal">漏洞总数</div></div>
<div class="dashboard-kpi-card" role="button" tabindex="0" onclick="switchPage('mcp-monitor')" onkeydown="if(event.key==='Enter'||event.key===' ') { event.preventDefault(); switchPage('mcp-monitor'); }" data-i18n="dashboard.clickToViewMCP" data-i18n-attr="title" title="点击查看 MCP 监控"><div class="dashboard-kpi-value" id="dashboard-kpi-tools-calls">-</div><div class="dashboard-kpi-label" data-i18n="dashboard.toolCalls">工具调用次数</div></div>
<div class="dashboard-kpi-card" role="button" tabindex="0" onclick="switchPage('mcp-monitor')" onkeydown="if(event.key==='Enter'||event.key===' ') { event.preventDefault(); switchPage('mcp-monitor'); }" data-i18n="dashboard.clickToViewMCP" data-i18n-attr="title" title="点击查看 MCP 监控"><div class="dashboard-kpi-value" id="dashboard-kpi-success-rate">-</div><div class="dashboard-kpi-label" data-i18n="dashboard.successRate">工具执行成功率</div></div>
</div>
<!-- 两列主内容区 -->
<div class="dashboard-grid">
<div class="dashboard-main">
<section class="dashboard-section dashboard-section-chart">
<h3 class="dashboard-section-title" data-i18n="dashboard.severityDistribution">漏洞严重程度分布</h3>
<div class="dashboard-chart-wrap">
<div class="dashboard-stacked-bar" id="dashboard-stacked-bar">
<span class="dashboard-bar-seg seg-critical" id="dashboard-bar-critical" style="width: 0%"></span>
<span class="dashboard-bar-seg seg-high" id="dashboard-bar-high" style="width: 0%"></span>
<span class="dashboard-bar-seg seg-medium" id="dashboard-bar-medium" style="width: 0%"></span>
<span class="dashboard-bar-seg seg-low" id="dashboard-bar-low" style="width: 0%"></span>
<span class="dashboard-bar-seg seg-info" id="dashboard-bar-info" style="width: 0%"></span>
</div>
<div class="dashboard-legend" id="dashboard-vuln-bars">
<div class="dashboard-legend-item"><span class="dashboard-legend-dot critical"></span><span class="dashboard-legend-label" data-i18n="dashboard.severityCritical">严重</span><span class="dashboard-legend-value" id="dashboard-severity-critical">0</span></div>
<div class="dashboard-legend-item"><span class="dashboard-legend-dot high"></span><span class="dashboard-legend-label" data-i18n="dashboard.severityHigh">高危</span><span class="dashboard-legend-value" id="dashboard-severity-high">0</span></div>
<div class="dashboard-legend-item"><span class="dashboard-legend-dot medium"></span><span class="dashboard-legend-label" data-i18n="dashboard.severityMedium">中危</span><span class="dashboard-legend-value" id="dashboard-severity-medium">0</span></div>
<div class="dashboard-legend-item"><span class="dashboard-legend-dot low"></span><span class="dashboard-legend-label" data-i18n="dashboard.severityLow">低危</span><span class="dashboard-legend-value" id="dashboard-severity-low">0</span></div>
<div class="dashboard-legend-item"><span class="dashboard-legend-dot info"></span><span class="dashboard-legend-label" data-i18n="dashboard.severityInfo">信息</span><span class="dashboard-legend-value" id="dashboard-severity-info">0</span></div>
</div>
</div>
</section>
<section class="dashboard-section dashboard-section-overview">
<h3 class="dashboard-section-title" data-i18n="dashboard.runOverview">运行概览</h3>
<div class="dashboard-overview-list">
<div class="dashboard-overview-item dashboard-overview-item-batch" role="button" tabindex="0" onclick="switchPage('tasks')" onkeydown="if(event.key==='Enter'||event.key===' ') { event.preventDefault(); switchPage('tasks'); }">
<span class="dashboard-overview-icon dashboard-overview-icon-batch" aria-hidden="true"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg></span>
<div class="dashboard-overview-content">
<div class="dashboard-overview-header">
<span class="dashboard-overview-label" data-i18n="dashboard.batchQueues">批量任务队列</span>
<span class="dashboard-overview-total" id="dashboard-batch-total">-</span>
</div>
<div class="dashboard-overview-stats">
<span class="dashboard-overview-stat dashboard-overview-stat-pending">
<span class="dashboard-overview-stat-badge badge-pending"></span>
<span class="dashboard-overview-stat-value" id="dashboard-batch-pending">-</span>
<span class="dashboard-overview-stat-label" data-i18n="dashboard.pending">待执行</span>
</span>
<span class="dashboard-overview-stat dashboard-overview-stat-running">
<span class="dashboard-overview-stat-badge badge-running"></span>
<span class="dashboard-overview-stat-value" id="dashboard-batch-running">-</span>
<span class="dashboard-overview-stat-label" data-i18n="dashboard.executing">执行中</span>
</span>
<span class="dashboard-overview-stat dashboard-overview-stat-done">
<span class="dashboard-overview-stat-badge badge-done"></span>
<span class="dashboard-overview-stat-value" id="dashboard-batch-done">-</span>
<span class="dashboard-overview-stat-label" data-i18n="dashboard.completed">已完成</span>
</span>
</div>
<div class="dashboard-overview-progress">
<div class="dashboard-overview-progress-bar">
<div class="dashboard-overview-progress-segment dashboard-overview-progress-pending" id="dashboard-batch-progress-pending" style="width: 0%"></div>
<div class="dashboard-overview-progress-segment dashboard-overview-progress-running" id="dashboard-batch-progress-running" style="width: 0%"></div>
<div class="dashboard-overview-progress-segment dashboard-overview-progress-done" id="dashboard-batch-progress-done" style="width: 0%"></div>
</div>
</div>
</div>
</div>
<div class="dashboard-overview-item dashboard-overview-item-tools" role="button" tabindex="0" onclick="switchPage('mcp-monitor')" onkeydown="if(event.key==='Enter'||event.key===' ') { event.preventDefault(); switchPage('mcp-monitor'); }">
<span class="dashboard-overview-icon dashboard-overview-icon-tools" aria-hidden="true"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg></span>
<div class="dashboard-overview-content">
<div class="dashboard-overview-header">
<span class="dashboard-overview-label" data-i18n="dashboard.toolInvocations">工具调用</span>
<span class="dashboard-overview-success-rate" id="dashboard-tools-success-rate">-</span>
</div>
<div class="dashboard-overview-value-group">
<span class="dashboard-overview-value-large" id="dashboard-tools-calls">-</span>
<span class="dashboard-overview-value-unit" data-i18n="dashboard.callsUnit">次调用</span>
<span class="dashboard-overview-value-separator">·</span>
<span class="dashboard-overview-value-normal" id="dashboard-tools-count">-</span>
<span class="dashboard-overview-value-unit" data-i18n="dashboard.toolsUnit">个工具</span>
</div>
</div>
</div>
<div class="dashboard-overview-item dashboard-overview-item-knowledge" role="button" tabindex="0" onclick="switchPage('knowledge-management')" onkeydown="if(event.key==='Enter'||event.key===' ') { event.preventDefault(); switchPage('knowledge-management'); }">
<span class="dashboard-overview-icon dashboard-overview-icon-knowledge" aria-hidden="true"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg></span>
<div class="dashboard-overview-content">
<div class="dashboard-overview-header">
<span class="dashboard-overview-label" data-i18n="dashboard.knowledgeLabel">知识</span>
<span class="dashboard-overview-status" id="dashboard-knowledge-status">-</span>
</div>
<div class="dashboard-overview-value-group">
<span class="dashboard-overview-value-large" id="dashboard-knowledge-items">-</span>
<span class="dashboard-overview-value-unit" data-i18n="dashboard.knowledgeItems">项知识</span>
<span class="dashboard-overview-value-separator">·</span>
<span class="dashboard-overview-value-normal" id="dashboard-knowledge-categories">-</span>
<span class="dashboard-overview-value-unit" data-i18n="dashboard.categoriesUnit">个分类</span>
</div>
</div>
</div>
<div class="dashboard-overview-item dashboard-overview-item-skills" role="button" tabindex="0" onclick="switchPage('skills-monitor')" onkeydown="if(event.key==='Enter'||event.key===' ') { event.preventDefault(); switchPage('skills-monitor'); }">
<span class="dashboard-overview-icon dashboard-overview-icon-skills" aria-hidden="true"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg></span>
<div class="dashboard-overview-content">
<div class="dashboard-overview-header">
<span class="dashboard-overview-label" data-i18n="dashboard.skillsLabel">Skills</span>
<span class="dashboard-overview-status" id="dashboard-skills-status">-</span>
</div>
<div class="dashboard-overview-value-group">
<span class="dashboard-overview-value-large" id="dashboard-skills-calls">-</span>
<span class="dashboard-overview-value-unit" data-i18n="dashboard.callsUnit">次调用</span>
<span class="dashboard-overview-value-separator">·</span>
<span class="dashboard-overview-value-normal" id="dashboard-skills-count">-</span>
<span class="dashboard-overview-value-unit" data-i18n="dashboard.skillUnit">个 Skill</span>
</div>
</div>
</div>
</div>
</section>
<section class="dashboard-section dashboard-section-quick dashboard-quick-inline">
<h3 class="dashboard-section-title" data-i18n="dashboard.quickLinks">快捷入口</h3>
<div class="dashboard-quick-links dashboard-quick-links-row">
<a class="dashboard-quick-link" onclick="switchPage('chat')"><span class="dashboard-quick-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg></span><span data-i18n="nav.chat">对话</span></a>
<a class="dashboard-quick-link" onclick="switchPage('tasks')"><span class="dashboard-quick-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11l3 3L22 4"></path><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path></svg></span><span data-i18n="nav.tasks">任务管理</span></a>
<a class="dashboard-quick-link" onclick="switchPage('vulnerabilities')"><span class="dashboard-quick-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg></span><span data-i18n="nav.vulnerabilities">漏洞管理</span></a>
<a class="dashboard-quick-link" onclick="switchPage('mcp-management')"><span class="dashboard-quick-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"></path></svg></span><span data-i18n="nav.mcpManagement">MCP 管理</span></a>
<a class="dashboard-quick-link" onclick="switchPage('knowledge-management')"><span class="dashboard-quick-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path></svg></span><span data-i18n="nav.knowledgeManagement">知识管理</span></a>
<a class="dashboard-quick-link" onclick="switchPage('skills-management')"><span class="dashboard-quick-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path><polyline points="14 2 14 8 20 8"></polyline></svg></span><span data-i18n="nav.skillsManagement">Skills 管理</span></a>
<a class="dashboard-quick-link" onclick="switchPage('roles-management')"><span class="dashboard-quick-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg></span><span data-i18n="nav.rolesManagement">角色管理</span></a>
</div>
</section>
</div>
<div class="dashboard-side">
<section class="dashboard-section dashboard-section-tools">
<h3 class="dashboard-section-title" data-i18n="dashboard.toolsExecCount">工具执行次数</h3>
<div class="dashboard-tools-chart-wrap">
<div class="dashboard-tools-chart-placeholder" id="dashboard-tools-pie-placeholder" data-i18n="common.noData">暂无数据</div>
<div class="dashboard-tools-bar-chart" id="dashboard-tools-bar-chart"></div>
</div>
</section>
</div>
</div>
<div class="dashboard-cta-block">
<div class="dashboard-cta-content">
<div class="dashboard-cta-icon" aria-hidden="true">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>
</div>
<div class="dashboard-cta-copy">
<p class="dashboard-cta-text" data-i18n="dashboard.ctaTitle">开始你的安全之旅</p>
<p class="dashboard-cta-sub" data-i18n="dashboard.ctaSub">在对话中描述目标AI 将协助执行扫描与漏洞分析</p>
</div>
</div>
<button class="dashboard-cta-btn" onclick="switchPage('chat')">
<span data-i18n="dashboard.goToChat">前往对话</span>
<span class="dashboard-cta-btn-arrow" aria-hidden="true"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 12h14M12 5l7 7-7 7"/></svg></span>
</button>
</div>
</div>
</div>
</div>
<!-- 对话页面 -->
<div id="page-chat" class="page">
<div class="chat-page-layout">
<!-- 历史对话侧边栏(可折叠,与主导航侧边栏类似) -->
<aside class="conversation-sidebar" id="conversation-sidebar">
<!-- 头部一行折叠与「新对话」并排避免绝对定位重叠flex 为最佳实践) -->
<div class="sidebar-header conversation-sidebar-header">
<button type="button" class="new-chat-btn" onclick="startNewConversation()">
<span>+</span> <span data-i18n="chat.newChat">新对话</span>
</button>
<button type="button" class="conversation-sidebar-collapse-btn" onclick="toggleConversationSidebar()" data-i18n="chat.toggleConversationPanel" data-i18n-attr="title" data-i18n-skip-text="true" title="折叠/展开对话列表" aria-label="折叠/展开对话列表">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M15 18l-6-6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
<div class="sidebar-content">
<!-- 全局搜索 -->
<div class="conversation-search-box" style="margin-bottom: 16px; margin-top: 16px;">
<input type="text" id="conversation-search-input" data-i18n="chat.searchHistory" data-i18n-attr="placeholder" placeholder="搜索历史记录..."
oninput="handleConversationSearch(this.value)"
onkeypress="if(event.key === 'Enter') handleConversationSearch(this.value)" />
<button class="conversation-search-clear" id="conversation-search-clear"
onclick="clearConversationSearch()" style="display: none;" data-i18n="common.clearSearch" data-i18n-attr="title" title="清除搜索">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
<path d="M15 9l-6 6M9 9l6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</button>
</div>
<!-- 对话分组 -->
<div class="conversation-groups-section">
<div class="section-header">
<span class="section-title" data-i18n="chat.conversationGroups">对话分组</span>
<button class="add-group-btn" onclick="showCreateGroupModal()" data-i18n="chat.addGroup" data-i18n-attr="title" data-i18n-skip-text="true" title="新建分组">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
<div id="conversation-groups-list" class="conversation-groups-list"></div>
</div>
<!-- 最近对话 -->
<div class="recent-conversations-section">
<div class="section-header">
<span class="section-title" data-i18n="chat.recentConversations">最近对话</span>
<button class="batch-manage-btn" onclick="showBatchManageModal()" data-i18n="chat.batchManage" data-i18n-attr="title" data-i18n-skip-text="true" title="批量管理">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="3" y1="12" x2="21" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
<line x1="3" y1="6" x2="21" y2="6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
<line x1="3" y1="18" x2="21" y2="18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
<circle cx="8" cy="6" r="1" fill="currentColor"/>
<circle cx="8" cy="12" r="1" fill="currentColor"/>
<circle cx="8" cy="18" r="1" fill="currentColor"/>
</svg>
</button>
</div>
<div id="conversations-list" class="conversations-list"></div>
</div>
</div>
</aside>
<!-- 分组详情页面 -->
<div id="group-detail-page" class="group-detail-page" style="display: none;">
<div class="group-detail-header">
<button class="back-btn" onclick="exitGroupDetail()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19 12H5M12 19l-7-7 7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<h2 id="group-detail-title" class="group-detail-title"></h2>
<div class="group-detail-actions">
<button class="group-action-btn" onclick="toggleGroupSearch()" data-i18n="chatGroup.search" data-i18n-attr="title" title="搜索" id="group-search-toggle-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="11" cy="11" r="8" stroke="currentColor" stroke-width="2"/>
<path d="m21 21-4.35-4.35" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</button>
<button class="group-action-btn" onclick="editGroup()" data-i18n="chatGroup.edit" data-i18n-attr="title" 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="group-action-btn delete-btn" onclick="deleteGroup()" data-i18n="chatGroup.delete" data-i18n-attr="title" 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 id="group-search-container" class="group-search-container" style="display: none;">
<div class="group-search-input-wrapper">
<input type="text" id="group-search-input" class="group-search-input" data-i18n="chat.searchInGroup" data-i18n-attr="placeholder" placeholder="搜索分组中的对话..." onkeyup="handleGroupSearchInput(event)" oninput="handleGroupSearchInput(event)">
<button class="group-search-clear-btn" onclick="clearGroupSearch()" data-i18n="common.clearSearch" data-i18n-attr="title" title="清除搜索" id="group-search-clear-btn" style="display: none;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
<path d="m8 8 8 8M16 8l-8 8" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</button>
</div>
</div>
<div class="group-detail-content">
<div id="group-conversations-list" class="group-conversations-list"></div>
</div>
</div>
<!-- 对话界面 -->
<div class="chat-container">
<!-- 会话顶部栏(只在有会话选中时显示) -->
<div id="conversation-header" class="conversation-header" style="display: none;">
<div class="conversation-header-content">
<button id="attack-chain-btn" class="attack-chain-btn" data-i18n="chat.viewAttackChain" data-i18n-attr="title" data-i18n-skip-text="true" title="查看攻击链" disabled>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5 13.5l3-3M8 8H5a4 4 0 1 0 0 8h3m8-8h3a4 4 0 0 1 0 8h-3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span data-i18n="chat.attackChain">攻击链</span>
</button>
</div>
</div>
<div id="active-tasks-bar" class="active-tasks-bar"></div>
<div id="chat-messages" class="chat-messages"></div>
<div id="chat-input-container" class="chat-input-container">
<div class="role-selector-wrapper">
<button id="role-selector-btn" class="role-selector-btn" onclick="toggleRoleSelectionPanel()" data-i18n="chat.selectRole" data-i18n-attr="title" title="选择角色">
<span id="role-selector-icon" class="role-selector-icon">🔵</span>
<span id="role-selector-text" class="role-selector-text" data-i18n="chat.defaultRole">默认</span>
<svg class="role-selector-arrow" width="10" height="10" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<!-- 角色选择下拉面板 -->
<div id="role-selection-panel" class="role-selection-panel" style="display: none;">
<div class="role-selection-panel-header">
<h3 class="role-selection-panel-title" data-i18n="chatGroup.rolePanelTitle">选择角色</h3>
<button class="role-selection-panel-close" onclick="closeRoleSelectionPanel()" data-i18n="common.close" data-i18n-attr="title" title="关闭">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
<div id="role-selection-list" class="role-selection-list-main"></div>
</div>
</div>
<div class="chat-input-with-files">
<div id="chat-file-list" class="chat-file-list" aria-label="已选文件列表"></div>
<div class="chat-input-field">
<textarea id="chat-input" data-i18n="chat.inputPlaceholder" data-i18n-attr="placeholder" data-i18n-skip-text="true" placeholder="输入测试目标或命令... (输入 @ 选择工具 | Shift+Enter 换行Enter 发送)" rows="1"></textarea>
<div id="mention-suggestions" class="mention-suggestions" role="listbox" aria-label="工具提及候选"></div>
</div>
</div>
<input type="file" id="chat-file-input" class="chat-file-input-hidden" multiple accept="*" data-i18n="chat.selectFile" data-i18n-attr="title" title="选择文件">
<button type="button" class="chat-upload-btn" onclick="document.getElementById('chat-file-input').click()" data-i18n="chat.uploadFile" data-i18n-attr="title" title="上传文件(可多选或拖拽到此处)">
<svg width="18" height="18" 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-4M17 8l-5-5-5 5M12 3v12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<button class="send-btn" onclick="sendMessage()">
<span data-i18n="chat.send">发送</span>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 12h14M12 5l7 7-7 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
</div>
</div>
</div>
<!-- MCP状态监控页面 -->
<div id="page-mcp-monitor" class="page">
<div class="page-header">
<h2 data-i18n="mcp.monitorTitle">MCP 状态监控</h2>
<button class="btn-secondary" onclick="refreshMonitorPanel()"><span data-i18n="common.refresh">刷新</span></button>
</div>
<div class="page-content">
<div class="monitor-sections">
<section class="monitor-section monitor-overview">
<div class="section-header">
<h3 data-i18n="mcp.execStats">执行统计</h3>
</div>
<div id="monitor-stats" class="monitor-stats-grid">
<div class="monitor-empty" data-i18n="mcpMonitor.loading">加载中...</div>
</div>
</section>
<section class="monitor-section monitor-executions">
<div class="section-header">
<h3 data-i18n="mcp.latestExecutions">最新执行记录</h3>
<div class="section-actions">
<label>
<span data-i18n="mcp.toolSearch">工具搜索</span>
<input type="text" id="monitor-tool-filter" data-i18n="mcp.toolSearchPlaceholder" data-i18n-attr="placeholder" placeholder="输入工具名称..." oninput="handleToolFilterInput()" onkeydown="if(event.key==='Enter') applyMonitorFilters()" />
</label>
<label>
<span data-i18n="mcp.statusFilter">状态筛选</span>
<select id="monitor-status-filter" onchange="applyMonitorFilters()">
<option value="all" data-i18n="mcp.filterAll">全部</option>
<option value="completed" data-i18n="mcpMonitor.statusCompleted">已完成</option>
<option value="running" data-i18n="mcpMonitor.statusRunning">执行中</option>
<option value="failed" data-i18n="mcpMonitor.statusFailed">失败</option>
</select>
</label>
</div>
</div>
<div id="monitor-batch-actions" class="monitor-batch-actions" style="display: none;">
<div class="batch-actions-info">
<span id="monitor-selected-count">已选择 0 项</span>
</div>
<div class="batch-actions-buttons">
<button class="btn-secondary" onclick="selectAllExecutions()" data-i18n="mcp.selectAll">全选</button>
<button class="btn-secondary" onclick="deselectAllExecutions()" data-i18n="mcpMonitor.deselectAll">取消全选</button>
<button class="btn-secondary btn-delete" onclick="batchDeleteExecutions()" data-i18n="mcp.deleteSelected">批量删除</button>
</div>
</div>
<div id="monitor-executions" class="monitor-table-container">
<div class="monitor-empty" data-i18n="mcpMonitor.loading">加载中...</div>
</div>
</section>
</div>
</div>
</div>
<!-- MCP管理页面 -->
<div id="page-mcp-management" class="page">
<div class="page-header">
<h2 data-i18n="mcp.managementTitle">MCP 管理</h2>
<div class="page-header-actions">
<button class="btn-secondary" onclick="loadExternalMCPs()"><span data-i18n="common.refresh">刷新</span></button>
<button class="btn-primary" onclick="showAddExternalMCPModal()" data-i18n="mcp.addExternalMCP">添加外部MCP</button>
</div>
</div>
<div class="page-content">
<!-- MCP工具配置 -->
<div class="settings-section" style="margin-bottom: 32px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<h3 style="margin: 0;" data-i18n="mcp.toolConfig">MCP 工具配置</h3>
<button class="btn-primary" onclick="saveToolsConfig()" data-i18n="mcp.saveToolConfig">保存工具配置</button>
</div>
<div class="tools-controls">
<div class="tools-actions">
<button class="btn-secondary" onclick="selectAllTools()" data-i18n="mcp.selectAll">全选</button>
<button class="btn-secondary" onclick="deselectAllTools()" data-i18n="mcp.deselectAll">全不选</button>
<div class="search-box">
<input type="text" id="tools-search" data-i18n="mcp.toolSearchPlaceholder" data-i18n-attr="placeholder" placeholder="搜索工具..." onkeypress="handleSearchKeyPress(event)" oninput="if(this.value.trim() === '') clearSearch()" />
<button class="btn-search" onclick="searchTools()" data-i18n="common.search" data-i18n-attr="title" title="搜索">🔍</button>
</div>
<div class="tools-stats" id="tools-stats"></div>
</div>
<div id="tools-list" class="tools-list"></div>
</div>
</div>
<!-- 外部MCP配置 -->
<div class="settings-section">
<h3 data-i18n="mcp.externalConfig">外部 MCP 配置</h3>
<div class="external-mcp-controls">
<div class="external-mcp-actions">
<div class="external-mcp-stats" id="external-mcp-stats"></div>
</div>
<div id="external-mcp-list" class="external-mcp-list"></div>
</div>
</div>
</div>
</div>
<!-- 知识管理页面 -->
<div id="page-knowledge-management" class="page">
<div class="page-header">
<h2 data-i18n="knowledge.title">知识管理</h2>
<div class="page-header-actions">
<button class="btn-secondary" onclick="refreshKnowledgeBase()" data-i18n="common.refresh">刷新</button>
<button class="btn-secondary" onclick="rebuildKnowledgeIndex()" data-i18n="knowledge.rebuildIndex">重建索引</button>
<button class="btn-primary" onclick="showAddKnowledgeItemModal()" data-i18n="knowledge.addKnowledge">添加知识</button>
</div>
</div>
<div class="page-content">
<div class="knowledge-controls">
<div class="knowledge-stats-bar" id="knowledge-stats">
<div class="knowledge-stat-item">
<span class="knowledge-stat-label" data-i18n="knowledge.totalItems">总知识项</span>
<span class="knowledge-stat-value">-</span>
</div>
<div class="knowledge-stat-item">
<span class="knowledge-stat-label" data-i18n="knowledge.categories">分类数</span>
<span class="knowledge-stat-value">-</span>
</div>
<div class="knowledge-stat-item">
<span class="knowledge-stat-label" data-i18n="knowledgePage.totalContent">总内容</span>
<span class="knowledge-stat-value">-</span>
</div>
</div>
<div id="knowledge-index-progress" style="display: none; margin-bottom: 16px;"></div>
<div class="knowledge-filters">
<label>
<span data-i18n="knowledgePage.categoryFilter">分类筛选</span>
<div class="custom-select-wrapper">
<div class="custom-select" id="knowledge-category-filter-wrapper">
<div class="custom-select-trigger" id="knowledge-category-filter-trigger">
<span data-i18n="knowledgePage.all">全部</span>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="custom-select-dropdown" id="knowledge-category-filter-dropdown">
<div class="custom-select-option" data-value="" onclick="selectKnowledgeCategory('')" data-i18n="knowledgePage.all">全部</div>
</div>
</div>
</div>
</label>
<div class="search-box">
<input type="text" id="knowledge-search" data-i18n="knowledgePage.searchPlaceholder" data-i18n-attr="placeholder" placeholder="搜索知识..." oninput="handleKnowledgeSearchInput()" onkeydown="if(event.key==='Enter') searchKnowledgeItems()" />
<button class="btn-search" onclick="searchKnowledgeItems()" data-i18n="common.search" data-i18n-attr="title" title="搜索">🔍</button>
</div>
</div>
</div>
<div id="knowledge-items-list" class="knowledge-items-list">
<div class="loading-spinner" data-i18n="knowledgePage.loading">加载中...</div>
</div>
<div id="knowledge-pagination"></div>
</div>
</div>
<!-- 知识检索历史页面 -->
<div id="page-knowledge-retrieval-logs" class="page">
<div class="page-header">
<h2 data-i18n="knowledge.retrievalLogs">检索历史</h2>
<button class="btn-secondary" onclick="refreshRetrievalLogs()" data-i18n="common.refresh">刷新</button>
</div>
<div class="page-content">
<div class="retrieval-logs-controls">
<div class="retrieval-stats-bar" id="retrieval-stats">
<div class="retrieval-stat-item">
<span class="retrieval-stat-label" data-i18n="retrievalLogs.totalRetrievals">总检索次数</span>
<span class="retrieval-stat-value">-</span>
</div>
<div class="retrieval-stat-item">
<span class="retrieval-stat-label" data-i18n="retrievalLogs.successRetrievals">成功检索</span>
<span class="retrieval-stat-value">-</span>
</div>
<div class="retrieval-stat-item">
<span class="retrieval-stat-label" data-i18n="retrievalLogs.successRate">成功率</span>
<span class="retrieval-stat-value">-</span>
</div>
<div class="retrieval-stat-item">
<span class="retrieval-stat-label" data-i18n="retrievalLogs.retrievedItems">检索到知识项</span>
<span class="retrieval-stat-value">-</span>
</div>
</div>
<div class="retrieval-logs-filters">
<label>
<span data-i18n="retrievalLogs.conversationId">对话ID</span>
<input type="text" id="retrieval-logs-conversation-id" data-i18n="retrievalLogs.optionalConversation" data-i18n-attr="placeholder" placeholder="可选:筛选特定对话" />
</label>
<label>
<span data-i18n="retrievalLogs.messageId">消息ID</span>
<input type="text" id="retrieval-logs-message-id" data-i18n="retrievalLogs.optionalMessage" data-i18n-attr="placeholder" placeholder="可选:筛选特定消息" />
</label>
<button class="btn-secondary" onclick="filterRetrievalLogs()" data-i18n="retrievalLogs.filter">筛选</button>
</div>
</div>
<div id="retrieval-logs-list" class="retrieval-logs-list">
<div class="loading-spinner" data-i18n="retrievalLogs.loading">加载中...</div>
</div>
</div>
</div>
<!-- 信息收集页面 -->
<div id="page-info-collect" class="page">
<div class="page-header">
<h2 data-i18n="infoCollectPage.title">信息收集</h2>
<div class="page-header-actions">
<button class="btn-secondary" onclick="resetFofaForm()" data-i18n="infoCollectPage.reset">重置</button>
<button class="btn-primary" onclick="submitFofaSearch()" data-i18n="infoCollectPage.confirm">确定</button>
</div>
</div>
<div class="page-content">
<div class="info-collect-panel">
<div class="info-collect-form">
<div class="form-group">
<label for="fofa-query" data-i18n="infoCollectPage.fofaQuerySyntax">FOFA 查询语法</label>
<textarea id="fofa-query" class="info-collect-query-input" rows="1" data-i18n="infoCollect.queryPlaceholder" data-i18n-attr="placeholder" placeholder='例如app="Apache" && country="CN"'></textarea>
<small class="form-hint" data-i18n="infoCollectPage.formHint">查询语法参考 FOFA 文档,支持 && / || / () 等。</small>
<div class="info-collect-presets" aria-label="FOFA 查询示例" data-i18n="infoCollectPage.queryPresetsAria" data-i18n-attr="aria-label" data-i18n-skip-text="true">
<button class="preset-chip" type="button" onclick="applyFofaQueryPreset('app=&quot;Apache&quot; &amp;&amp; country=&quot;CN&quot;')" data-i18n="infoCollectPage.presetApache" data-i18n-attr="title" data-i18n-title="infoCollectPage.fillExample" title="填入示例">Apache + 中国</button>
<button class="preset-chip" type="button" onclick="applyFofaQueryPreset('title=&quot;登录&quot; &amp;&amp; country=&quot;CN&quot;')" data-i18n="infoCollectPage.presetLogin" data-i18n-attr="title" data-i18n-title="infoCollectPage.fillExample" title="填入示例">登录页 + 中国</button>
<button class="preset-chip" type="button" onclick="applyFofaQueryPreset('domain=&quot;example.com&quot;')" data-i18n="infoCollectPage.presetDomain" data-i18n-attr="title" data-i18n-title="infoCollectPage.fillExample" title="填入示例">指定域名</button>
<button class="preset-chip" type="button" onclick="applyFofaQueryPreset('ip=&quot;1.1.1.1&quot;')" data-i18n="infoCollectPage.presetIp" data-i18n-attr="title" data-i18n-title="infoCollectPage.fillExample" title="填入示例">指定 IP</button>
</div>
</div>
<div class="form-group">
<label for="fofa-nl" data-i18n="infoCollectPage.naturalLanguage">自然语言AI 解析为 FOFA 语法)</label>
<div class="info-collect-nl-row">
<textarea id="fofa-nl" class="info-collect-query-input" rows="1" data-i18n="infoCollectPage.nlPlaceholder" data-i18n-attr="placeholder" placeholder="例如:找美国 Missouri 的 Apache 站点,标题包含 Home"></textarea>
<button id="fofa-nl-parse-btn" class="btn-secondary" type="button" onclick="parseFofaNaturalLanguage()" data-i18n="infoCollectPage.parseBtn" data-i18n-attr="title" data-i18n-title="infoCollectPage.parseBtnTitle" title="将自然语言解析为 FOFA 查询语法">AI 解析</button>
</div>
<div id="fofa-nl-status" class="fofa-nl-status muted" style="display: none;" aria-live="polite"></div>
<small class="form-hint" data-i18n="infoCollectPage.parseHint">解析后会弹窗展示 FOFA 语法(可编辑),确认无误后再填入查询框并执行查询。</small>
</div>
<div class="info-collect-form-row">
<div class="form-group">
<label for="fofa-size" data-i18n="infoCollectPage.returnCount">返回数量</label>
<input type="number" id="fofa-size" min="1" max="10000" value="100" />
</div>
<div class="form-group">
<label for="fofa-page" data-i18n="infoCollectPage.pageNum">页码</label>
<input type="number" id="fofa-page" min="1" value="1" />
</div>
<div class="form-group">
<label class="checkbox-label" style="margin-top: 24px;">
<input type="checkbox" id="fofa-full" class="modern-checkbox" />
<span class="checkbox-custom"></span>
<span class="checkbox-text" data-i18n="infoCollectPage.fullLabel">full</span>
</label>
</div>
</div>
<div class="form-group">
<label for="fofa-fields" data-i18n="infoCollectPage.returnFields">返回字段名(逗号分隔)</label>
<input type="text" id="fofa-fields" value="host,ip,port,domain,title,protocol,country,province,city,server" />
<div class="info-collect-presets" aria-label="FOFA 字段模板" data-i18n="infoCollectPage.fieldsPresetsAria" data-i18n-attr="aria-label" data-i18n-skip-text="true">
<button class="preset-chip" type="button" onclick="applyFofaFieldsPreset('host,ip,port,domain')" data-i18n="infoCollectPage.minFields" data-i18n-attr="title" data-i18n-title="infoCollectPage.minFieldsTitle" title="适合快速导出目标">最小字段</button>
<button class="preset-chip" type="button" onclick="applyFofaFieldsPreset('host,title,ip,port,domain,protocol,server,icp,country,province,city')" data-i18n="infoCollectPage.webCommon" data-i18n-attr="title" data-i18n-title="infoCollectPage.webCommonTitle" title="适合浏览和筛选">Web 常用</button>
<button class="preset-chip" type="button" onclick="applyFofaFieldsPreset('host,ip,port,domain,title,protocol,country,province,city,server,as_number,as_organization,icp,header,banner')" data-i18n="infoCollectPage.intelEnhanced" data-i18n-attr="title" data-i18n-title="infoCollectPage.intelEnhancedTitle" title="更偏指纹/情报">情报增强</button>
</div>
</div>
</div>
</div>
<div class="info-collect-results">
<div class="info-collect-results-header">
<div class="info-collect-results-header-left">
<div class="info-collect-results-title" data-i18n="infoCollectPage.queryResults">查询结果</div>
<div class="info-collect-results-meta" id="fofa-results-meta">-</div>
</div>
<div class="info-collect-results-toolbar" aria-label="结果工具条" data-i18n="infoCollectPage.resultsToolbarAria" data-i18n-attr="aria-label" data-i18n-skip-text="true">
<div class="info-collect-selected" id="fofa-selected-meta" data-i18n="infoCollectPage.selectedRowsZero">已选择 0 条</div>
<button class="btn-secondary btn-small" type="button" onclick="toggleFofaColumnsPanel()" data-i18n="infoCollectPage.showHideColumns" data-i18n-attr="title" title="显示/隐藏字段"><span data-i18n="infoCollectPage.columns"></span></button>
<button class="btn-secondary btn-small" type="button" onclick="exportFofaResults('csv')" data-i18n="infoCollectPage.exportCsvTitle" data-i18n-attr="title" title="导出当前结果为 CSVUTF-8兼容中文"><span data-i18n="infoCollectPage.exportCsv">导出 CSV</span></button>
<button class="btn-secondary btn-small" type="button" onclick="exportFofaResults('json')" data-i18n="infoCollectPage.exportJsonTitle" data-i18n-attr="title" title="导出当前结果为 JSON"><span data-i18n="infoCollectPage.exportJson">导出 JSON</span></button>
<button class="btn-secondary btn-small" type="button" onclick="exportFofaResults('xlsx')" data-i18n="infoCollectPage.exportXlsxTitle" data-i18n-attr="title" title="导出当前结果为 Excel"><span data-i18n="infoCollectPage.exportXlsx">导出 XLSX</span></button>
<button class="btn-primary btn-small" type="button" onclick="batchScanSelectedFofaRows()" data-i18n="infoCollectPage.batchScanTitle" data-i18n-attr="title" title="将所选行创建为批量任务队列"><span data-i18n="infoCollectPage.batchScan">批量扫描</span></button>
</div>
</div>
<div class="info-collect-results-table-wrap">
<!-- 字段显示/隐藏面板 -->
<div id="fofa-columns-panel" class="info-collect-columns-panel" style="display: none;">
<div class="info-collect-columns-panel-header">
<div class="info-collect-columns-title" data-i18n="infoCollectPage.showColumns">显示字段</div>
<div class="info-collect-columns-actions">
<button class="btn-secondary btn-small" type="button" onclick="showAllFofaColumns()" data-i18n="infoCollectPage.columnsPanelAll">全选</button>
<button class="btn-secondary btn-small" type="button" onclick="hideAllFofaColumns()" data-i18n="infoCollectPage.columnsPanelNone">全不选</button>
<button class="btn-secondary btn-small" type="button" onclick="closeFofaColumnsPanel()" data-i18n="infoCollectPage.columnsPanelClose">关闭</button>
</div>
</div>
<div id="fofa-columns-list" class="info-collect-columns-list"></div>
</div>
<table class="info-collect-table">
<thead id="fofa-results-thead"></thead>
<tbody id="fofa-results-tbody">
<tr><td class="muted" style="padding: 16px;" data-i18n="common.noData">暂无数据</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- 漏洞管理页面 -->
<div id="page-vulnerabilities" class="page">
<div class="page-header">
<h2 data-i18n="vulnerability.title">漏洞管理</h2>
<div class="page-header-actions">
<button class="btn-secondary" onclick="refreshVulnerabilities()" data-i18n="common.refresh">刷新</button>
<button class="btn-primary" onclick="showAddVulnerabilityModal()" data-i18n="vulnerability.addVuln">添加漏洞</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" data-i18n="vulnerabilityPage.statTotal">总漏洞数</div>
<div class="stat-value" id="stat-total">-</div>
</div>
<div class="stat-card stat-critical">
<div class="stat-label" data-i18n="dashboard.severityCritical">严重</div>
<div class="stat-value" id="stat-critical">-</div>
</div>
<div class="stat-card stat-high">
<div class="stat-label" data-i18n="dashboard.severityHigh">高危</div>
<div class="stat-value" id="stat-high">-</div>
</div>
<div class="stat-card stat-medium">
<div class="stat-label" data-i18n="dashboard.severityMedium">中危</div>
<div class="stat-value" id="stat-medium">-</div>
</div>
<div class="stat-card stat-low">
<div class="stat-label" data-i18n="dashboard.severityLow">低危</div>
<div class="stat-value" id="stat-low">-</div>
</div>
<div class="stat-card stat-info">
<div class="stat-label" data-i18n="dashboard.severityInfo">信息</div>
<div class="stat-value" id="stat-info">-</div>
</div>
</div>
</div>
<!-- 筛选和搜索 -->
<div class="vulnerability-controls">
<div class="vulnerability-filters">
<label>
<span data-i18n="vulnerabilityPage.vulnId">漏洞ID</span>
<input type="text" id="vulnerability-id-filter" data-i18n="vulnerabilityPage.searchVulnId" data-i18n-attr="placeholder" placeholder="搜索漏洞ID" />
</label>
<label>
<span data-i18n="vulnerabilityPage.conversationId">会话ID</span>
<input type="text" id="vulnerability-conversation-filter" data-i18n="vulnerabilityPage.filterConversation" data-i18n-attr="placeholder" placeholder="筛选特定会话" />
</label>
<label>
<span data-i18n="vulnerabilityPage.severity">严重程度</span>
<select id="vulnerability-severity-filter">
<option value="" data-i18n="knowledgePage.all">全部</option>
<option value="critical" data-i18n="dashboard.severityCritical">严重</option>
<option value="high" data-i18n="dashboard.severityHigh">高危</option>
<option value="medium" data-i18n="dashboard.severityMedium">中危</option>
<option value="low" data-i18n="dashboard.severityLow">低危</option>
<option value="info" data-i18n="dashboard.severityInfo">信息</option>
</select>
</label>
<label>
<span data-i18n="vulnerabilityPage.status">状态</span>
<select id="vulnerability-status-filter">
<option value="" data-i18n="knowledgePage.all">全部</option>
<option value="open" data-i18n="vulnerabilityPage.statusOpen">待处理</option>
<option value="confirmed" data-i18n="vulnerabilityPage.statusConfirmed">已确认</option>
<option value="fixed" data-i18n="vulnerabilityPage.statusFixed">已修复</option>
<option value="false_positive" data-i18n="vulnerabilityPage.statusFalsePositive">误报</option>
</select>
</label>
<button class="btn-secondary" onclick="filterVulnerabilities()" data-i18n="vulnerabilityPage.filter">筛选</button>
<button class="btn-secondary" onclick="clearVulnerabilityFilters()" data-i18n="vulnerabilityPage.clear">清除</button>
</div>
</div>
<!-- 漏洞列表 -->
<div id="vulnerabilities-list" class="vulnerabilities-list">
<div class="loading-spinner" data-i18n="vulnerabilityPage.loading">加载中...</div>
</div>
<!-- 分页控件 -->
<div id="vulnerability-pagination" class="pagination-container pagination-fixed"></div>
</div>
</div>
<!-- WebShell 管理页面 -->
<div id="page-webshell" class="page">
<div class="page-header">
<h2 data-i18n="webshell.title">WebShell 管理</h2>
<div class="page-header-actions">
<button class="btn-primary" onclick="showAddWebshellModal()" data-i18n="webshell.addConnection">添加连接</button>
</div>
</div>
<div class="page-content webshell-page-content">
<div class="webshell-layout">
<div id="webshell-sidebar" class="webshell-sidebar">
<div class="webshell-sidebar-header" data-i18n="webshell.connections">连接列表</div>
<div id="webshell-list" class="webshell-list">
<div class="webshell-empty" data-i18n="webshell.noConnections">暂无连接,请点击「添加连接」</div>
</div>
</div>
<div id="webshell-resize-handle" class="webshell-resize-handle" title="拖拽调整宽度"></div>
<div class="webshell-main">
<div id="webshell-workspace" class="webshell-workspace">
<div class="webshell-workspace-placeholder" data-i18n="webshell.selectOrAdd">请从左侧选择连接,或添加新的 WebShell 连接</div>
</div>
</div>
</div>
</div>
</div>
<!-- 任务管理页面 -->
<div id="page-tasks" class="page">
<div class="page-header">
<h2 data-i18n="tasks.title">任务管理</h2>
<div class="page-header-actions">
<button class="btn-primary" onclick="showBatchImportModal()" data-i18n="tasks.newTask">新建任务</button>
<label class="auto-refresh-toggle">
<input type="checkbox" id="tasks-auto-refresh" checked onchange="toggleTasksAutoRefresh(this.checked)">
<span data-i18n="tasks.autoRefresh">自动刷新</span>
</label>
<button class="btn-secondary" onclick="refreshBatchQueues()" data-i18n="common.refresh">刷新</button>
</div>
</div>
<div class="page-content">
<!-- 批量任务队列列表 -->
<div class="batch-queues-section" id="batch-queues-section" style="display: none;">
<!-- 筛选控件 -->
<div class="batch-queues-filters tasks-filters">
<label>
<span data-i18n="tasksPage.statusFilter">状态筛选</span>
<select id="batch-queues-status-filter" onchange="filterBatchQueues()">
<option value="all" data-i18n="knowledgePage.all">全部</option>
<option value="pending" data-i18n="tasksPage.statusPending">待执行</option>
<option value="running" data-i18n="dashboard.executing">执行中</option>
<option value="paused" data-i18n="tasksPage.statusPaused">已暂停</option>
<option value="completed" data-i18n="dashboard.completed">已完成</option>
<option value="cancelled" data-i18n="tasksPage.statusCancelled">已取消</option>
</select>
</label>
<label style="flex: 1; max-width: 300px;">
<span data-i18n="tasksPage.searchQueuePlaceholder">搜索队列ID、标题或创建时间</span>
<input type="text" id="batch-queues-search" data-i18n="tasksPage.searchKeywordPlaceholder" data-i18n-attr="placeholder" placeholder="输入关键字搜索..."
oninput="filterBatchQueues()">
</label>
</div>
<div id="batch-queues-list" class="batch-queues-list"></div>
<!-- 分页控件 -->
<div id="batch-queues-pagination" class="pagination-container pagination-fixed"></div>
</div>
</div>
</div>
<!-- 角色管理页面 -->
<div id="page-roles-management" class="page">
<div class="page-header">
<h2 data-i18n="roles.title">角色管理</h2>
<div class="page-header-actions">
<button class="btn-secondary" onclick="refreshRoles()" data-i18n="common.refresh">刷新</button>
<button class="btn-primary" onclick="showAddRoleModal()" data-i18n="roles.createRole">创建角色</button>
</div>
</div>
<div class="page-content">
<div class="roles-controls">
<div class="roles-search-box">
<input type="text" id="roles-search" data-i18n="roles.searchPlaceholder" data-i18n-attr="placeholder" placeholder="搜索角色..." oninput="handleRolesSearchInput()" onkeydown="if(event.key==='Enter') searchRoles()" />
<button class="roles-search-clear" id="roles-search-clear" onclick="clearRolesSearch()" style="display: none;" data-i18n="common.clearSearch" data-i18n-attr="title" title="清除搜索">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
<path d="M15 9l-6 6M9 9l6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</button>
</div>
</div>
<div id="roles-list" class="roles-grid">
<div class="loading-spinner" data-i18n="knowledgePage.loading">加载中...</div>
</div>
</div>
</div>
<!-- Skills状态监控页面 -->
<div id="page-skills-monitor" class="page">
<div class="page-header">
<h2 data-i18n="skills.monitorTitle">Skills状态监控</h2>
<div class="page-header-actions">
<button class="btn-secondary" onclick="refreshSkillsMonitor()" data-i18n="common.refresh">刷新</button>
</div>
</div>
<div class="page-content">
<div class="monitor-sections">
<section class="monitor-section monitor-overview">
<div class="section-header">
<h3 data-i18n="skills.callStats">调用统计</h3>
</div>
<div id="skills-stats" class="monitor-stats-grid">
<div class="monitor-empty" data-i18n="skillsPage.loading">加载中...</div>
</div>
</section>
<section class="monitor-section monitor-executions">
<div class="section-header">
<h3 data-i18n="skillsPage.skillsCallStats">Skills调用统计</h3>
<div class="section-actions">
<button class="btn-secondary btn-small" onclick="clearSkillsStats()" data-i18n="skillsPage.clearStatsTitle" data-i18n-attr="title" data-i18n-skip-text="true" title="清空所有统计数据"><span data-i18n="skillsPage.clearStats">清空统计</span></button>
</div>
</div>
<div id="skills-monitor-list" class="monitor-table-container">
<div class="monitor-empty" data-i18n="skillsPage.loading">加载中...</div>
</div>
</section>
</div>
</div>
</div>
<!-- Skills管理页面 -->
<div id="page-skills-management" class="page">
<div class="page-header">
<h2 data-i18n="skills.title">Skills管理</h2>
<div class="page-header-actions">
<button class="btn-secondary" onclick="refreshSkills()" data-i18n="common.refresh">刷新</button>
<button class="btn-primary" onclick="showAddSkillModal()" data-i18n="skills.createSkill">创建Skill</button>
</div>
</div>
<div class="page-content page-content-with-pagination">
<div class="skills-controls">
<div class="skills-search-box">
<input type="text" id="skills-search" data-i18n="skillsPage.searchPlaceholder" data-i18n-attr="placeholder" placeholder="搜索Skills..." oninput="handleSkillsSearchInput()" onkeydown="if(event.key==='Enter') searchSkills()" />
<button class="skills-search-clear" id="skills-search-clear" onclick="clearSkillsSearch()" style="display: none;" data-i18n="common.clearSearch" data-i18n-attr="title" title="清除搜索">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
<path d="M15 9l-6 6M9 9l6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</button>
</div>
</div>
<div id="skills-list" class="skills-grid">
<div class="loading-spinner" data-i18n="skillsPage.loading">加载中...</div>
</div>
<div id="skills-pagination" class="pagination-container pagination-fixed"></div>
</div>
</div>
<!-- 系统设置页面 -->
<div id="page-settings" class="page">
<div class="page-header">
<h2 data-i18n="settings.title">系统设置</h2>
</div>
<div class="page-content settings-layout">
<!-- 左侧导航栏 -->
<aside class="settings-sidebar">
<nav class="settings-nav">
<div class="settings-nav-item active" data-section="basic" onclick="switchSettingsSection('basic')">
<span data-i18n="settings.nav.basic">基本设置</span>
</div>
<div class="settings-nav-item" data-section="robots" onclick="switchSettingsSection('robots')">
<span data-i18n="settings.nav.robots">机器人设置</span>
</div>
<div class="settings-nav-item" data-section="terminal" onclick="switchSettingsSection('terminal')">
<span data-i18n="settings.nav.terminal">终端</span>
</div>
<div class="settings-nav-item" data-section="security" onclick="switchSettingsSection('security')">
<span data-i18n="settings.nav.security">安全设置</span>
</div>
</nav>
</aside>
<!-- 右侧内容区域 -->
<div class="settings-content">
<!-- 基本设置 -->
<div id="settings-section-basic" class="settings-section-content active">
<div class="settings-section-header">
<h3 data-i18n="settingsBasic.basicTitle">基本设置</h3>
</div>
<!-- OpenAI配置 -->
<div class="settings-subsection">
<h4 data-i18n="settingsBasic.openaiConfig">OpenAI 配置</h4>
<div class="settings-form">
<div class="form-group">
<label for="openai-base-url">Base URL <span style="color: red;">*</span></label>
<input type="text" id="openai-base-url" data-i18n="settingsBasic.openaiBaseUrlPlaceholder" data-i18n-attr="placeholder" placeholder="https://api.openai.com/v1" required />
</div>
<div class="form-group">
<label for="openai-api-key">API Key <span style="color: red;">*</span></label>
<input type="password" id="openai-api-key" data-i18n="settingsBasic.openaiApiKeyPlaceholder" data-i18n-attr="placeholder" placeholder="输入OpenAI API Key" required />
</div>
<div class="form-group">
<label for="openai-model"><span data-i18n="settingsBasic.model">模型</span> <span style="color: red;">*</span></label>
<input type="text" id="openai-model" data-i18n="settingsBasic.modelPlaceholder" data-i18n-attr="placeholder" placeholder="gpt-4" required />
</div>
</div>
</div>
<!-- FOFA配置 -->
<div class="settings-subsection">
<h4 data-i18n="settingsBasic.fofaConfig">FOFA 配置</h4>
<div class="settings-form">
<div class="form-group">
<label for="fofa-base-url">Base URL</label>
<input type="text" id="fofa-base-url" data-i18n="settingsBasic.fofaBaseUrlPlaceholder" data-i18n-attr="placeholder" placeholder="https://fofa.info/api/v1/search/all可选" />
<small class="form-hint" data-i18n="settingsBasic.fofaBaseUrlHint">留空则使用默认地址。</small>
</div>
<div class="form-group">
<label for="fofa-email" data-i18n="settingsBasic.email">Email</label>
<input type="text" id="fofa-email" data-i18n="settingsBasic.fofaEmailPlaceholder" data-i18n-attr="placeholder" placeholder="输入 FOFA 账号邮箱" autocomplete="off" />
</div>
<div class="form-group">
<label for="fofa-api-key">API Key</label>
<input type="password" id="fofa-api-key" data-i18n="settingsBasic.fofaApiKeyPlaceholder" data-i18n-attr="placeholder" placeholder="输入 FOFA API Key" autocomplete="off" />
<small class="form-hint" data-i18n="settingsBasic.fofaApiKeyHint">仅保存在服务器配置中(`config.yaml`)。</small>
</div>
</div>
</div>
<!-- Agent配置 -->
<div class="settings-subsection">
<h4 data-i18n="settingsBasic.agentConfig">Agent 配置</h4>
<div class="settings-form">
<div class="form-group">
<label for="agent-max-iterations" data-i18n="settingsBasic.maxIterations">最大迭代次数</label>
<input type="number" id="agent-max-iterations" min="1" max="100" data-i18n="settingsBasic.iterationsPlaceholder" data-i18n-attr="placeholder" placeholder="30" />
</div>
</div>
</div>
<!-- 知识库配置 -->
<div class="settings-subsection">
<h4 data-i18n="settingsBasic.knowledgeConfig">知识库配置</h4>
<div class="settings-form">
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" id="knowledge-enabled" class="modern-checkbox" />
<span class="checkbox-custom"></span>
<span class="checkbox-text" data-i18n="settingsBasic.enableKnowledge">启用知识检索功能</span>
</label>
</div>
<div class="form-group">
<label for="knowledge-base-path" data-i18n="settingsBasic.knowledgeBasePath">知识库路径</label>
<input type="text" id="knowledge-base-path" data-i18n="settingsBasic.knowledgeBasePathPlaceholder" data-i18n-attr="placeholder" placeholder="knowledge_base" />
<small class="form-hint" data-i18n="settingsBasic.knowledgeBasePathHint">相对于配置文件所在目录的路径</small>
</div>
<div class="settings-subsection-header">
<h5 data-i18n="settingsBasic.embeddingConfig">嵌入模型配置</h5>
</div>
<div class="form-group">
<label for="knowledge-embedding-provider" data-i18n="settingsBasic.provider">提供商</label>
<select id="knowledge-embedding-provider">
<option value="openai">OpenAI</option>
</select>
</div>
<div class="form-group">
<label for="knowledge-embedding-base-url">Base URL</label>
<input type="text" id="knowledge-embedding-base-url" data-i18n="settingsBasic.embeddingBaseUrlPlaceholder" data-i18n-attr="placeholder" placeholder="留空则使用OpenAI配置的base_url" />
<small class="form-hint" data-i18n="settingsBasic.embeddingBaseUrlPlaceholder">留空则使用OpenAI配置的base_url</small>
</div>
<div class="form-group">
<label for="knowledge-embedding-api-key">API Key</label>
<input type="password" id="knowledge-embedding-api-key" data-i18n="settingsBasic.embeddingApiKeyPlaceholder" data-i18n-attr="placeholder" placeholder="留空则使用OpenAI配置的api_key" />
<small class="form-hint" data-i18n="settingsBasic.embeddingApiKeyPlaceholder">留空则使用OpenAI配置的api_key</small>
</div>
<div class="form-group">
<label for="knowledge-embedding-model" data-i18n="settingsBasic.modelName">模型名称</label>
<input type="text" id="knowledge-embedding-model" data-i18n="settingsBasic.embeddingModelPlaceholder" data-i18n-attr="placeholder" placeholder="text-embedding-v4" />
</div>
<div class="settings-subsection-header">
<h5 data-i18n="settingsBasic.retrievalConfig">检索配置</h5>
</div>
<div class="form-group">
<label for="knowledge-retrieval-top-k" data-i18n="settingsBasic.topK">Top-K 结果数量</label>
<input type="number" id="knowledge-retrieval-top-k" min="1" max="20" data-i18n="settingsBasic.topKPlaceholder" data-i18n-attr="placeholder" placeholder="5" />
<small class="form-hint" data-i18n="settingsBasic.topKHint">检索返回的Top-K结果数量</small>
</div>
<div class="form-group">
<label for="knowledge-retrieval-similarity-threshold" data-i18n="settingsBasic.similarityThreshold">相似度阈值</label>
<input type="number" id="knowledge-retrieval-similarity-threshold" min="0" max="1" step="0.1" data-i18n="settingsBasic.similarityPlaceholder" data-i18n-attr="placeholder" placeholder="0.7" />
<small class="form-hint" data-i18n="settingsBasic.similarityHint">相似度阈值0-1低于此值的结果将被过滤</small>
</div>
<div class="form-group">
<label for="knowledge-retrieval-hybrid-weight" data-i18n="settingsBasic.hybridWeight">混合检索权重</label>
<input type="number" id="knowledge-retrieval-hybrid-weight" min="0" max="1" step="0.1" data-i18n="settingsBasic.hybridPlaceholder" data-i18n-attr="placeholder" placeholder="0.7" />
<small class="form-hint" data-i18n="settingsBasic.hybridHint">向量检索的权重0-11.0表示纯向量检索0.0表示纯关键词检索</small>
</div>
</div>
<div class="settings-subsection-header">
<h5 data-i18n="settingsBasic.indexConfig">索引配置</h5>
</div>
<div class="form-group">
<label for="knowledge-indexing-chunk-size" data-i18n="settingsBasic.chunkSize">分块大小Chunk Size</label>
<input type="number" id="knowledge-indexing-chunk-size" min="128" max="4096" data-i18n="settingsBasic.chunkSizePlaceholder" data-i18n-attr="placeholder" placeholder="512" />
<small class="form-hint" data-i18n="settingsBasic.chunkSizeHint">每个块的最大 token 数(默认 512长文本会被分割成多个块</small>
</div>
<div class="form-group">
<label for="knowledge-indexing-chunk-overlap" data-i18n="settingsBasic.chunkOverlap">分块重叠Chunk Overlap</label>
<input type="number" id="knowledge-indexing-chunk-overlap" min="0" max="512" data-i18n="settingsBasic.chunkOverlapPlaceholder" data-i18n-attr="placeholder" placeholder="50" />
<small class="form-hint" data-i18n="settingsBasic.chunkOverlapHint">块之间的重叠 token 数(默认 50保持上下文连贯性</small>
</div>
<div class="form-group">
<label for="knowledge-indexing-max-chunks-per-item" data-i18n="settingsBasic.maxChunksPerItem">单个知识项最大块数</label>
<input type="number" id="knowledge-indexing-max-chunks-per-item" min="0" max="1000" data-i18n="settingsBasic.maxChunksPlaceholder" data-i18n-attr="placeholder" placeholder="0" />
<small class="form-hint" data-i18n="settingsBasic.maxChunksHint">单个知识项的最大块数量0 表示不限制),防止单个文件消耗过多 API 配额</small>
</div>
<div class="form-group">
<label for="knowledge-indexing-max-rpm" data-i18n="settingsBasic.maxRpm">每分钟最大请求数Max RPM</label>
<input type="number" id="knowledge-indexing-max-rpm" min="0" max="1000" data-i18n="settingsBasic.maxRpmPlaceholder" data-i18n-attr="placeholder" placeholder="0" />
<small class="form-hint" data-i18n="settingsBasic.maxRpmHint">每分钟最大请求数(默认 0 表示不限制),如 OpenAI 默认 200 RPM</small>
</div>
<div class="form-group">
<label for="knowledge-indexing-rate-limit-delay-ms" data-i18n="settingsBasic.rateLimitDelay">请求间隔延迟(毫秒)</label>
<input type="number" id="knowledge-indexing-rate-limit-delay-ms" min="0" max="10000" data-i18n="settingsBasic.rateLimitPlaceholder" data-i18n-attr="placeholder" placeholder="300" />
<small class="form-hint" data-i18n="settingsBasic.rateLimitHint">请求间隔毫秒数(默认 300用于避免 API 速率限制,设为 0 不限制</small>
</div>
<div class="form-group">
<label for="knowledge-indexing-max-retries" data-i18n="settingsBasic.maxRetries">最大重试次数</label>
<input type="number" id="knowledge-indexing-max-retries" min="0" max="10" data-i18n="settingsBasic.maxRetriesPlaceholder" data-i18n-attr="placeholder" placeholder="3" />
<small class="form-hint" data-i18n="settingsBasic.maxRetriesHint">最大重试次数(默认 3遇到速率限制或服务器错误时自动重试</small>
</div>
<div class="form-group">
<label for="knowledge-indexing-retry-delay-ms" data-i18n="settingsBasic.retryDelay">重试间隔(毫秒)</label>
<input type="number" id="knowledge-indexing-retry-delay-ms" min="0" max="10000" data-i18n="settingsBasic.retryDelayPlaceholder" data-i18n-attr="placeholder" placeholder="1000" />
<small class="form-hint" data-i18n="settingsBasic.retryDelayHint">重试间隔毫秒数(默认 1000每次重试会递增延迟</small>
</div> </div>
<div class="settings-actions">
<button class="btn-primary" onclick="applySettings()" data-i18n="settings.apply.button">应用配置</button>
</div>
</div>
<!-- 机器人设置 -->
<div id="settings-section-robots" class="settings-section-content">
<div class="settings-section-header">
<h3 data-i18n="settings.robots.title">机器人设置</h3>
<p class="settings-description" data-i18n="settings.robots.description">配置企业微信、钉钉、飞书等机器人,在手机端直接与 CyberStrikeAI 对话,无需在服务器上打开网页。</p>
</div>
<!-- 企业微信 -->
<div class="settings-subsection">
<h4 data-i18n="settings.robots.wecom.title">企业微信</h4>
<div class="settings-form">
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" id="robot-wecom-enabled" class="modern-checkbox" />
<span class="checkbox-custom"></span>
<span class="checkbox-text" data-i18n="settings.robots.wecom.enabled">启用企业微信机器人</span>
</label>
</div>
<div class="form-group">
<label for="robot-wecom-token" data-i18n="settings.robots.wecom.token">Token</label>
<input type="text" id="robot-wecom-token" data-i18n="settings.robots.wecom.tokenPlaceholder" data-i18n-attr="placeholder" placeholder="Token" autocomplete="off" />
</div>
<div class="form-group">
<label for="robot-wecom-encoding-aes-key" data-i18n="settings.robots.wecom.encodingAesKey">EncodingAESKey</label>
<input type="text" id="robot-wecom-encoding-aes-key" data-i18n="settings.robots.wecom.encodingAesKeyPlaceholder" data-i18n-attr="placeholder" placeholder="EncodingAESKey明文模式可留空" autocomplete="off" />
</div>
<div class="form-group">
<label for="robot-wecom-corp-id" data-i18n="settings.robots.wecom.corpId">CorpID</label>
<input type="text" id="robot-wecom-corp-id" data-i18n="settings.robots.wecom.corpIdPlaceholder" data-i18n-attr="placeholder" placeholder="企业 ID" autocomplete="off" />
</div>
<div class="form-group">
<label for="robot-wecom-secret" data-i18n="settings.robots.wecom.secret">Secret</label>
<input type="password" id="robot-wecom-secret" data-i18n="settings.robots.wecom.secretPlaceholder" data-i18n-attr="placeholder" placeholder="应用 Secret" autocomplete="off" />
</div>
<div class="form-group">
<label for="robot-wecom-agent-id" data-i18n="settings.robots.wecom.agentId">AgentID</label>
<input type="number" id="robot-wecom-agent-id" data-i18n="settings.robots.wecom.agentIdPlaceholder" data-i18n-attr="placeholder" placeholder="应用 AgentId" />
</div>
</div>
</div>
<!-- 钉钉 -->
<div class="settings-subsection">
<h4 data-i18n="settings.robots.dingtalk.title">钉钉</h4>
<div class="settings-form">
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" id="robot-dingtalk-enabled" class="modern-checkbox" />
<span class="checkbox-custom"></span>
<span class="checkbox-text" data-i18n="settings.robots.dingtalk.enabled">启用钉钉机器人</span>
</label>
</div>
<div class="form-group">
<label for="robot-dingtalk-client-id" data-i18n="settings.robots.dingtalk.clientIdLabel">Client ID (AppKey)</label>
<input type="text" id="robot-dingtalk-client-id" data-i18n="settings.robots.dingtalk.clientIdPlaceholder" data-i18n-attr="placeholder" placeholder="钉钉应用 AppKey" autocomplete="off" />
</div>
<div class="form-group">
<label for="robot-dingtalk-client-secret" data-i18n="settings.robots.dingtalk.clientSecretLabel">Client Secret</label>
<input type="password" id="robot-dingtalk-client-secret" data-i18n="settings.robots.dingtalk.clientSecretPlaceholder" data-i18n-attr="placeholder" placeholder="钉钉应用 Secret" autocomplete="off" />
<small class="form-hint" data-i18n="settings.robots.dingtalk.streamHint">需开启机器人能力并配置流式接入</small>
</div>
</div>
</div>
<!-- 飞书 -->
<div class="settings-subsection">
<h4 data-i18n="settings.robots.lark.title">飞书 (Lark)</h4>
<div class="settings-form">
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" id="robot-lark-enabled" class="modern-checkbox" />
<span class="checkbox-custom"></span>
<span class="checkbox-text" data-i18n="settings.robots.lark.enabled">启用飞书机器人</span>
</label>
</div>
<div class="form-group">
<label for="robot-lark-app-id" data-i18n="settings.robots.lark.appIdLabel">App ID</label>
<input type="text" id="robot-lark-app-id" data-i18n="settings.robots.lark.appIdPlaceholder" data-i18n-attr="placeholder" placeholder="飞书应用 App ID" autocomplete="off" />
</div>
<div class="form-group">
<label for="robot-lark-app-secret" data-i18n="settings.robots.lark.appSecretLabel">App Secret</label>
<input type="password" id="robot-lark-app-secret" data-i18n="settings.robots.lark.appSecretPlaceholder" data-i18n-attr="placeholder" placeholder="飞书应用 App Secret" autocomplete="off" />
</div>
<div class="form-group">
<label for="robot-lark-verify-token" data-i18n="settings.robots.lark.verifyTokenLabel">Verify Token可选</label>
<input type="text" id="robot-lark-verify-token" data-i18n="settings.robots.lark.verifyTokenPlaceholder" data-i18n-attr="placeholder" placeholder="事件订阅 Verification Token" autocomplete="off" />
</div>
</div>
</div>
<div class="settings-subsection">
<h4 data-i18n="settingsRobotsExtra.botCommandsTitle">机器人命令说明</h4>
<p class="settings-description" data-i18n="settingsRobotsExtra.botCommandsDesc">在对话中可发送以下命令(支持中英文):</p>
<ul style="color: var(--text-muted); font-size: 13px; line-height: 1.8; margin: 8px 0 0 16px;">
<li><code>帮助</code> <code>help</code><span data-i18n="settingsRobotsExtra.botCmdHelp">显示本帮助 | Show this help</span></li>
<li><code>列表</code> <code>list</code><span data-i18n="settingsRobotsExtra.botCmdList">列出所有对话标题与 ID | List conversations</span></li>
<li><code>切换 &lt;ID&gt;</code> <code>switch &lt;ID&gt;</code><span data-i18n="settingsRobotsExtra.botCmdSwitch">指定对话继续 | Switch to conversation</span></li>
<li><code>新对话</code> <code>new</code><span data-i18n="settingsRobotsExtra.botCmdNew">开启新对话 | Start new conversation</span></li>
<li><code>清空</code> <code>clear</code><span data-i18n="settingsRobotsExtra.botCmdClear">清空当前上下文 | Clear context</span></li>
<li><code>当前</code> <code>current</code><span data-i18n="settingsRobotsExtra.botCmdCurrent">显示当前对话 ID 与标题 | Show current conversation</span></li>
<li><code>停止</code> <code>stop</code><span data-i18n="settingsRobotsExtra.botCmdStop">中断当前任务 | Stop running task</span></li>
<li><code>角色</code> <code>roles</code><span data-i18n="settingsRobotsExtra.botCmdRoles">列出所有可用角色 | List roles</span></li>
<li><code>角色 &lt;&gt;</code> <code>role &lt;name&gt;</code><span data-i18n="settingsRobotsExtra.botCmdRole">切换当前角色 | Switch role</span></li>
<li><code>删除 &lt;ID&gt;</code> <code>delete &lt;ID&gt;</code><span data-i18n="settingsRobotsExtra.botCmdDelete">删除指定对话 | Delete conversation</span></li>
<li><code>版本</code> <code>version</code><span data-i18n="settingsRobotsExtra.botCmdVersion">显示当前版本号 | Show version</span></li>
</ul>
<p class="settings-description" style="margin-top: 8px;" data-i18n="settingsRobotsExtra.botCommandsFooter">除以上命令外,直接输入内容将发送给 AI 进行渗透测试/安全分析。Otherwise, send any text for AI penetration testing / security analysis.</p>
</div>
<div class="settings-actions">
<button class="btn-primary" onclick="applySettings()" data-i18n="settings.apply.button">应用配置</button>
</div>
</div>
<!-- 终端 -->
<div id="settings-section-terminal" class="settings-section-content">
<div class="settings-section-header">
<h3 data-i18n="settingsTerminal.title">终端</h3>
<p class="settings-description" data-i18n="settingsTerminal.description">在服务器上执行命令,便于运维与调试。命令在服务端执行,请勿执行敏感或破坏性操作。</p>
</div>
<div class="terminal-wrapper">
<div class="terminal-tabs">
<div class="terminal-tab active" data-tab-id="1"><span class="terminal-tab-label" onclick="switchTerminalTab(1)">终端 1</span><button type="button" class="terminal-tab-close" onclick="removeTerminalTab(1); event.stopPropagation();" data-i18n="settingsTerminal.close" data-i18n-attr="title" title="关闭">×</button></div>
<button type="button" class="terminal-tab-new" onclick="addTerminalTab()" data-i18n="settingsTerminal.newTerminal" data-i18n-attr="title" title="新终端">+</button>
</div>
<div class="terminal-panes">
<div id="terminal-pane-1" class="terminal-pane active">
<div id="terminal-container-1" class="terminal-container"></div>
</div>
</div>
</div>
</div>
<!-- 安全设置 -->
<div id="settings-section-security" class="settings-section-content">
<div class="settings-section-header">
<h3 data-i18n="settings.nav.security">安全设置</h3>
</div>
<div class="settings-subsection">
<h4 data-i18n="settingsSecurity.changePasswordTitle">修改密码</h4>
<p class="settings-description" data-i18n="settingsSecurity.changePasswordDesc">修改登录密码后,需要使用新密码重新登录。</p>
<div class="settings-form">
<div class="form-group">
<label for="auth-current-password" data-i18n="settingsSecurity.currentPassword">当前密码</label>
<input type="password" id="auth-current-password" data-i18n="settingsSecurity.currentPasswordPlaceholder" data-i18n-attr="placeholder" placeholder="输入当前登录密码" autocomplete="current-password" />
</div>
<div class="form-group">
<label for="auth-new-password" data-i18n="settingsSecurity.newPassword">新密码</label>
<input type="password" id="auth-new-password" data-i18n="settingsSecurity.newPasswordPlaceholder" data-i18n-attr="placeholder" placeholder="设置新密码(至少 8 位)" autocomplete="new-password" />
</div>
<div class="form-group">
<label for="auth-confirm-password" data-i18n="settingsSecurity.confirmPassword">确认新密码</label>
<input type="password" id="auth-confirm-password" data-i18n="settingsSecurity.confirmPasswordPlaceholder" data-i18n-attr="placeholder" placeholder="再次输入新密码" autocomplete="new-password" />
</div>
<div class="form-actions">
<button class="btn-secondary" type="button" onclick="resetPasswordForm()" data-i18n="settingsSecurity.clear">清空</button>
<button class="btn-primary change-password-submit" type="button" onclick="changePassword()" data-i18n="settingsSecurity.changePasswordBtn">修改密码</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- MCP调用详情模态框 -->
<div id="mcp-detail-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2 data-i18n="mcpDetailModal.title">工具调用详情</h2>
<span class="modal-close" onclick="closeMCPDetail()">&times;</span>
</div>
<div class="modal-body">
<div class="detail-section detail-section-overview">
<div class="detail-section-header">
<h3 data-i18n="mcpDetailModal.execInfo">执行信息</h3>
</div>
<div class="detail-info-grid">
<div class="detail-item">
<strong data-i18n="mcpDetailModal.tool">工具</strong>
<span id="detail-tool-name"></span>
</div>
<div class="detail-item">
<strong data-i18n="mcpDetailModal.status">状态</strong>
<span id="detail-status" class="status-chip status-unknown"></span>
</div>
<div class="detail-item">
<strong data-i18n="mcpDetailModal.time">时间</strong>
<span id="detail-time"></span>
</div>
<div class="detail-item">
<strong data-i18n="mcpDetailModal.executionId">执行 ID</strong>
<span id="detail-execution-id" class="mono-text"></span>
</div>
</div>
</div>
<div class="detail-section">
<div class="detail-section-header">
<h3 data-i18n="mcpDetailModal.requestParams">请求参数</h3>
<button class="btn-ghost" type="button" onclick="copyDetailBlock('detail-request', this)" data-i18n="mcpDetailModal.copyJson">复制 JSON</button>
</div>
<div class="detail-code-card">
<pre id="detail-request" class="code-block"></pre>
</div>
</div>
<div class="detail-section">
<div class="detail-section-header">
<h3 data-i18n="mcpDetailModal.responseResult">响应结果</h3>
<button class="btn-ghost" type="button" onclick="copyDetailBlock('detail-response', this)" data-i18n="mcpDetailModal.copyContent">复制内容</button>
</div>
<div class="detail-code-card">
<pre id="detail-response" class="code-block"></pre>
</div>
</div>
<div class="detail-section detail-success-wrapper" id="detail-success-section" style="display: none;">
<div class="detail-section-header">
<h3 data-i18n="mcpDetailModal.correctInfo">正确信息</h3>
<button class="btn-ghost" type="button" onclick="copyDetailBlock('detail-success', this)" data-i18n="mcpDetailModal.copyContent">复制内容</button>
</div>
<div class="detail-code-card">
<pre id="detail-success" class="code-block"></pre>
</div>
</div>
<div class="detail-section detail-error-wrapper" id="detail-error-section" style="display: none;">
<div class="detail-section-header">
<h3 data-i18n="mcpDetailModal.errorInfo">错误信息</h3>
<button class="btn-ghost" type="button" onclick="copyDetailBlock('detail-error', this)" data-i18n="mcpDetailModal.copyError">复制错误</button>
</div>
<div class="detail-code-card">
<pre id="detail-error" class="code-block error"></pre>
</div>
</div>
</div>
</div>
</div>
<!-- 外部MCP配置模态框 -->
<div id="external-mcp-modal" class="modal">
<div class="modal-content" style="max-width: 900px;">
<div class="modal-header">
<h2 id="external-mcp-modal-title" data-i18n="mcp.addExternalMCP">添加外部MCP</h2>
<span class="modal-close" onclick="closeExternalMCPModal()">&times;</span>
</div>
<div class="modal-body">
<div class="form-group">
<label for="external-mcp-json"><span data-i18n="externalMcpModal.configJson">配置JSON</span> <span style="color: red;">*</span></label>
<textarea id="external-mcp-json" rows="15" data-i18n="externalMcpModal.placeholder" data-i18n-attr="placeholder" style="font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 0.875rem; line-height: 1.5;"></textarea>
<div class="password-hint">
<strong data-i18n="externalMcpModal.formatLabel">配置格式:</strong><span data-i18n="externalMcpModal.formatDesc">JSON对象key为配置名称value为配置内容。状态通过"启动/停止"按钮控制无需在JSON中配置。</span><br>
<strong data-i18n="externalMcpModal.configExample">配置示例:</strong><br>
<strong data-i18n="externalMcpModal.stdioMode">stdio模式</strong><br>
<code data-i18n="externalMcpModal.exampleStdio" style="display: block; margin: 8px 0; padding: 8px; background: var(--bg-secondary); border-radius: 4px; white-space: pre-wrap;">{
"hexstrike-ai": {
"command": "python3",
"args": ["/path/to/script.py", "--server", "http://example.com"],
"description": "描述",
"timeout": 300
}
}</code>
<strong data-i18n="externalMcpModal.httpMode">HTTP模式</strong><br>
<code data-i18n="externalMcpModal.exampleHttp" style="display: block; margin: 8px 0; padding: 8px; background: var(--bg-secondary); border-radius: 4px; white-space: pre-wrap;">{
"cyberstrike-ai-http": {
"transport": "http",
"url": "http://127.0.0.1:8081/mcp"
}
}</code>
<strong data-i18n="externalMcpModal.sseMode">SSE模式</strong><br>
<code data-i18n="externalMcpModal.exampleSse" style="display: block; margin: 8px 0; padding: 8px; background: var(--bg-secondary); border-radius: 4px; white-space: pre-wrap;">{
"cyberstrike-ai-sse": {
"transport": "sse",
"url": "http://127.0.0.1:8081/mcp/sse"
}
}</code>
</div>
<div id="external-mcp-json-error" class="error-message" style="display: none; margin-top: 8px; padding: 8px; background: rgba(220, 53, 69, 0.1); border: 1px solid rgba(220, 53, 69, 0.3); border-radius: 4px; color: var(--error-color); font-size: 0.875rem;"></div>
</div>
<div class="form-group">
<button type="button" class="btn-secondary" onclick="formatExternalMCPJSON()" style="margin-top: 8px;" data-i18n="externalMcpModal.formatJson">格式化JSON</button>
<button type="button" class="btn-secondary" onclick="loadExternalMCPExample()" style="margin-top: 8px; margin-left: 8px;" data-i18n="externalMcpModal.loadExample">加载示例</button>
</div>
</div>
<div class="modal-footer">
<button class="btn-secondary" onclick="closeExternalMCPModal()" data-i18n="common.cancel">取消</button>
<button class="btn-primary" onclick="saveExternalMCP()" data-i18n="common.save">保存</button>
</div>
</div>
</div>
<!-- 攻击链可视化模态框 -->
<div id="attack-chain-modal" class="modal">
<div class="modal-content attack-chain-modal-content">
<div class="modal-header">
<h2 data-i18n="attackChainModal.title">攻击链可视化</h2>
<div class="modal-header-actions">
<button class="btn-primary attack-chain-action-btn" onclick="regenerateAttackChain()" data-i18n="attackChainModal.regenerateTitle" data-i18n-attr="title" data-i18n-skip-text="true" title="重新生成攻击链(包含最新对话内容)">
🔄 <span data-i18n="attackChainModal.regenerate">重新生成</span>
</button>
<button class="btn-secondary attack-chain-action-btn" onclick="exportAttackChain('png')" data-i18n="attackChainModal.exportPng" data-i18n-attr="title" title="导出为PNG">
📥 PNG
</button>
<button class="btn-secondary attack-chain-action-btn" onclick="exportAttackChain('svg')" data-i18n="attackChainModal.exportSvg" data-i18n-attr="title" title="导出为SVG">
📥 SVG
</button>
<button class="btn-secondary attack-chain-action-btn" onclick="refreshAttackChain()" data-i18n="attackChainModal.refreshTitle" data-i18n-attr="title" title="刷新当前攻击链(不重新生成)">
<span data-i18n="common.refresh">刷新</span>
</button>
<span class="modal-close" onclick="closeAttackChainModal()">&times;</span>
</div>
</div>
<div class="modal-body attack-chain-body">
<div class="attack-chain-main-layout">
<div class="attack-chain-visualization-area">
<div class="attack-chain-toolbar">
<div class="attack-chain-info">
<span id="attack-chain-stats">Nodes: 0 | Edges: 0</span>
</div>
<div class="attack-chain-filters">
<input type="text" id="attack-chain-search" data-i18n="attackChainModal.searchPlaceholder" data-i18n-attr="placeholder" placeholder="搜索节点..."
oninput="filterAttackChainNodes(this.value)">
<select id="attack-chain-type-filter"
onchange="filterAttackChainByType(this.value)">
<option value="all" data-i18n="attackChainModal.allTypes">所有类型</option>
<option value="target" data-i18n="attackChainModal.target">目标</option>
<option value="action" data-i18n="attackChainModal.action">行动</option>
<option value="vulnerability" data-i18n="attackChainModal.vulnerability">漏洞</option>
</select>
<select id="attack-chain-risk-filter"
onchange="filterAttackChainByRisk(this.value)">
<option value="all" data-i18n="attackChainModal.allRisks">所有风险</option>
<option value="high" data-i18n="attackChainModal.highRisk">高风险 (80-100)</option>
<option value="medium-high" data-i18n="attackChainModal.mediumHighRisk">中高风险 (60-79)</option>
<option value="medium" data-i18n="attackChainModal.mediumRisk">中风险 (40-59)</option>
<option value="low" data-i18n="attackChainModal.lowRisk">低风险 (0-39)</option>
</select>
<button class="btn-secondary" onclick="resetAttackChainFilters()" data-i18n="attackChainModal.resetFilter">
重置筛选
</button>
</div>
</div>
<div id="attack-chain-container" class="attack-chain-container">
<div class="loading-spinner" data-i18n="attackChainModal.loading">加载中...</div>
</div>
</div>
<div class="attack-chain-sidebar">
<div class="attack-chain-sidebar-content">
<div class="attack-chain-legend">
<div class="legend-section">
<div class="legend-title" data-i18n="attackChainModal.riskLevel">风险等级</div>
<div class="legend-item">
<span class="legend-color" style="background: #ff4444;"></span>
<span data-i18n="attackChainModal.highRisk">高风险 (80-100)</span>
</div>
<div class="legend-item">
<span class="legend-color" style="background: #ff8800;"></span>
<span data-i18n="attackChainModal.mediumHighRisk">中高风险 (60-79)</span>
</div>
<div class="legend-item">
<span class="legend-color" style="background: #ffbb00;"></span>
<span data-i18n="attackChainModal.mediumRisk">中风险 (40-59)</span>
</div>
<div class="legend-item">
<span class="legend-color" style="background: #88cc00;"></span>
<span data-i18n="attackChainModal.lowRisk">低风险 (0-39)</span>
</div>
</div>
<div class="legend-section">
<div class="legend-title" data-i18n="attackChainModal.lineMeaning">连接线含义</div>
<div class="legend-item">
<span class="legend-line" style="border-top: 2px solid #42a5f5;"></span>
<span data-i18n="attackChainModal.blueLine">蓝色线:行动发现漏洞</span>
</div>
<div class="legend-item">
<span class="legend-line" style="border-top: 2px solid #e53935;"></span>
<span data-i18n="attackChainModal.redLine">红色线:使能/促成关系</span>
</div>
<div class="legend-item">
<span class="legend-line" style="border-top: 2px solid #616161;"></span>
<span data-i18n="attackChainModal.grayLine">灰色线:逻辑顺序</span>
</div>
</div>
<div id="attack-chain-details" class="legend-section attack-chain-details" style="display: none;">
<div class="legend-title attack-chain-details-title">
<span data-i18n="attackChainModal.nodeDetails">节点详情</span>
<button class="attack-chain-details-close" onclick="closeNodeDetails()" data-i18n="attackChainModal.closeDetails" data-i18n-attr="title" title="关闭详情">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
<div id="attack-chain-details-content" class="attack-chain-details-content"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Marked.js for Markdown parsing -->
<script src="https://cdn.jsdelivr.net/npm/marked@11.1.1/marked.min.js"></script>
<!-- DOMPurify for HTML sanitization to prevent XSS -->
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.8/dist/purify.min.js"></script>
<!-- Cytoscape.js for attack chain visualization -->
<script src="https://cdn.jsdelivr.net/npm/cytoscape@3.27.0/dist/cytoscape.min.js"></script>
<!-- ELK.js for high-quality DAG layout (reduces edge crossings) -->
<script src="https://cdn.jsdelivr.net/npm/elkjs@0.9.2/lib/elk.bundled.js"></script>
<!-- SheetJS for XLSX export (info-collect) -->
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
<script>
// 确保ELK对象全局可用
if (typeof ELK === 'undefined' && typeof elk !== 'undefined') {
window.ELK = elk;
}
</script>
<!-- 知识项编辑模态框 -->
<!-- Skill模态框 -->
<div id="skill-modal" class="modal">
<div class="modal-content" style="max-width: 900px;">
<div class="modal-header">
<h2 id="skill-modal-title" data-i18n="skillModal.addSkill">添加Skill</h2>
<span class="modal-close" onclick="closeSkillModal()">&times;</span>
</div>
<div class="modal-body">
<div class="form-group">
<label for="skill-name"><span data-i18n="skillModal.skillName">Skill名称</span> <span style="color: red;">*</span></label>
<input type="text" id="skill-name" data-i18n="skillModal.skillNamePlaceholder" data-i18n-attr="placeholder" placeholder="例如: sql-injection-testing" required />
<small class="form-hint" data-i18n="skillModal.skillNameHint">只能包含字母、数字、连字符和下划线</small>
</div>
<div class="form-group">
<label for="skill-description" data-i18n="skillModal.description">描述</label>
<input type="text" id="skill-description" data-i18n="skillModal.descriptionPlaceholder" data-i18n-attr="placeholder" placeholder="Skill的简短描述" />
</div>
<div class="form-group">
<label for="skill-content"><span data-i18n="skillModal.contentLabel">内容Markdown格式</span> <span style="color: red;">*</span></label>
<textarea id="skill-content" rows="20" data-i18n="skillModal.contentPlaceholder" data-i18n-attr="placeholder" placeholder="输入skill内容支持Markdown格式..." style="font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 0.875rem; line-height: 1.5;" required></textarea>
<small class="form-hint"><span data-i18n="skillModal.contentHint">支持YAML front matter格式可选例如</span><br>
---<br>
name: skill-name<br>
description: Skill描述<br>
version: 1.0.0<br>
---<br><br>
# Skill标题<br>
这里是skill内容...</small>
</div>
</div>
<div class="modal-footer">
<button class="btn-secondary" onclick="closeSkillModal()" data-i18n="common.cancel">取消</button>
<button class="btn-primary" onclick="saveSkill()" data-i18n="common.save">保存</button>
</div>
</div>
</div>
<div id="knowledge-item-modal" class="modal">
<div class="modal-content" style="max-width: 900px;">
<div class="modal-header">
<h2 id="knowledge-item-modal-title">添加知识</h2>
<span class="modal-close" onclick="closeKnowledgeItemModal()">&times;</span>
</div>
<div class="modal-body">
<div class="form-group">
<label for="knowledge-item-category">分类(风险类型)<span style="color: red;">*</span></label>
<input type="text" id="knowledge-item-category" placeholder="例如SQL注入" required />
</div>
<div class="form-group">
<label for="knowledge-item-title">标题<span style="color: red;">*</span></label>
<input type="text" id="knowledge-item-title" placeholder="知识项标题" required />
</div>
<div class="form-group">
<label for="knowledge-item-content">内容Markdown格式<span style="color: red;">*</span></label>
<textarea id="knowledge-item-content" rows="20" placeholder="输入知识内容支持Markdown格式..." style="font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 0.875rem; line-height: 1.5;" required></textarea>
</div>
</div>
<div class="modal-footer">
<button class="btn-secondary" onclick="closeKnowledgeItemModal()">取消</button>
<button class="btn-primary" onclick="saveKnowledgeItem()">保存</button>
</div>
</div>
</div>
<!-- 批量管理对话模态框 -->
<div id="batch-manage-modal" class="modal">
<div class="modal-content batch-manage-modal-content">
<div class="modal-header">
<h2 id="batch-manage-title">管理对话记录·共<span id="batch-manage-count">0</span></h2>
<div class="batch-manage-header-actions">
<div class="batch-search-box">
<input type="text" id="batch-search-input" data-i18n="batchManageModal.searchPlaceholder" data-i18n-attr="placeholder" placeholder="搜索历史记录" oninput="filterBatchConversations(this.value)" />
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="11" cy="11" r="8" stroke="currentColor" stroke-width="2"/>
<path d="m21 21-4.35-4.35" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</div>
<span class="modal-close" onclick="closeBatchManageModal()">&times;</span>
</div>
</div>
<div class="modal-body batch-manage-body">
<div class="batch-conversations-table">
<div class="batch-table-header">
<div class="batch-table-col-checkbox"></div>
<div class="batch-table-col-name" data-i18n="batchManageModal.conversationName">对话名称</div>
<div class="batch-table-col-time" data-i18n="batchManageModal.lastTime">最近一次对话时间</div>
<div class="batch-table-col-action" data-i18n="batchManageModal.action">操作</div>
</div>
<div id="batch-conversations-list" class="batch-conversations-list"></div>
</div>
</div>
<div class="modal-footer batch-manage-footer">
<label class="select-all-checkbox">
<input type="checkbox" id="batch-select-all" onchange="toggleSelectAllBatch()" />
<span data-i18n="batchManageModal.selectAll">全选</span>
</label>
<div class="batch-footer-actions">
<button class="btn-secondary" onclick="closeBatchManageModal()" data-i18n="common.cancel">取消</button>
<button class="btn-primary" onclick="deleteSelectedConversations()" data-i18n="batchManageModal.deleteSelected">删除所选</button>
</div>
</div>
</div>
</div>
<!-- 创建分组模态框 -->
<div id="create-group-modal" class="modal">
<div class="modal-content create-group-modal-content">
<div class="modal-header">
<h2 data-i18n="createGroupModal.title">创建分组</h2>
<span class="modal-close" onclick="closeCreateGroupModal()">&times;</span>
</div>
<div class="modal-body create-group-body">
<p class="create-group-description" data-i18n="createGroupModal.description">分组功能可将对话集中归类管理,让对话更加井然有序。</p>
<div class="create-group-input-wrapper">
<button type="button" class="group-icon-input" id="create-group-icon-btn" onclick="toggleGroupIconPicker()" data-i18n="createGroupModal.selectIcon" data-i18n-attr="title" title="点击选择图标">📁</button>
<input type="text" id="create-group-name-input" data-i18n="createGroupModal.groupNamePlaceholder" data-i18n-attr="placeholder" placeholder="请输入分组名称" />
<!-- Emoji选择器面板 -->
<div id="group-icon-picker" class="group-icon-picker" style="display: none;">
<div class="icon-picker-header">
<span data-i18n="createGroupModal.pickIcon">选择图标</span>
<div class="icon-picker-custom">
<input type="text" id="custom-icon-input" class="custom-icon-input" data-i18n="createGroupModal.customIcon" data-i18n-attr="placeholder" placeholder="自定义" maxlength="2" />
<button type="button" class="custom-icon-btn" onclick="applyCustomIcon()" data-i18n="createGroupModal.confirmIcon">确定</button>
</div>
</div>
<div class="icon-picker-grid">
<span class="icon-option" onclick="selectGroupIcon('📁')">📁</span>
<span class="icon-option" onclick="selectGroupIcon('🔒')">🔒</span>
<span class="icon-option" onclick="selectGroupIcon('🛡️')">🛡️</span>
<span class="icon-option" onclick="selectGroupIcon('⚔️')">⚔️</span>
<span class="icon-option" onclick="selectGroupIcon('🎯')">🎯</span>
<span class="icon-option" onclick="selectGroupIcon('🔍')">🔍</span>
<span class="icon-option" onclick="selectGroupIcon('💻')">💻</span>
<span class="icon-option" onclick="selectGroupIcon('🐛')">🐛</span>
<span class="icon-option" onclick="selectGroupIcon('🚀')">🚀</span>
<span class="icon-option" onclick="selectGroupIcon('⚡')"></span>
<span class="icon-option" onclick="selectGroupIcon('🔥')">🔥</span>
<span class="icon-option" onclick="selectGroupIcon('💡')">💡</span>
<span class="icon-option" onclick="selectGroupIcon('🎮')">🎮</span>
<span class="icon-option" onclick="selectGroupIcon('🏴‍☠️')">🏴‍☠️</span>
<span class="icon-option" onclick="selectGroupIcon('🕵️')">🕵️</span>
<span class="icon-option" onclick="selectGroupIcon('🔑')">🔑</span>
<span class="icon-option" onclick="selectGroupIcon('📡')">📡</span>
<span class="icon-option" onclick="selectGroupIcon('🌐')">🌐</span>
<span class="icon-option" onclick="selectGroupIcon('📊')">📊</span>
<span class="icon-option" onclick="selectGroupIcon('📝')">📝</span>
<span class="icon-option" onclick="selectGroupIcon('🗂️')">🗂️</span>
<span class="icon-option" onclick="selectGroupIcon('📌')">📌</span>
<span class="icon-option" onclick="selectGroupIcon('⭐')"></span>
<span class="icon-option" onclick="selectGroupIcon('💎')">💎</span>
</div>
</div>
</div>
<div class="create-group-suggestions">
<div class="suggestion-tag" data-i18n="createGroupModal.suggestionPenetrationTest" onclick="selectSuggestionByKey('createGroupModal.suggestionPenetrationTest')">渗透测试</div>
<div class="suggestion-tag" data-i18n="createGroupModal.suggestionCtf" onclick="selectSuggestionByKey('createGroupModal.suggestionCtf')">CTF</div>
<div class="suggestion-tag" data-i18n="createGroupModal.suggestionRedTeam" onclick="selectSuggestionByKey('createGroupModal.suggestionRedTeam')">红队</div>
<div class="suggestion-tag" data-i18n="createGroupModal.suggestionVulnerabilityMining" onclick="selectSuggestionByKey('createGroupModal.suggestionVulnerabilityMining')">漏洞挖掘</div>
</div>
</div>
<div class="modal-footer">
<button class="btn-secondary" onclick="closeCreateGroupModal()" data-i18n="createGroupModal.cancel">取消</button>
<button class="btn-primary" onclick="createGroup(event)" data-i18n="createGroupModal.create">创建</button>
</div>
</div>
</div>
<!-- 上下文菜单 -->
<div id="conversation-context-menu" class="context-menu" style="display: none;">
<div id="attack-chain-menu-item" class="context-menu-item" onclick="showAttackChainFromContext()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5 13.5l3-3M8 8H5a4 4 0 1 0 0 8h3m8-8h3a4 4 0 0 1 0 8h-3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span data-i18n="contextMenu.viewAttackChain">查看攻击链</span>
</div>
<div class="context-menu-divider"></div>
<div class="context-menu-item" onclick="renameConversation()">
<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>
<span data-i18n="contextMenu.rename">重命名</span>
</div>
<div class="context-menu-item" onclick="pinConversation()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 17v5M5 17h14l-1-7H6l-1 7zM9 10V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span id="pin-conversation-menu-text" data-i18n="contextMenu.pinConversation">置顶此对话</span>
</div>
<div class="context-menu-item" onclick="showBatchManageModal()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="3" y1="12" x2="21" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
<line x1="3" y1="6" x2="21" y2="6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
<line x1="3" y1="18" x2="21" y2="18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
<circle cx="8" cy="6" r="1" fill="currentColor"/>
<circle cx="8" cy="12" r="1" fill="currentColor"/>
<circle cx="8" cy="18" r="1" fill="currentColor"/>
</svg>
<span data-i18n="contextMenu.batchManage">批量管理</span>
</div>
<div class="context-menu-item context-menu-item-has-submenu" onmouseenter="handleMoveToGroupSubmenuEnter()" onmouseleave="handleMoveToGroupSubmenuLeave(event)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span data-i18n="contextMenu.moveToGroup">移动到分组</span>
<svg class="submenu-arrow" width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<div id="move-to-group-submenu" class="context-submenu" style="display: none;" onmouseenter="clearSubmenuHideTimeout()" onmouseleave="hideMoveToGroupSubmenu()"></div>
</div>
<div class="context-menu-divider"></div>
<div class="context-menu-item context-menu-item-danger" onclick="deleteConversationFromContext()">
<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>
<span data-i18n="contextMenu.deleteConversation">删除此对话</span>
</div>
</div>
<!-- 分组上下文菜单 -->
<div id="group-context-menu" class="context-menu" style="display: none;">
<div class="context-menu-item" onclick="renameGroupFromContext()">
<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>
<span data-i18n="contextMenu.rename">重命名</span>
</div>
<div class="context-menu-item" onclick="pinGroupFromContext()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 17v5M5 17h14l-1-7H6l-1 7zM9 10V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span id="pin-group-menu-text" data-i18n="contextMenu.pinGroup">置顶此分组</span>
</div>
<div class="context-menu-item context-menu-item-danger" onclick="deleteGroupFromContext()">
<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>
<span data-i18n="contextMenu.deleteGroup">删除此分组</span>
</div>
</div>
<!-- 新建任务模态框 -->
<div id="batch-import-modal" class="modal">
<div class="modal-content" style="max-width: 800px;">
<div class="modal-header">
<h2 data-i18n="batchImportModal.title">新建任务</h2>
<span class="modal-close" onclick="closeBatchImportModal()">&times;</span>
</div>
<div class="modal-body">
<div class="form-group">
<label for="batch-queue-title" data-i18n="batchImportModal.queueTitle">任务标题</label>
<input type="text" id="batch-queue-title" data-i18n="batchImportModal.queueTitlePlaceholder" data-i18n-attr="placeholder" placeholder="请输入任务标题(可选,用于标识和筛选)" />
<div class="form-hint" style="margin-top: 4px;" data-i18n="batchImportModal.queueTitleHint">为批量任务队列设置一个标题,方便后续查找和管理。</div>
</div>
<div class="form-group">
<label for="batch-queue-role" data-i18n="batchImportModal.role">角色</label>
<select id="batch-queue-role" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 0.875rem;">
<option value="" data-i18n="batchImportModal.defaultRole">默认</option>
</select>
<div class="form-hint" style="margin-top: 4px;" data-i18n="batchImportModal.roleHint">选择一个角色,所有任务将使用该角色的配置(提示词和工具)执行。</div>
</div>
<div class="form-group">
<label for="batch-tasks-input"><span data-i18n="batchImportModal.tasksList">任务列表(每行一个任务)</span><span style="color: red;">*</span></label>
<textarea id="batch-tasks-input" rows="15" data-i18n="batchImportModal.tasksListPlaceholderExample" data-i18n-attr="placeholder" placeholder="请输入任务列表,每行一个任务,例如:&#10;扫描 192.168.1.1 的开放端口&#10;检查 https://example.com 是否存在SQL注入&#10;枚举 example.com 的子域名" style="font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 0.875rem; line-height: 1.5;"></textarea>
<div class="form-hint" style="margin-top: 8px;" data-i18n="batchImportModal.tasksListHintFull">提示:每行输入一个任务指令,系统将依次执行这些任务。空行会被自动忽略。</div>
</div>
<div class="form-group">
<div id="batch-import-stats" class="batch-import-stats"></div>
</div>
</div>
<div class="modal-footer">
<button class="btn-secondary" onclick="closeBatchImportModal()" data-i18n="common.cancel">取消</button>
<button class="btn-primary" onclick="createBatchQueue()" data-i18n="batchImportModal.createQueue">创建队列</button>
</div>
</div>
</div>
<!-- 批量任务队列详情模态框 -->
<div id="batch-queue-detail-modal" class="modal">
<div class="modal-content" style="max-width: 900px;">
<div class="modal-header">
<h2 id="batch-queue-detail-title" data-i18n="batchQueueDetailModal.title">批量任务队列详情</h2>
<div style="display: flex; align-items: center; gap: 12px;">
<div class="modal-header-actions">
<button class="btn-secondary btn-small" id="batch-queue-add-task-btn" onclick="showAddBatchTaskModal()" style="display: none;" data-i18n="batchQueueDetailModal.addTask">添加任务</button>
<button class="btn-primary btn-small" id="batch-queue-start-btn" onclick="startBatchQueue()" style="display: none;" data-i18n="batchQueueDetailModal.startExecute">开始执行</button>
<button class="btn-secondary btn-small" id="batch-queue-pause-btn" onclick="pauseBatchQueue()" style="display: none;" data-i18n="batchQueueDetailModal.pauseQueue">暂停队列</button>
<button class="btn-secondary btn-small btn-danger" id="batch-queue-delete-btn" onclick="deleteBatchQueue()" style="display: none;" data-i18n="batchQueueDetailModal.deleteQueue">删除队列</button>
</div>
<span class="modal-close" onclick="closeBatchQueueDetailModal()">&times;</span>
</div>
</div>
<div class="modal-body">
<div id="batch-queue-detail-content"></div>
</div>
</div>
</div>
<!-- 编辑批量任务模态框 -->
<div id="edit-batch-task-modal" class="modal">
<div class="modal-content" style="max-width: 600px;">
<div class="modal-header">
<h2 data-i18n="editBatchTaskModal.title">编辑任务</h2>
<span class="modal-close" onclick="closeEditBatchTaskModal()">&times;</span>
</div>
<div class="modal-body">
<div class="form-group">
<label for="edit-task-message" data-i18n="editBatchTaskModal.taskMessage">任务消息</label>
<textarea id="edit-task-message" class="form-control" rows="5" data-i18n="editBatchTaskModal.taskMessagePlaceholder" data-i18n-attr="placeholder" placeholder="请输入任务消息"></textarea>
</div>
<div class="form-actions">
<button class="btn-primary" onclick="saveBatchTask()" data-i18n="common.save">保存</button>
<button class="btn-secondary" onclick="closeEditBatchTaskModal()" data-i18n="common.cancel">取消</button>
</div>
</div>
</div>
</div>
<!-- 添加批量任务模态框 -->
<div id="add-batch-task-modal" class="modal">
<div class="modal-content" style="max-width: 600px;">
<div class="modal-header">
<h2 data-i18n="addBatchTaskModal.title">添加任务</h2>
<span class="modal-close" onclick="closeAddBatchTaskModal()">&times;</span>
</div>
<div class="modal-body">
<div class="form-group">
<label for="add-task-message" data-i18n="addBatchTaskModal.taskMessage">任务消息</label>
<textarea id="add-task-message" class="form-control" rows="5" data-i18n="addBatchTaskModal.taskMessagePlaceholder" data-i18n-attr="placeholder" placeholder="请输入任务消息"></textarea>
</div>
<div class="form-actions">
<button class="btn-primary" onclick="saveAddBatchTask()" data-i18n="addBatchTaskModal.add">添加</button>
<button class="btn-secondary" onclick="closeAddBatchTaskModal()" data-i18n="common.cancel">取消</button>
</div>
</div>
</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" data-i18n="vulnerability.addVuln">添加漏洞</h2>
<span class="modal-close" onclick="closeVulnerabilityModal()">&times;</span>
</div>
<div class="modal-body">
<div class="form-group">
<label for="vulnerability-conversation-id"><span data-i18n="vulnerabilityModal.conversationId">会话ID</span> <span style="color: red;">*</span></label>
<input type="text" id="vulnerability-conversation-id" data-i18n="vulnerabilityModal.conversationIdPlaceholder" data-i18n-attr="placeholder" placeholder="输入会话ID" required />
</div>
<div class="form-group">
<label for="vulnerability-title"><span data-i18n="vulnerabilityModal.title">标题</span> <span style="color: red;">*</span></label>
<input type="text" id="vulnerability-title" data-i18n="vulnerabilityModal.titlePlaceholder" data-i18n-attr="placeholder" placeholder="漏洞标题" required />
</div>
<div class="form-group">
<label for="vulnerability-description" data-i18n="vulnerabilityModal.description">描述</label>
<textarea id="vulnerability-description" rows="5" data-i18n="vulnerabilityModal.descriptionPlaceholder" data-i18n-attr="placeholder" placeholder="漏洞详细描述"></textarea>
</div>
<div class="form-group">
<label for="vulnerability-severity"><span data-i18n="vulnerabilityModal.severity">严重程度</span> <span style="color: red;">*</span></label>
<select id="vulnerability-severity" required>
<option value="" data-i18n="vulnerabilityModal.pleaseSelect">请选择</option>
<option value="critical" data-i18n="vulnerabilityModal.severityCritical">严重</option>
<option value="high" data-i18n="vulnerabilityModal.severityHigh">高危</option>
<option value="medium" data-i18n="vulnerabilityModal.severityMedium">中危</option>
<option value="low" data-i18n="vulnerabilityModal.severityLow">低危</option>
<option value="info" data-i18n="vulnerabilityModal.severityInfo">信息</option>
</select>
</div>
<div class="form-group">
<label for="vulnerability-status" data-i18n="vulnerabilityModal.status">状态</label>
<select id="vulnerability-status">
<option value="open" data-i18n="vulnerabilityModal.statusOpen">待处理</option>
<option value="confirmed" data-i18n="vulnerabilityModal.statusConfirmed">已确认</option>
<option value="fixed" data-i18n="vulnerabilityModal.statusFixed">已修复</option>
<option value="false_positive" data-i18n="vulnerabilityModal.statusFalsePositive">误报</option>
</select>
</div>
<div class="form-group">
<label for="vulnerability-type" data-i18n="vulnerabilityModal.type">漏洞类型</label>
<input type="text" id="vulnerability-type" data-i18n="vulnerabilityModal.typePlaceholder" data-i18n-attr="placeholder" placeholder="如SQL注入、XSS、CSRF等" />
</div>
<div class="form-group">
<label for="vulnerability-target" data-i18n="vulnerabilityModal.target">目标</label>
<input type="text" id="vulnerability-target" data-i18n="vulnerabilityModal.targetPlaceholder" data-i18n-attr="placeholder" placeholder="受影响的目标URL、IP地址等" />
</div>
<div class="form-group">
<label for="vulnerability-proof" data-i18n="vulnerabilityModal.proof">证明POC</label>
<textarea id="vulnerability-proof" rows="5" data-i18n="vulnerabilityModal.proofPlaceholder" data-i18n-attr="placeholder" placeholder="漏洞证明,如请求/响应、截图等"></textarea>
</div>
<div class="form-group">
<label for="vulnerability-impact" data-i18n="vulnerabilityModal.impact">影响</label>
<textarea id="vulnerability-impact" rows="3" data-i18n="vulnerabilityModal.impactPlaceholder" data-i18n-attr="placeholder" placeholder="漏洞影响说明"></textarea>
</div>
<div class="form-group">
<label for="vulnerability-recommendation" data-i18n="vulnerabilityModal.recommendation">修复建议</label>
<textarea id="vulnerability-recommendation" rows="3" data-i18n="vulnerabilityModal.recommendationPlaceholder" data-i18n-attr="placeholder" placeholder="修复建议"></textarea>
</div>
</div>
<div class="modal-footer">
<button class="btn-secondary" onclick="closeVulnerabilityModal()" data-i18n="common.cancel">取消</button>
<button class="btn-primary" onclick="saveVulnerability()" data-i18n="common.save">保存</button>
</div>
</div>
</div>
<!-- WebShell 添加连接模态框 -->
<div id="webshell-modal" class="modal">
<div class="modal-content" style="max-width: 560px;">
<div class="modal-header">
<h2 id="webshell-modal-title" data-i18n="webshell.addConnection">添加连接</h2>
<span class="modal-close" onclick="closeWebshellModal()">&times;</span>
</div>
<div class="modal-body">
<input type="hidden" id="webshell-edit-id" value="" />
<div class="form-group">
<label for="webshell-url"><span data-i18n="webshell.url">Shell 地址</span> <span style="color: red;">*</span></label>
<input type="text" id="webshell-url" data-i18n="webshell.urlPlaceholder" data-i18n-attr="placeholder" placeholder="http(s)://target.com/shell.php" />
</div>
<div class="form-group">
<label for="webshell-password" data-i18n="webshell.password">连接密码/密钥</label>
<input type="text" id="webshell-password" data-i18n="webshell.passwordPlaceholder" data-i18n-attr="placeholder" placeholder="如冰蝎/蚁剑的连接密码" />
</div>
<div class="form-group">
<label for="webshell-type" data-i18n="webshell.type">Shell 类型</label>
<select id="webshell-type">
<option value="php" data-i18n="webshell.typePhp">PHP</option>
<option value="asp" data-i18n="webshell.typeAsp">ASP</option>
<option value="aspx" data-i18n="webshell.typeAspx">ASPX</option>
<option value="jsp" data-i18n="webshell.typeJsp">JSP</option>
<option value="custom" data-i18n="webshell.typeCustom">自定义</option>
</select>
</div>
<div class="form-group">
<label for="webshell-method" data-i18n="webshell.method">请求方式</label>
<select id="webshell-method">
<option value="post" data-i18n="webshell.methodPost">POST</option>
<option value="get" data-i18n="webshell.methodGet">GET</option>
</select>
</div>
<div class="form-group">
<label for="webshell-cmd-param" data-i18n="webshell.cmdParam">命令参数名</label>
<input type="text" id="webshell-cmd-param" data-i18n="webshell.cmdParamPlaceholder" data-i18n-attr="placeholder" placeholder="不填默认为 cmd如 xxx 则请求为 xxx=命令" />
</div>
<div class="form-group">
<label for="webshell-remark" data-i18n="webshell.remark">备注</label>
<input type="text" id="webshell-remark" data-i18n="webshell.remarkPlaceholder" data-i18n-attr="placeholder" placeholder="便于识别的备注名" />
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn-secondary" id="webshell-test-btn" onclick="testWebshellConnection()" data-i18n="webshell.testConnectivity">测试连通性</button>
<button class="btn-secondary" onclick="closeWebshellModal()" data-i18n="common.cancel">取消</button>
<button class="btn-primary" onclick="saveWebshellConnection()" data-i18n="common.save">保存</button>
</div>
</div>
</div>
<!-- 角色选择弹窗 -->
<div id="role-select-modal" class="modal">
<div class="modal-content role-select-modal-content">
<div class="modal-header">
<h2 data-i18n="chatGroup.rolePanelTitle">选择角色</h2>
<span class="modal-close" onclick="closeRoleSelectModal()">&times;</span>
</div>
<div class="modal-body role-select-body">
<div id="role-select-list" class="role-select-list"></div>
</div>
</div>
</div>
<!-- 角色编辑模态框 -->
<div id="role-modal" class="modal">
<div class="modal-content" style="max-width: 900px;">
<div class="modal-header">
<h2 id="role-modal-title" data-i18n="roleModal.addRole">添加角色</h2>
<span class="modal-close" onclick="closeRoleModal()">&times;</span>
</div>
<div class="modal-body">
<div class="form-group">
<label for="role-name"><span data-i18n="roleModal.roleName">角色名称</span> <span style="color: red;">*</span></label>
<input type="text" id="role-name" data-i18n="roleModal.roleNamePlaceholder" data-i18n-attr="placeholder" placeholder="输入角色名称" required />
</div>
<div class="form-group">
<label for="role-description" data-i18n="roleModal.roleDescription">角色描述</label>
<input type="text" id="role-description" data-i18n="roleModal.roleDescriptionPlaceholder" data-i18n-attr="placeholder" placeholder="输入角色描述" />
</div>
<div class="form-group">
<label for="role-icon" data-i18n="roleModal.roleIcon">角色图标</label>
<input type="text" id="role-icon" data-i18n="roleModal.roleIconPlaceholder" data-i18n-attr="placeholder" placeholder="输入emoji图标例如: 🏆" maxlength="10" />
<small class="form-hint" data-i18n="roleModal.roleIconHint">输入一个emoji作为角色的图标将显示在角色选择器中。</small>
</div>
<div class="form-group">
<label for="role-user-prompt" data-i18n="roleModal.userPrompt">用户提示词</label>
<textarea id="role-user-prompt" rows="10" data-i18n="roleModal.userPromptPlaceholder" data-i18n-attr="placeholder" placeholder="输入用户提示词,会在用户消息前追加此提示词..."></textarea>
<small class="form-hint" data-i18n="roleModal.userPromptHint">此提示词会追加到用户消息前用于指导AI的行为。注意这不会修改系统提示词。</small>
</div>
<div class="form-group" id="role-tools-section">
<label data-i18n="roleModal.relatedTools">关联的工具(可选)</label>
<div id="role-tools-default-hint" class="role-tools-default-hint" style="display: none;">
<div class="role-tools-default-info">
<span class="role-tools-default-icon"></span>
<div class="role-tools-default-content">
<div class="role-tools-default-title" data-i18n="roleModal.defaultRoleToolsTitle">默认角色使用所有工具</div>
<div class="role-tools-default-desc" data-i18n="roleModal.defaultRoleToolsDesc">默认角色会自动使用MCP管理中启用的所有工具无需单独配置。</div>
</div>
</div>
</div>
<div class="role-tools-controls">
<div class="role-tools-actions">
<button type="button" class="btn-secondary" onclick="selectAllRoleTools()" data-i18n="roleModal.selectAll">全选</button>
<button type="button" class="btn-secondary" onclick="deselectAllRoleTools()" data-i18n="roleModal.deselectAll">全不选</button>
<div class="role-tools-search-box">
<input type="text" id="role-tools-search" data-i18n="roleModal.searchToolsPlaceholder" data-i18n-attr="placeholder" placeholder="搜索工具..."
oninput="searchRoleTools(this.value)"
onkeypress="if(event.key === 'Enter') searchRoleTools(this.value)" />
<button class="role-tools-search-clear" id="role-tools-search-clear"
onclick="clearRoleToolsSearch()" style="display: none;" data-i18n="common.clearSearch" data-i18n-attr="title" title="清除搜索">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
<path d="M15 9l-6 6M9 9l6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</button>
</div>
</div>
<div id="role-tools-stats" class="role-tools-stats"></div>
</div>
<div id="role-tools-list" class="role-tools-list">
<div class="tools-loading" data-i18n="roleModal.loadingTools">正在加载工具列表...</div>
</div>
<small class="form-hint" data-i18n="roleModal.relatedToolsHint">勾选要关联的工具留空则使用MCP管理中的全部工具配置。</small>
</div>
<div class="form-group" id="role-skills-section">
<label data-i18n="roleModal.relatedSkills">关联的Skills可选</label>
<div class="role-skills-controls">
<div class="role-skills-actions">
<button type="button" class="btn-secondary" onclick="selectAllRoleSkills()" data-i18n="roleModal.selectAll">全选</button>
<button type="button" class="btn-secondary" onclick="deselectAllRoleSkills()" data-i18n="roleModal.deselectAll">全不选</button>
<div class="role-skills-search-box">
<input type="text" id="role-skills-search" data-i18n="roleModal.searchSkillsPlaceholder" data-i18n-attr="placeholder" placeholder="搜索skill..."
oninput="searchRoleSkills(this.value)"
onkeypress="if(event.key === 'Enter') searchRoleSkills(this.value)" />
<button class="role-skills-search-clear" id="role-skills-search-clear"
onclick="clearRoleSkillsSearch()" style="display: none;" data-i18n="common.clearSearch" data-i18n-attr="title" title="清除搜索">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
<path d="M15 9l-6 6M9 9l6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</button>
</div>
</div>
<div id="role-skills-stats" class="role-skills-stats"></div>
</div>
<div id="role-skills-list" class="role-skills-list">
<div class="skills-loading" data-i18n="roleModal.loadingSkills">正在加载skills列表...</div>
</div>
<small class="form-hint" data-i18n="roleModal.relatedSkillsHint">勾选要关联的skills这些skills的内容会在执行任务前注入到系统提示词中帮助AI更好地理解相关专业知识。</small>
</div>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" id="role-enabled" class="modern-checkbox" checked />
<span class="checkbox-custom"></span>
<span class="checkbox-text" data-i18n="roleModal.enableRole">启用此角色</span>
</label>
</div>
</div>
<div class="modal-footer">
<button class="btn-secondary" onclick="closeRoleModal()" data-i18n="common.cancel">取消</button>
<button class="btn-primary" onclick="saveRole()" data-i18n="common.save">保存</button>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/i18next@23.11.5/i18next.min.js"></script>
<script src="/static/js/i18n.js"></script>
<script src="/static/js/builtin-tools.js"></script>
<script src="/static/js/auth.js"></script>
<script src="/static/js/info-collect.js"></script>
<script src="/static/js/router.js"></script>
<script src="/static/js/dashboard.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="https://cdn.jsdelivr.net/npm/xterm@4.19.0/lib/xterm.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.5.0/lib/xterm-addon-fit.js"></script>
<script src="/static/js/terminal.js"></script>
<script src="/static/js/knowledge.js"></script>
<script src="/static/js/skills.js"></script>
<script src="/static/js/vulnerability.js?v=4"></script>
<script src="/static/js/webshell.js"></script>
<script src="/static/js/tasks.js"></script>
<script src="/static/js/roles.js"></script>
</body>
</html>