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 = `