From 31b2aae5682a8b751c351c8cdd2a36a4fd0b807f 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, 28 Dec 2025 23:22:41 +0800 Subject: [PATCH] Add files via upload --- internal/database/monitor.go | 25 +++++++--- internal/handler/monitor.go | 35 +++++++++++--- web/static/js/monitor.js | 88 +++++++++++++++++++++++++++++------- 3 files changed, 119 insertions(+), 29 deletions(-) diff --git a/internal/database/monitor.go b/internal/database/monitor.go index 5d8c1cbc..5e3b704f 100644 --- a/internal/database/monitor.go +++ b/internal/database/monitor.go @@ -70,10 +70,15 @@ func (db *DB) SaveToolExecution(exec *mcp.ToolExecution) error { } // CountToolExecutions 统计工具执行记录总数 -func (db *DB) CountToolExecutions() (int, error) { +func (db *DB) CountToolExecutions(status string) (int, error) { query := `SELECT COUNT(*) FROM tool_executions` + args := []interface{}{} + if status != "" { + query += ` WHERE status = ?` + args = append(args, status) + } var count int - err := db.QueryRow(query).Scan(&count) + err := db.QueryRow(query, args...).Scan(&count) if err != nil { return 0, err } @@ -82,13 +87,14 @@ func (db *DB) CountToolExecutions() (int, error) { // LoadToolExecutions 加载所有工具执行记录(支持分页) func (db *DB) LoadToolExecutions() ([]*mcp.ToolExecution, error) { - return db.LoadToolExecutionsWithPagination(0, 1000) + return db.LoadToolExecutionsWithPagination(0, 1000, "") } // LoadToolExecutionsWithPagination 分页加载工具执行记录 // limit: 最大返回记录数,0 表示使用默认值 1000 // offset: 跳过的记录数,用于分页 -func (db *DB) LoadToolExecutionsWithPagination(offset, limit int) ([]*mcp.ToolExecution, error) { +// status: 状态筛选,空字符串表示不过滤 +func (db *DB) LoadToolExecutionsWithPagination(offset, limit int, status string) ([]*mcp.ToolExecution, error) { if limit <= 0 { limit = 1000 // 默认限制 } @@ -99,11 +105,16 @@ func (db *DB) LoadToolExecutionsWithPagination(offset, limit int) ([]*mcp.ToolEx query := ` SELECT id, tool_name, arguments, status, result, error, start_time, end_time, duration_ms FROM tool_executions - ORDER BY start_time DESC - LIMIT ? OFFSET ? ` + args := []interface{}{} + if status != "" { + query += ` WHERE status = ?` + args = append(args, status) + } + query += ` ORDER BY start_time DESC LIMIT ? OFFSET ?` + args = append(args, limit, offset) - rows, err := db.Query(query, limit, offset) + rows, err := db.Query(query, args...) if err != nil { return nil, err } diff --git a/internal/handler/monitor.go b/internal/handler/monitor.go index e901904f..6999d983 100644 --- a/internal/handler/monitor.go +++ b/internal/handler/monitor.go @@ -64,7 +64,10 @@ func (h *MonitorHandler) Monitor(c *gin.Context) { } } - executions, total := h.loadExecutionsWithPagination(page, pageSize) + // 解析状态筛选参数 + status := c.Query("status") + + executions, total := h.loadExecutionsWithPagination(page, pageSize, status) stats := h.loadStats() totalPages := (total + pageSize - 1) / pageSize @@ -84,13 +87,23 @@ func (h *MonitorHandler) Monitor(c *gin.Context) { } func (h *MonitorHandler) loadExecutions() []*mcp.ToolExecution { - executions, _ := h.loadExecutionsWithPagination(1, 1000) + executions, _ := h.loadExecutionsWithPagination(1, 1000, "") return executions } -func (h *MonitorHandler) loadExecutionsWithPagination(page, pageSize int) ([]*mcp.ToolExecution, int) { +func (h *MonitorHandler) loadExecutionsWithPagination(page, pageSize int, status string) ([]*mcp.ToolExecution, int) { if h.db == nil { allExecutions := h.mcpServer.GetAllExecutions() + // 如果指定了状态筛选,先进行筛选 + if status != "" { + filtered := make([]*mcp.ToolExecution, 0) + for _, exec := range allExecutions { + if exec.Status == status { + filtered = append(filtered, exec) + } + } + allExecutions = filtered + } total := len(allExecutions) offset := (page - 1) * pageSize end := offset + pageSize @@ -104,10 +117,20 @@ func (h *MonitorHandler) loadExecutionsWithPagination(page, pageSize int) ([]*mc } offset := (page - 1) * pageSize - executions, err := h.db.LoadToolExecutionsWithPagination(offset, pageSize) + executions, err := h.db.LoadToolExecutionsWithPagination(offset, pageSize, status) if err != nil { h.logger.Warn("从数据库加载执行记录失败,回退到内存数据", zap.Error(err)) allExecutions := h.mcpServer.GetAllExecutions() + // 如果指定了状态筛选,先进行筛选 + if status != "" { + filtered := make([]*mcp.ToolExecution, 0) + for _, exec := range allExecutions { + if exec.Status == status { + filtered = append(filtered, exec) + } + } + allExecutions = filtered + } total := len(allExecutions) offset := (page - 1) * pageSize end := offset + pageSize @@ -120,8 +143,8 @@ func (h *MonitorHandler) loadExecutionsWithPagination(page, pageSize int) ([]*mc return allExecutions[offset:end], total } - // 获取总数 - total, err := h.db.CountToolExecutions() + // 获取总数(考虑状态筛选) + total, err := h.db.CountToolExecutions(status) if err != nil { h.logger.Warn("获取执行记录总数失败", zap.Error(err)) // 回退:使用已加载的记录数估算 diff --git a/web/static/js/monitor.js b/web/static/js/monitor.js index 0a6feb33..2d098db3 100644 --- a/web/static/js/monitor.js +++ b/web/static/js/monitor.js @@ -972,7 +972,17 @@ async function refreshMonitorPanel(page = null) { const currentPage = page !== null ? page : monitorState.pagination.page; const pageSize = monitorState.pagination.pageSize; - const response = await apiFetch(`/api/monitor?page=${currentPage}&page_size=${pageSize}`, { method: 'GET' }); + // 获取当前的筛选条件 + const statusFilter = document.getElementById('monitor-status-filter'); + const currentFilter = statusFilter ? statusFilter.value : 'all'; + + // 构建请求 URL + let url = `/api/monitor?page=${currentPage}&page_size=${pageSize}`; + if (currentFilter && currentFilter !== 'all') { + url += `&status=${encodeURIComponent(currentFilter)}`; + } + + const response = await apiFetch(url, { method: 'GET' }); const result = await response.json().catch(() => ({})); if (!response.ok) { throw new Error(result.error || '获取监控数据失败'); @@ -993,7 +1003,7 @@ async function refreshMonitorPanel(page = null) { } renderMonitorStats(monitorState.stats, monitorState.lastFetchedAt); - renderMonitorExecutions(monitorState.executions); + renderMonitorExecutions(monitorState.executions, currentFilter); renderMonitorPagination(); } catch (error) { console.error('刷新监控面板失败:', error); @@ -1006,10 +1016,59 @@ async function refreshMonitorPanel(page = null) { } } -function applyMonitorFilters() { +async function applyMonitorFilters() { const statusFilter = document.getElementById('monitor-status-filter'); const status = statusFilter ? statusFilter.value : 'all'; - renderMonitorExecutions(monitorState.executions, status); + // 当筛选条件改变时,从后端重新获取数据 + await refreshMonitorPanelWithFilter(status); +} + +async function refreshMonitorPanelWithFilter(statusFilter = 'all') { + const statsContainer = document.getElementById('monitor-stats'); + const execContainer = document.getElementById('monitor-executions'); + + try { + const currentPage = 1; // 筛选时重置到第一页 + const pageSize = monitorState.pagination.pageSize; + + // 构建请求 URL + let url = `/api/monitor?page=${currentPage}&page_size=${pageSize}`; + if (statusFilter && statusFilter !== 'all') { + url += `&status=${encodeURIComponent(statusFilter)}`; + } + + const response = await apiFetch(url, { method: 'GET' }); + const result = await response.json().catch(() => ({})); + if (!response.ok) { + throw new Error(result.error || '获取监控数据失败'); + } + + monitorState.executions = Array.isArray(result.executions) ? result.executions : []; + monitorState.stats = result.stats || {}; + monitorState.lastFetchedAt = new Date(); + + // 更新分页信息 + if (result.total !== undefined) { + monitorState.pagination = { + page: result.page || currentPage, + pageSize: result.page_size || pageSize, + total: result.total || 0, + totalPages: result.total_pages || 1 + }; + } + + renderMonitorStats(monitorState.stats, monitorState.lastFetchedAt); + renderMonitorExecutions(monitorState.executions, statusFilter); + renderMonitorPagination(); + } catch (error) { + console.error('刷新监控面板失败:', error); + if (statsContainer) { + statsContainer.innerHTML = `
无法加载统计信息:${escapeHtml(error.message)}
`; + } + if (execContainer) { + execContainer.innerHTML = `
无法加载执行记录:${escapeHtml(error.message)}
`; + } + } } function renderMonitorStats(statsMap = {}, lastFetchedAt = null) { @@ -1091,21 +1150,18 @@ function renderMonitorExecutions(executions = [], statusFilter = 'all') { } if (!Array.isArray(executions) || executions.length === 0) { - container.innerHTML = '
暂无执行记录
'; + // 根据是否有筛选条件显示不同的提示 + if (statusFilter && statusFilter !== 'all') { + container.innerHTML = '
当前筛选条件下暂无记录
'; + } else { + container.innerHTML = '
暂无执行记录
'; + } return; } - const normalizedStatus = statusFilter === 'all' ? null : statusFilter; - const filtered = normalizedStatus - ? executions.filter(exec => (exec.status || '').toLowerCase() === normalizedStatus) - : executions; - - if (filtered.length === 0) { - container.innerHTML = '
当前筛选条件下暂无记录
'; - return; - } - - const rows = filtered + // 由于筛选已经在后端完成,这里直接使用所有传入的执行记录 + // 不再需要前端再次筛选,因为后端已经返回了筛选后的数据 + const rows = executions .map(exec => { const status = (exec.status || 'unknown').toLowerCase(); const statusClass = `monitor-status-chip ${status}`;