From 895ae1a5f048fb6eda528d4c4dc736c0cf3fe656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Sun, 16 Nov 2025 21:44:29 +0800 Subject: [PATCH] Add files via upload --- data/conversations.db | Bin 0 -> 4096 bytes data/conversations.db-shm | Bin 0 -> 32768 bytes data/conversations.db-wal | Bin 0 -> 869352 bytes internal/app/app.go | 1 + internal/database/monitor.go | 54 +++++++++ internal/handler/monitor.go | 55 +++++++++ web/static/css/style.css | 28 +++++ web/static/js/app.js | 218 ++++++++++++++++++++++++++++------- 8 files changed, 312 insertions(+), 44 deletions(-) create mode 100644 data/conversations.db create mode 100644 data/conversations.db-shm create mode 100644 data/conversations.db-wal diff --git a/data/conversations.db b/data/conversations.db new file mode 100644 index 0000000000000000000000000000000000000000..4ebf78cf96088c7148cb47caf804cc036de75a34 GIT binary patch literal 4096 zcmWFz^vNtqRY=P(%1ta$FlG>7U}9o$P*7lCU|@t|AVoG{WY8;GzzfnYK(-m98b?E5 nGz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!nC=3ArfDQ*E literal 0 HcmV?d00001 diff --git a/data/conversations.db-shm b/data/conversations.db-shm new file mode 100644 index 0000000000000000000000000000000000000000..82b33425ba86d1e2265b8a8966635fe58b44283a GIT binary patch literal 32768 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 literal 0 HcmV?d00001 diff --git a/internal/app/app.go b/internal/app/app.go index f16d553e..addef393 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -233,6 +233,7 @@ func setupRoutes( // 监控 protected.GET("/monitor", monitorHandler.Monitor) protected.GET("/monitor/execution/:id", monitorHandler.GetExecution) + protected.DELETE("/monitor/execution/:id", monitorHandler.DeleteExecution) protected.GET("/monitor/stats", monitorHandler.GetStats) // 配置管理 diff --git a/internal/database/monitor.go b/internal/database/monitor.go index 06807fb5..5d8c1cbc 100644 --- a/internal/database/monitor.go +++ b/internal/database/monitor.go @@ -232,6 +232,17 @@ func (db *DB) GetToolExecution(id string) (*mcp.ToolExecution, error) { return &exec, nil } +// DeleteToolExecution 删除工具执行记录 +func (db *DB) DeleteToolExecution(id string) error { + query := `DELETE FROM tool_executions WHERE id = ?` + _, err := db.Exec(query, id) + if err != nil { + db.logger.Error("删除工具执行记录失败", zap.Error(err), zap.String("executionId", id)) + return err + } + return nil +} + // SaveToolStats 保存工具统计信息 func (db *DB) SaveToolStats(toolName string, stats *mcp.ToolStats) error { var lastCallTime sql.NullTime @@ -332,3 +343,46 @@ func (db *DB) UpdateToolStats(toolName string, totalCalls, successCalls, failedC return nil } + +// DecreaseToolStats 减少工具统计信息(用于删除执行记录时) +// 如果统计信息变为0,则删除该统计记录 +func (db *DB) DecreaseToolStats(toolName string, totalCalls, successCalls, failedCalls int) error { + // 先更新统计信息 + query := ` + UPDATE tool_stats SET + total_calls = CASE WHEN total_calls - ? < 0 THEN 0 ELSE total_calls - ? END, + success_calls = CASE WHEN success_calls - ? < 0 THEN 0 ELSE success_calls - ? END, + failed_calls = CASE WHEN failed_calls - ? < 0 THEN 0 ELSE failed_calls - ? END, + updated_at = ? + WHERE tool_name = ? + ` + + _, err := db.Exec(query, totalCalls, totalCalls, successCalls, successCalls, failedCalls, failedCalls, time.Now(), toolName) + if err != nil { + db.logger.Error("减少工具统计信息失败", zap.Error(err), zap.String("toolName", toolName)) + return err + } + + // 检查更新后的 total_calls 是否为 0,如果是则删除该统计记录 + checkQuery := `SELECT total_calls FROM tool_stats WHERE tool_name = ?` + var newTotalCalls int + err = db.QueryRow(checkQuery, toolName).Scan(&newTotalCalls) + if err != nil { + // 如果查询失败(记录不存在),直接返回 + return nil + } + + // 如果 total_calls 为 0,删除该统计记录 + if newTotalCalls == 0 { + deleteQuery := `DELETE FROM tool_stats WHERE tool_name = ?` + _, err = db.Exec(deleteQuery, toolName) + if err != nil { + db.logger.Warn("删除零统计记录失败", zap.Error(err), zap.String("toolName", toolName)) + // 不返回错误,因为主要操作(更新统计)已成功 + } else { + db.logger.Info("已删除零统计记录", zap.String("toolName", toolName)) + } + } + + return nil +} diff --git a/internal/handler/monitor.go b/internal/handler/monitor.go index a7155c83..e901904f 100644 --- a/internal/handler/monitor.go +++ b/internal/handler/monitor.go @@ -220,4 +220,59 @@ func (h *MonitorHandler) GetStats(c *gin.Context) { c.JSON(http.StatusOK, stats) } +// DeleteExecution 删除执行记录 +func (h *MonitorHandler) DeleteExecution(c *gin.Context) { + id := c.Param("id") + if id == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "执行记录ID不能为空"}) + return + } + + // 如果使用数据库,先获取执行记录信息,然后删除并更新统计 + if h.db != nil { + // 先获取执行记录信息(用于更新统计) + exec, err := h.db.GetToolExecution(id) + if err != nil { + // 如果找不到记录,可能已经被删除,直接返回成功 + h.logger.Warn("执行记录不存在,可能已被删除", zap.String("executionId", id), zap.Error(err)) + c.JSON(http.StatusOK, gin.H{"message": "执行记录不存在或已被删除"}) + return + } + + // 删除执行记录 + err = h.db.DeleteToolExecution(id) + if err != nil { + h.logger.Error("删除执行记录失败", zap.Error(err), zap.String("executionId", id)) + c.JSON(http.StatusInternalServerError, gin.H{"error": "删除执行记录失败: " + err.Error()}) + return + } + + // 更新统计信息(减少相应的计数) + totalCalls := 1 + successCalls := 0 + failedCalls := 0 + if exec.Status == "failed" { + failedCalls = 1 + } else if exec.Status == "completed" { + successCalls = 1 + } + + if exec.ToolName != "" { + if err := h.db.DecreaseToolStats(exec.ToolName, totalCalls, successCalls, failedCalls); err != nil { + h.logger.Warn("更新统计信息失败", zap.Error(err), zap.String("toolName", exec.ToolName)) + // 不返回错误,因为记录已经删除成功 + } + } + + h.logger.Info("执行记录已从数据库删除", zap.String("executionId", id), zap.String("toolName", exec.ToolName)) + c.JSON(http.StatusOK, gin.H{"message": "执行记录已删除"}) + return + } + + // 如果不使用数据库,尝试从内存中删除(内部MCP服务器) + // 注意:内存中的记录可能已经被清理,所以这里只记录日志 + h.logger.Info("尝试删除内存中的执行记录", zap.String("executionId", id)) + c.JSON(http.StatusOK, gin.H{"message": "执行记录已删除(如果存在)"}) +} + diff --git a/web/static/css/style.css b/web/static/css/style.css index 4335be03..bb845c16 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -1842,6 +1842,24 @@ header { border-color: var(--accent-color); } +.btn-danger { + padding: 10px 20px; + background: rgba(220, 53, 69, 0.08); + color: var(--error-color, #dc3545); + border: 1px solid rgba(220, 53, 69, 0.3); + border-radius: 6px; + font-size: 0.9375rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; +} + +.btn-danger:hover { + background: rgba(220, 53, 69, 0.15); + border-color: var(--error-color, #dc3545); + color: #c82333; +} + .monitor-modal-content { max-width: 1080px; width: 95%; @@ -2005,6 +2023,16 @@ header { font-size: 0.75rem; } +.monitor-execution-actions .btn-delete { + color: var(--error-color, #dc3545); +} + +.monitor-execution-actions .btn-delete:hover { + background: rgba(220, 53, 69, 0.1); + border-color: rgba(220, 53, 69, 0.4); + color: #c82333; +} + .monitor-vuln-container { display: grid; gap: 16px; diff --git a/web/static/js/app.js b/web/static/js/app.js index 542a20d7..43750f47 100644 --- a/web/static/js/app.js +++ b/web/static/js/app.js @@ -1688,6 +1688,9 @@ async function cancelActiveTask(conversationId, button) { // 设置相关功能 let currentConfig = null; let allTools = []; +// 全局工具状态映射,用于保存用户在所有页面的修改 +// key: tool.name, value: { enabled: boolean, is_external: boolean, external_mcp: string } +let toolStateMap = new Map(); // 从localStorage读取每页显示数量,默认为20 const getToolsPageSize = () => { const saved = localStorage.getItem('toolsPageSize'); @@ -1706,6 +1709,9 @@ async function openSettings() { const modal = document.getElementById('settings-modal'); modal.style.display = 'block'; + // 每次打开时清空全局状态映射,重新加载最新配置 + toolStateMap.clear(); + // 每次打开时重新加载最新配置 await loadConfig(); @@ -1775,6 +1781,9 @@ let toolsSearchKeyword = ''; // 加载工具列表(分页) async function loadToolsList(page = 1, searchKeyword = '') { try { + // 在加载新页面之前,先保存当前页的状态到全局映射 + saveCurrentPageToolStates(); + const pageSize = toolsPagination.pageSize; let url = `/api/config/tools?page=${page}&page_size=${pageSize}`; if (searchKeyword) { @@ -1795,6 +1804,17 @@ async function loadToolsList(page = 1, searchKeyword = '') { totalPages: result.total_pages || 1 }; + // 初始化工具状态映射(如果工具不在映射中,使用服务器返回的状态) + allTools.forEach(tool => { + if (!toolStateMap.has(tool.name)) { + toolStateMap.set(tool.name, { + enabled: tool.enabled, + is_external: tool.is_external || false, + external_mcp: tool.external_mcp || '' + }); + } + }); + renderToolsList(); renderToolsPagination(); } catch (error) { @@ -1806,6 +1826,23 @@ async function loadToolsList(page = 1, searchKeyword = '') { } } +// 保存当前页的工具状态到全局映射 +function saveCurrentPageToolStates() { + document.querySelectorAll('#tools-list .tool-item').forEach(item => { + const checkbox = item.querySelector('input[type="checkbox"]'); + const toolName = item.dataset.toolName; + const isExternal = item.dataset.isExternal === 'true'; + const externalMcp = item.dataset.externalMcp || ''; + if (toolName && checkbox) { + toolStateMap.set(toolName, { + enabled: checkbox.checked, + is_external: isExternal, + external_mcp: externalMcp + }); + } + }); +} + // 搜索工具 function searchTools() { const searchInput = document.getElementById('tools-search'); @@ -1859,11 +1896,18 @@ function renderToolsList() { toolItem.dataset.isExternal = tool.is_external ? 'true' : 'false'; toolItem.dataset.externalMcp = tool.external_mcp || ''; + // 从全局状态映射获取工具状态,如果不存在则使用服务器返回的状态 + const toolState = toolStateMap.get(tool.name) || { + enabled: tool.enabled, + is_external: tool.is_external || false, + external_mcp: tool.external_mcp || '' + }; + // 外部工具标签 - const externalBadge = tool.is_external ? '外部' : ''; + const externalBadge = toolState.is_external ? '外部' : ''; toolItem.innerHTML = ` - +
${escapeHtml(tool.name)} @@ -1932,10 +1976,40 @@ function renderToolsPagination() { toolsList.appendChild(pagination); } +// 处理工具checkbox状态变化 +function handleToolCheckboxChange(toolName, enabled) { + // 更新全局状态映射 + const toolItem = document.querySelector(`.tool-item[data-tool-name="${toolName}"]`); + if (toolItem) { + const isExternal = toolItem.dataset.isExternal === 'true'; + const externalMcp = toolItem.dataset.externalMcp || ''; + toolStateMap.set(toolName, { + enabled: enabled, + is_external: isExternal, + external_mcp: externalMcp + }); + } + updateToolsStats(); +} + // 全选工具 function selectAllTools() { document.querySelectorAll('#tools-list input[type="checkbox"]').forEach(checkbox => { checkbox.checked = true; + // 更新全局状态映射 + const toolItem = checkbox.closest('.tool-item'); + if (toolItem) { + const toolName = toolItem.dataset.toolName; + const isExternal = toolItem.dataset.isExternal === 'true'; + const externalMcp = toolItem.dataset.externalMcp || ''; + if (toolName) { + toolStateMap.set(toolName, { + enabled: true, + is_external: isExternal, + external_mcp: externalMcp + }); + } + } }); updateToolsStats(); } @@ -1944,6 +2018,20 @@ function selectAllTools() { function deselectAllTools() { document.querySelectorAll('#tools-list input[type="checkbox"]').forEach(checkbox => { checkbox.checked = false; + // 更新全局状态映射 + const toolItem = checkbox.closest('.tool-item'); + if (toolItem) { + const toolName = toolItem.dataset.toolName; + const isExternal = toolItem.dataset.isExternal === 'true'; + const externalMcp = toolItem.dataset.externalMcp || ''; + if (toolName) { + toolStateMap.set(toolName, { + enabled: false, + is_external: isExternal, + external_mcp: externalMcp + }); + } + } }); updateToolsStats(); } @@ -1980,6 +2068,9 @@ async function updateToolsStats() { const statsEl = document.getElementById('tools-stats'); if (!statsEl) return; + // 先保存当前页的状态到全局映射 + saveCurrentPageToolStates(); + // 计算当前页的启用工具数 const currentPageEnabled = Array.from(document.querySelectorAll('#tools-list input[type="checkbox"]:checked')).length; const currentPageTotal = document.querySelectorAll('#tools-list input[type="checkbox"]').length; @@ -1993,22 +2084,32 @@ async function updateToolsStats() { if (toolsSearchKeyword) { totalTools = allTools.length; totalEnabled = allTools.filter(tool => { + // 优先使用全局状态映射,否则使用checkbox状态,最后使用服务器返回的状态 + const savedState = toolStateMap.get(tool.name); + if (savedState !== undefined) { + return savedState.enabled; + } const checkbox = document.getElementById(`tool-${tool.name}`); return checkbox ? checkbox.checked : tool.enabled; }).length; } else { // 没有搜索时,需要获取所有工具的状态 - // 先使用当前已知的工具状态 - const toolStateMap = new Map(); + // 先使用全局状态映射和当前页的checkbox状态 + const localStateMap = new Map(); - // 从当前页的checkbox获取状态 + // 从当前页的checkbox获取状态(如果全局映射中没有) allTools.forEach(tool => { - const checkbox = document.getElementById(`tool-${tool.name}`); - if (checkbox) { - toolStateMap.set(tool.name, checkbox.checked); + const savedState = toolStateMap.get(tool.name); + if (savedState !== undefined) { + localStateMap.set(tool.name, savedState.enabled); } else { - // 如果checkbox不存在(不在当前页),使用工具原始状态 - toolStateMap.set(tool.name, tool.enabled); + const checkbox = document.getElementById(`tool-${tool.name}`); + if (checkbox) { + localStateMap.set(tool.name, checkbox.checked); + } else { + // 如果checkbox不存在(不在当前页),使用工具原始状态 + localStateMap.set(tool.name, tool.enabled); + } } }); @@ -2026,9 +2127,10 @@ async function updateToolsStats() { const pageResult = await pageResponse.json(); pageResult.tools.forEach(tool => { - // 如果工具不在当前页,使用服务器返回的状态 - if (!toolStateMap.has(tool.name)) { - toolStateMap.set(tool.name, tool.enabled); + // 优先使用全局状态映射,否则使用服务器返回的状态 + if (!localStateMap.has(tool.name)) { + const savedState = toolStateMap.get(tool.name); + localStateMap.set(tool.name, savedState ? savedState.enabled : tool.enabled); } }); @@ -2041,7 +2143,7 @@ async function updateToolsStats() { } // 计算启用的工具数 - totalEnabled = Array.from(toolStateMap.values()).filter(enabled => enabled).length; + totalEnabled = Array.from(localStateMap.values()).filter(enabled => enabled).length; } } catch (error) { console.warn('获取工具统计失败,使用当前页数据', error); @@ -2112,22 +2214,8 @@ async function applySettings() { }; // 收集工具启用状态 - // 由于使用分页,需要先获取所有工具的状态 - // 先获取当前页的工具状态 - const currentPageTools = new Map(); - document.querySelectorAll('#tools-list .tool-item').forEach(item => { - const checkbox = item.querySelector('input[type="checkbox"]'); - const toolName = item.dataset.toolName; - const isExternal = item.dataset.isExternal === 'true'; - const externalMcp = item.dataset.externalMcp || ''; - if (toolName) { - currentPageTools.set(toolName, { - enabled: checkbox.checked, - is_external: isExternal, - external_mcp: externalMcp - }); - } - }); + // 先保存当前页的状态到全局映射 + saveCurrentPageToolStates(); // 获取所有工具列表以获取完整状态(遍历所有页面) // 注意:无论是否在搜索状态下,都要获取所有工具的状态,以确保完整保存 @@ -2148,16 +2236,15 @@ async function applySettings() { const pageResult = await pageResponse.json(); - // 将当前页的工具添加到映射中 - // 如果工具在当前显示的页面中(匹配搜索且在当前页),使用当前页的修改 - // 否则使用服务器返回的状态 + // 将工具添加到映射中 + // 优先使用全局状态映射中的状态(用户修改过的),否则使用服务器返回的状态 pageResult.tools.forEach(tool => { - const currentPageTool = currentPageTools.get(tool.name); + const savedState = toolStateMap.get(tool.name); allToolsMap.set(tool.name, { name: tool.name, - enabled: currentPageTool ? currentPageTool.enabled : tool.enabled, - is_external: currentPageTool ? currentPageTool.is_external : (tool.is_external || false), - external_mcp: currentPageTool ? currentPageTool.external_mcp : (tool.external_mcp || '') + 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 || '') }); }); @@ -2179,9 +2266,9 @@ async function applySettings() { }); }); } catch (error) { - console.warn('获取所有工具列表失败,仅使用当前页工具状态', error); - // 如果获取失败,只使用当前页的工具 - currentPageTools.forEach((toolData, toolName) => { + console.warn('获取所有工具列表失败,仅使用全局状态映射', error); + // 如果获取失败,使用全局状态映射 + toolStateMap.forEach((toolData, toolName) => { config.tools.push({ name: toolName, enabled: toolData.enabled, @@ -2457,8 +2544,9 @@ function renderMonitorStats(statsMap = {}, lastFetchedAt = null) {
`; - // 显示最多前4个工具的统计 + // 显示最多前4个工具的统计(过滤掉 totalCalls 为 0 的工具) const topTools = entries + .filter(tool => (tool.totalCalls || 0) > 0) .slice() .sort((a, b) => (b.totalCalls || 0) - (a.totalCalls || 0)) .slice(0, 4); @@ -2518,6 +2606,7 @@ function renderMonitorExecutions(executions = [], statusFilter = 'all') {
+
@@ -2525,6 +2614,12 @@ function renderMonitorExecutions(executions = [], statusFilter = 'all') { }) .join(''); + // 先移除旧的表格容器(保留分页控件) + const oldTableContainer = container.querySelector('.monitor-table-container'); + if (oldTableContainer) { + oldTableContainer.remove(); + } + // 创建表格容器 const tableContainer = document.createElement('div'); tableContainer.className = 'monitor-table-container'; @@ -2543,9 +2638,13 @@ function renderMonitorExecutions(executions = [], statusFilter = 'all') { `; - // 清空容器并添加表格 - container.innerHTML = ''; - container.appendChild(tableContainer); + // 在分页控件之前插入表格(如果存在分页控件) + const existingPagination = container.querySelector('.monitor-pagination'); + if (existingPagination) { + container.insertBefore(tableContainer, existingPagination); + } else { + container.appendChild(tableContainer); + } } // 渲染监控面板分页控件 @@ -2588,6 +2687,37 @@ function renderMonitorPagination() { container.appendChild(pagination); } +// 删除执行记录 +async function deleteExecution(executionId) { + if (!executionId) { + return; + } + + // 确认删除 + if (!confirm('确定要删除此执行记录吗?此操作不可恢复。')) { + return; + } + + try { + const response = await apiFetch(`/api/monitor/execution/${executionId}`, { + method: 'DELETE' + }); + + if (!response.ok) { + const error = await response.json().catch(() => ({})); + throw new Error(error.error || '删除执行记录失败'); + } + + // 删除成功后刷新当前页面 + const currentPage = monitorState.pagination.page; + await refreshMonitorPanel(currentPage); + + alert('执行记录已删除'); + } catch (error) { + console.error('删除执行记录失败:', error); + alert('删除执行记录失败: ' + error.message); + } +} function formatExecutionDuration(start, end) { if (!start) {