From 7be01e95ef8ede5dddae916fb56c9769cd93e57c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Thu, 18 Dec 2025 22:38:33 +0800 Subject: [PATCH] Add files via upload --- web/static/css/style.css | 525 +++++++++++++++++++++++++++++++++++--- web/static/js/monitor.js | 39 +-- web/static/js/router.js | 238 +++++++++++++++++ web/static/js/settings.js | 141 +++++++++- web/templates/index.html | 376 +++++++++++++++------------ 5 files changed, 1075 insertions(+), 244 deletions(-) create mode 100644 web/static/js/router.js diff --git a/web/static/css/style.css b/web/static/css/style.css index a43dcd92..c9156be6 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -53,12 +53,411 @@ body { min-height: 0; } -header { - background: var(--primary-color); - color: white; - padding: 16px 24px; - border-bottom: 1px solid rgba(255, 255, 255, 0.1); +/* 主侧边栏样式 - Ant Design Pro 风格 */ +.main-sidebar { + width: 256px; + background: linear-gradient(180deg, #fafbfc 0%, #f5f7fa 100%); + color: var(--text-primary); + display: flex; + flex-direction: column; flex-shrink: 0; + border-right: 1px solid var(--border-color); + box-shadow: 2px 0 8px rgba(0, 0, 0, 0.04); + position: relative; + transition: width 0.2s ease; +} + +.main-sidebar.collapsed { + width: 64px; +} + +.sidebar-collapse-btn { + position: absolute; + top: 16px; + right: 8px; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + border-radius: 6px; + background: var(--bg-primary); + border: 1px solid var(--border-color); + color: var(--text-secondary); + transition: all 0.2s ease; + z-index: 10; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08); +} + +.sidebar-collapse-btn:hover { + background: var(--bg-tertiary); + color: var(--text-primary); + border-color: var(--accent-color); +} + +.main-sidebar.collapsed .sidebar-collapse-btn { + right: 16px; + transform: rotate(180deg); +} + +.sidebar-collapse-btn svg { + width: 16px; + height: 16px; + stroke: currentColor; + transition: transform 0.2s ease; +} + +.main-sidebar-header { + padding: 16px 20px; + border-bottom: 1px solid var(--border-color); + flex-shrink: 0; + background: var(--bg-primary); +} + +.main-sidebar-header .logo { + display: flex; + align-items: center; + gap: 12px; + color: var(--text-primary); +} + +.main-sidebar-header .logo span { + font-size: 1.25rem; + font-weight: 600; + letter-spacing: -0.3px; + color: var(--text-primary); +} + +.main-sidebar-nav { + flex: 1; + overflow-y: auto; + overflow-x: hidden; + padding: 16px 0; + background: transparent; + padding-top: 56px; +} + +/* 侧边栏滚动条样式 */ +.main-sidebar-nav::-webkit-scrollbar { + width: 6px; +} + +.main-sidebar-nav::-webkit-scrollbar-track { + background: transparent; +} + +.main-sidebar-nav::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.2); + border-radius: 3px; +} + +.main-sidebar-nav::-webkit-scrollbar-thumb:hover { + background: rgba(0, 0, 0, 0.3); +} + +.nav-item { + margin-bottom: 0; +} + +.nav-item-content { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 20px; + cursor: pointer; + transition: all 0.2s ease; + color: var(--text-primary); + position: relative; + font-size: 0.9375rem; + border-left: 3px solid transparent; + justify-content: flex-start; +} + +.main-sidebar.collapsed .nav-item-content { + padding: 12px; + justify-content: center; + position: relative; +} + +.main-sidebar.collapsed .nav-item-content:hover::after { + content: attr(data-title); + position: absolute; + left: 100%; + top: 50%; + transform: translateY(-50%); + margin-left: 12px; + padding: 6px 12px; + background: rgba(0, 0, 0, 0.85); + color: white; + border-radius: 4px; + font-size: 0.8125rem; + white-space: nowrap; + z-index: 1000; + pointer-events: none; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); +} + +.main-sidebar.collapsed .nav-item-content:hover::before { + content: ''; + position: absolute; + left: 100%; + top: 50%; + transform: translateY(-50%); + margin-left: 6px; + border: 6px solid transparent; + border-right-color: rgba(0, 0, 0, 0.85); + z-index: 1001; + pointer-events: none; +} + +.nav-item-content:hover { + background: rgba(0, 102, 255, 0.06); + color: var(--text-primary); +} + +.nav-item.active > .nav-item-content { + background: linear-gradient(90deg, rgba(0, 102, 255, 0.12) 0%, rgba(0, 102, 255, 0.06) 100%); + color: var(--accent-color); + border-left-color: var(--accent-color); + font-weight: 500; +} + +.nav-item.active > .nav-item-content svg { + stroke: var(--accent-color); +} + +.nav-item-content svg { + flex-shrink: 0; + stroke: currentColor; + width: 18px; + height: 18px; +} + +.nav-item-content span { + flex: 1; + font-size: 0.9375rem; + font-weight: 400; + white-space: nowrap; + opacity: 1; + transition: opacity 0.2s ease; +} + +.main-sidebar.collapsed .nav-item-content span { + opacity: 0; + width: 0; + overflow: hidden; +} + +.nav-item-has-submenu .nav-item-content { + justify-content: space-between; +} + +.main-sidebar.collapsed .nav-item-has-submenu .nav-item-content { + justify-content: center; +} + +.main-sidebar.collapsed .submenu-arrow { + display: none; +} + +.submenu-arrow { + transition: transform 0.2s ease; + flex-shrink: 0; + width: 14px; + height: 14px; + stroke: var(--text-secondary); +} + +.nav-item.expanded .submenu-arrow { + transform: rotate(90deg); +} + +.nav-item.expanded > .nav-item-content { + color: var(--text-primary); + font-weight: 500; +} + +.nav-submenu { + max-height: 0; + overflow: hidden; + transition: max-height 0.3s ease; + background: rgba(255, 255, 255, 0.5); +} + +.nav-item.expanded .nav-submenu { + max-height: 300px; +} + +.nav-submenu-item { + padding: 10px 20px 10px 56px; + cursor: pointer; + transition: all 0.2s ease; + color: var(--text-secondary); + font-size: 0.875rem; + position: relative; + border-left: 3px solid transparent; +} + +.main-sidebar.collapsed .nav-submenu { + display: none; +} + +.main-sidebar.collapsed .nav-item.expanded .nav-submenu { + display: none; +} + +/* 子菜单弹出框样式 */ +.submenu-popup { + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + padding: 4px 0; + min-width: 160px; + margin-left: 8px; + animation: popupFadeIn 0.2s ease; +} + +@keyframes popupFadeIn { + from { + opacity: 0; + transform: translateX(-8px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +.submenu-popup-item { + padding: 10px 16px; + cursor: pointer; + transition: all 0.2s ease; + color: var(--text-primary); + font-size: 0.875rem; + white-space: nowrap; +} + +.submenu-popup-item:hover { + background: rgba(0, 102, 255, 0.08); + color: var(--accent-color); +} + +.submenu-popup-item:active { + background: rgba(0, 102, 255, 0.12); +} + +.submenu-popup-item.active { + background: rgba(0, 102, 255, 0.1); + color: var(--accent-color); + font-weight: 500; +} + +.nav-submenu-item:hover { + background: rgba(0, 102, 255, 0.06); + color: var(--text-primary); +} + +.nav-submenu-item.active { + background: linear-gradient(90deg, rgba(0, 102, 255, 0.12) 0%, rgba(0, 102, 255, 0.06) 100%); + color: var(--accent-color); + border-left-color: var(--accent-color); + font-weight: 500; +} + +/* 内容区域 */ +.content-area { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + min-width: 0; + background: #f5f7fa; +} + +/* 对话页面不需要page-header */ +#page-chat .page-header { + display: none; +} + +#page-chat .page-content { + padding: 0; + overflow: hidden; +} + +.page { + display: none; + flex: 1; + flex-direction: column; + overflow: hidden; + min-height: 0; +} + +.page.active { + display: flex; +} + +.page-header { + padding: 20px 24px; + background: linear-gradient(135deg, #ffffff 0%, #fafbfc 100%); + border-bottom: 1px solid var(--border-color); + display: flex; + justify-content: space-between; + align-items: center; + flex-shrink: 0; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04); +} + +.page-header h2 { + margin: 0; + font-size: 1.5rem; + font-weight: 600; + color: var(--text-primary); +} + +.page-header-actions { + display: flex; + gap: 12px; + align-items: center; +} + +.page-content { + flex: 1; + overflow-y: auto; + overflow-x: hidden; + padding: 24px; + min-height: 0; +} + +/* 对话页面布局 */ +.chat-page-layout { + display: flex; + flex: 1; + overflow: hidden; + min-height: 0; + width: 100%; + height: 100%; +} + +.conversation-sidebar { + width: 280px; + background: linear-gradient(180deg, #ffffff 0%, #fafbfc 100%); + border-right: 1px solid var(--border-color); + display: flex; + flex-direction: column; + flex-shrink: 0; + height: 100%; + overflow: hidden; +} + +header { + background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); + color: var(--text-primary); + padding: 10px 24px; + border-bottom: 1px solid var(--border-color); + flex-shrink: 0; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); } .header-content { @@ -78,10 +477,11 @@ header { } .logo h1 { - font-size: 1.75rem; + font-size: 1.5rem; font-weight: 600; letter-spacing: -0.5px; margin: 0; + color: var(--text-primary); } .header-right { @@ -92,7 +492,7 @@ header { .header-subtitle { font-size: 0.875rem; - color: rgba(255, 255, 255, 0.7); + color: var(--text-secondary); margin: 0; font-weight: 400; } @@ -108,13 +508,13 @@ header { align-items: center; justify-content: center; gap: 6px; - padding: 8px 14px; - border-radius: 999px; - border: 1px solid rgba(255, 255, 255, 0.2); - background: rgba(255, 255, 255, 0.05); - color: white; + padding: 6px 12px; + border-radius: 6px; + border: 1px solid var(--border-color); + background: var(--bg-primary); + color: var(--text-primary); cursor: pointer; - font-size: 0.85rem; + font-size: 0.8125rem; font-weight: 500; transition: all 0.2s ease; } @@ -124,41 +524,42 @@ header { } .header-actions button:hover { - background: rgba(255, 255, 255, 0.12); - border-color: rgba(255, 255, 255, 0.35); + background: var(--bg-tertiary); + border-color: var(--accent-color); + color: var(--accent-color); transform: translateY(-1px); } .monitor-btn { - color: #8cc4ff; - border-color: rgba(0, 102, 255, 0.35); - background: rgba(0, 102, 255, 0.15); + color: var(--accent-color); + border-color: var(--border-color); + background: var(--bg-primary); } .monitor-btn:hover { - background: rgba(0, 102, 255, 0.25); - border-color: rgba(0, 102, 255, 0.45); - color: #cfe4ff; + background: rgba(0, 102, 255, 0.08); + border-color: var(--accent-color); + color: var(--accent-color); } .attack-chain-btn { - color: #ffe08a; - border-color: rgba(255, 255, 255, 0.3); - background: rgba(255, 255, 255, 0.08); + color: var(--text-primary); + border-color: var(--border-color); + background: var(--bg-primary); } .attack-chain-btn:not(:disabled):hover { - background: rgba(255, 255, 255, 0.18); - border-color: rgba(255, 255, 255, 0.45); - color: #fff5cc; + background: var(--bg-tertiary); + border-color: var(--accent-color); + color: var(--accent-color); } .attack-chain-btn:disabled { - opacity: 0.55; + opacity: 0.5; cursor: not-allowed; - border-color: rgba(255, 255, 255, 0.15); - background: rgba(255, 255, 255, 0.04); - color: rgba(255, 255, 255, 0.6); + border-color: var(--border-color); + background: var(--bg-secondary); + color: var(--text-muted); } .settings-btn { @@ -166,7 +567,17 @@ header { min-width: 44px; } -/* 侧边栏样式 */ +/* 设置页面样式 */ +.settings-actions { + margin-top: 32px; + padding-top: 24px; + border-top: 1px solid var(--border-color); + display: flex; + justify-content: flex-end; + gap: 12px; +} + +/* 旧侧边栏样式(保留用于对话页面内的历史对话侧边栏) */ .sidebar { width: 280px; background: var(--bg-secondary); @@ -355,7 +766,7 @@ header { flex-direction: column; flex: 1; min-width: 0; - background: var(--bg-secondary); + background: #f5f7fa; overflow: hidden; height: 100%; } @@ -365,7 +776,7 @@ header { overflow-y: auto; overflow-x: hidden; padding: 24px; - background: var(--bg-secondary); + background: #f5f7fa; display: flex; flex-direction: column; min-height: 0; @@ -1365,12 +1776,12 @@ header { } header { - padding: 16px 20px; + padding: 10px 20px; flex-shrink: 0; } .logo h1 { - font-size: 1.5rem; + font-size: 1.25rem; } .header-subtitle { @@ -1382,7 +1793,36 @@ header { min-height: 0; } + /* 主侧边栏在移动设备上可以折叠或调整 */ + .main-sidebar { + width: 200px; + } + + .main-sidebar.collapsed { + width: 64px; + } + + .sidebar-collapse-btn { + width: 28px; + height: 28px; + } + + .main-sidebar-header .logo span { + font-size: 1rem; + } + + .nav-item-content { + padding: 10px 12px; + font-size: 0.875rem; + } + + .nav-item-content span { + font-size: 0.875rem; + } + + .conversation-sidebar, .sidebar { + width: 240px; height: 100%; overflow: hidden; } @@ -1410,6 +1850,19 @@ header { flex-shrink: 0; } + .page-header { + padding: 16px; + flex-wrap: wrap; + } + + .page-header h2 { + font-size: 1.25rem; + } + + .page-content { + padding: 16px; + } + .modal-content { width: 95%; margin: 10% auto; diff --git a/web/static/js/monitor.js b/web/static/js/monitor.js index 144dd7ee..a56f2e4e 100644 --- a/web/static/js/monitor.js +++ b/web/static/js/monitor.js @@ -911,42 +911,17 @@ const monitorState = { }; function openMonitorPanel() { - const modal = document.getElementById('monitor-modal'); - if (!modal) { - return; + // 切换到MCP监控页面 + if (typeof switchPage === 'function') { + switchPage('mcp-monitor'); } - modal.style.display = 'block'; - - // 重置显示状态 - const statsContainer = document.getElementById('monitor-stats'); - const execContainer = document.getElementById('monitor-executions'); - if (statsContainer) { - statsContainer.innerHTML = '
加载中...
'; - } - if (execContainer) { - execContainer.innerHTML = '
加载中...
'; - } - - const statusFilter = document.getElementById('monitor-status-filter'); - if (statusFilter) { - statusFilter.value = 'all'; - } - - // 重置分页状态 - monitorState.pagination = { - page: 1, - pageSize: 20, - total: 0, - totalPages: 0 - }; - - refreshMonitorPanel(1); } function closeMonitorPanel() { - const modal = document.getElementById('monitor-modal'); - if (modal) { - modal.style.display = 'none'; + // 不再需要关闭功能,因为现在是页面而不是模态框 + // 如果需要,可以切换回对话页面 + if (typeof switchPage === 'function') { + switchPage('chat'); } } diff --git a/web/static/js/router.js b/web/static/js/router.js new file mode 100644 index 00000000..9685695a --- /dev/null +++ b/web/static/js/router.js @@ -0,0 +1,238 @@ +// 页面路由管理 +let currentPage = 'chat'; + +// 初始化路由 +function initRouter() { + // 默认显示对话页面 + switchPage('chat'); + + // 从URL hash读取页面(如果有) + const hash = window.location.hash.slice(1); + if (hash && ['chat', 'mcp-monitor', 'mcp-management', 'settings'].includes(hash)) { + switchPage(hash); + } +} + +// 切换页面 +function switchPage(pageId) { + // 隐藏所有页面 + document.querySelectorAll('.page').forEach(page => { + page.classList.remove('active'); + }); + + // 显示目标页面 + const targetPage = document.getElementById(`page-${pageId}`); + if (targetPage) { + targetPage.classList.add('active'); + currentPage = pageId; + + // 更新URL hash + window.location.hash = pageId; + + // 更新导航状态 + updateNavState(pageId); + + // 页面特定的初始化 + initPage(pageId); + } +} + +// 更新导航状态 +function updateNavState(pageId) { + // 移除所有活动状态 + document.querySelectorAll('.nav-item').forEach(item => { + item.classList.remove('active'); + }); + + document.querySelectorAll('.nav-submenu-item').forEach(item => { + item.classList.remove('active'); + }); + + // 设置活动状态 + if (pageId === 'mcp-monitor' || pageId === 'mcp-management') { + // MCP子菜单项 + const mcpItem = document.querySelector('.nav-item[data-page="mcp"]'); + if (mcpItem) { + mcpItem.classList.add('active'); + // 展开MCP子菜单 + mcpItem.classList.add('expanded'); + } + + const submenuItem = document.querySelector(`.nav-submenu-item[data-page="${pageId}"]`); + if (submenuItem) { + submenuItem.classList.add('active'); + } + } else { + // 主菜单项 + const navItem = document.querySelector(`.nav-item[data-page="${pageId}"]`); + if (navItem) { + navItem.classList.add('active'); + } + } +} + +// 切换子菜单 +function toggleSubmenu(menuId) { + const sidebar = document.getElementById('main-sidebar'); + const navItem = document.querySelector(`.nav-item[data-page="${menuId}"]`); + + if (!navItem) return; + + // 检查侧边栏是否折叠 + if (sidebar && sidebar.classList.contains('collapsed')) { + // 折叠状态下显示弹出菜单 + showSubmenuPopup(navItem, menuId); + } else { + // 展开状态下正常切换子菜单 + navItem.classList.toggle('expanded'); + } +} + +// 显示子菜单弹出框 +function showSubmenuPopup(navItem, menuId) { + // 移除其他已打开的弹出菜单 + const existingPopup = document.querySelector('.submenu-popup'); + if (existingPopup) { + existingPopup.remove(); + return; // 如果已经打开,点击时关闭 + } + + const navItemContent = navItem.querySelector('.nav-item-content'); + const submenu = navItem.querySelector('.nav-submenu'); + + if (!submenu) return; + + // 获取菜单位置 + const rect = navItemContent.getBoundingClientRect(); + + // 创建弹出菜单 + const popup = document.createElement('div'); + popup.className = 'submenu-popup'; + popup.style.position = 'fixed'; + popup.style.left = (rect.right + 8) + 'px'; + popup.style.top = rect.top + 'px'; + popup.style.zIndex = '1000'; + + // 复制子菜单项到弹出菜单 + const submenuItems = submenu.querySelectorAll('.nav-submenu-item'); + submenuItems.forEach(item => { + const popupItem = document.createElement('div'); + popupItem.className = 'submenu-popup-item'; + popupItem.textContent = item.textContent.trim(); + + // 检查是否是当前激活的页面 + const pageId = item.getAttribute('data-page'); + if (pageId && document.querySelector(`.nav-submenu-item[data-page="${pageId}"].active`)) { + popupItem.classList.add('active'); + } + + popupItem.onclick = function(e) { + e.stopPropagation(); + e.preventDefault(); + + // 获取页面ID并切换 + const pageId = item.getAttribute('data-page'); + if (pageId) { + switchPage(pageId); + } + + // 关闭弹出菜单 + popup.remove(); + document.removeEventListener('click', closePopup); + }; + popup.appendChild(popupItem); + }); + + document.body.appendChild(popup); + + // 点击外部关闭弹出菜单 + const closePopup = function(e) { + if (!popup.contains(e.target) && !navItem.contains(e.target)) { + popup.remove(); + document.removeEventListener('click', closePopup); + } + }; + + // 延迟添加事件监听,避免立即触发 + setTimeout(() => { + document.addEventListener('click', closePopup); + }, 0); +} + +// 初始化页面 +function initPage(pageId) { + switch(pageId) { + case 'chat': + // 对话页面已由chat.js初始化 + break; + case 'mcp-monitor': + // 初始化监控面板 + if (typeof refreshMonitorPanel === 'function') { + refreshMonitorPanel(); + } + break; + case 'mcp-management': + // 初始化MCP管理 + if (typeof loadExternalMCPs === 'function') { + loadExternalMCPs(); + } + // 加载工具列表(MCP工具配置已移到MCP管理页面) + if (typeof loadToolsList === 'function') { + // 确保工具分页设置已初始化 + if (typeof getToolsPageSize === 'function' && typeof toolsPagination !== 'undefined') { + toolsPagination.pageSize = getToolsPageSize(); + } + loadToolsList(1, ''); + } + break; + case 'settings': + // 初始化设置页面 + if (typeof loadConfig === 'function') { + loadConfig(); + } + break; + } +} + +// 页面加载完成后初始化路由 +document.addEventListener('DOMContentLoaded', function() { + initRouter(); + initSidebarState(); + + // 监听hash变化 + window.addEventListener('hashchange', function() { + const hash = window.location.hash.slice(1); + if (hash && ['chat', 'mcp-monitor', 'mcp-management', 'settings'].includes(hash)) { + switchPage(hash); + } + }); +}); + +// 切换侧边栏折叠/展开 +function toggleSidebar() { + const sidebar = document.getElementById('main-sidebar'); + if (sidebar) { + sidebar.classList.toggle('collapsed'); + // 保存折叠状态到localStorage + const isCollapsed = sidebar.classList.contains('collapsed'); + localStorage.setItem('sidebarCollapsed', isCollapsed ? 'true' : 'false'); + } +} + +// 初始化侧边栏状态 +function initSidebarState() { + const sidebar = document.getElementById('main-sidebar'); + if (sidebar) { + const savedState = localStorage.getItem('sidebarCollapsed'); + if (savedState === 'true') { + sidebar.classList.add('collapsed'); + } + } +} + +// 导出函数供其他脚本使用 +window.switchPage = switchPage; +window.toggleSubmenu = toggleSubmenu; +window.toggleSidebar = toggleSidebar; +window.currentPage = function() { return currentPage; }; + diff --git a/web/static/js/settings.js b/web/static/js/settings.js index 3cc586d0..16ca5383 100644 --- a/web/static/js/settings.js +++ b/web/static/js/settings.js @@ -19,8 +19,10 @@ let toolsPagination = { // 打开设置 async function openSettings() { - const modal = document.getElementById('settings-modal'); - modal.style.display = 'block'; + // 切换到设置页面 + if (typeof switchPage === 'function') { + switchPage('settings'); + } // 每次打开时清空全局状态映射,重新加载最新配置 toolStateMap.clear(); @@ -34,27 +36,22 @@ async function openSettings() { }); } -// 关闭设置 +// 关闭设置(保留函数以兼容旧代码,但现在不需要关闭功能) function closeSettings() { - const modal = document.getElementById('settings-modal'); - modal.style.display = 'none'; + // 不再需要关闭功能,因为现在是页面而不是模态框 + // 如果需要,可以切换回对话页面 + if (typeof switchPage === 'function') { + switchPage('chat'); + } } -// 点击模态框外部关闭 +// 点击模态框外部关闭(只保留MCP详情模态框) window.onclick = function(event) { - const settingsModal = document.getElementById('settings-modal'); const mcpModal = document.getElementById('mcp-detail-modal'); - const monitorModal = document.getElementById('monitor-modal'); - if (event.target === settingsModal) { - closeSettings(); - } if (event.target === mcpModal) { closeMCPDetail(); } - if (event.target === monitorModal) { - closeMonitorPanel(); - } } // 加载配置 @@ -623,6 +620,122 @@ async function applySettings() { } } +// 保存工具配置(独立函数,用于MCP管理页面) +async function saveToolsConfig() { + try { + // 先保存当前页的状态到全局映射 + saveCurrentPageToolStates(); + + // 获取当前配置(只获取工具部分) + const response = await apiFetch('/api/config'); + if (!response.ok) { + throw new Error('获取配置失败'); + } + + const currentConfig = await response.json(); + + // 构建只包含工具配置的配置对象 + const config = { + openai: currentConfig.openai || {}, + agent: currentConfig.agent || {}, + tools: [] + }; + + // 收集工具启用状态(与applySettings中的逻辑相同) + try { + const allToolsMap = new Map(); + let page = 1; + let hasMore = true; + const pageSize = 100; + + // 遍历所有页面获取所有工具 + while (hasMore) { + const url = `/api/config/tools?page=${page}&page_size=${pageSize}`; + + const pageResponse = await apiFetch(url); + if (!pageResponse.ok) { + throw new Error('获取工具列表失败'); + } + + const pageResult = await pageResponse.json(); + + // 将工具添加到映射中 + pageResult.tools.forEach(tool => { + const savedState = toolStateMap.get(tool.name); + allToolsMap.set(tool.name, { + name: tool.name, + enabled: savedState ? savedState.enabled : tool.enabled, + is_external: savedState ? savedState.is_external : (tool.is_external || false), + external_mcp: savedState ? savedState.external_mcp : (tool.external_mcp || '') + }); + }); + + // 检查是否还有更多页面 + if (page >= pageResult.total_pages) { + hasMore = false; + } else { + page++; + } + } + + // 将所有工具添加到配置中 + allToolsMap.forEach(tool => { + config.tools.push({ + name: tool.name, + enabled: tool.enabled, + is_external: tool.is_external, + external_mcp: tool.external_mcp + }); + }); + } catch (error) { + console.warn('获取所有工具列表失败,仅使用全局状态映射', error); + // 如果获取失败,使用全局状态映射 + toolStateMap.forEach((toolData, toolName) => { + config.tools.push({ + name: toolName, + enabled: toolData.enabled, + is_external: toolData.is_external, + external_mcp: toolData.external_mcp + }); + }); + } + + // 更新配置 + const updateResponse = await apiFetch('/api/config', { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(config) + }); + + if (!updateResponse.ok) { + const error = await updateResponse.json(); + throw new Error(error.error || '更新配置失败'); + } + + // 应用配置 + const applyResponse = await apiFetch('/api/config/apply', { + method: 'POST' + }); + + if (!applyResponse.ok) { + const error = await applyResponse.json(); + throw new Error(error.error || '应用配置失败'); + } + + alert('工具配置已成功保存!'); + + // 重新加载工具列表以反映最新状态 + if (typeof loadToolsList === 'function') { + await loadToolsList(toolsPagination.page, toolsSearchKeyword); + } + } catch (error) { + console.error('保存工具配置失败:', error); + alert('保存工具配置失败: ' + error.message); + } +} + function resetPasswordForm() { const currentInput = document.getElementById('auth-current-password'); const newInput = document.getElementById('auth-new-password'); diff --git a/web/templates/index.html b/web/templates/index.html index 26ae191c..4d6741e2 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -36,199 +36,250 @@

AI 驱动的自动化安全测试平台

- -
- -
- - -