// logs.js - 查询日志页面功能 // 全局变量 let currentPage = 1; let totalPages = 1; let logsPerPage = 30; // 默认显示30条记录 let currentFilter = ''; let currentSearch = ''; let logsChart = null; let currentSortField = ''; let currentSortDirection = 'desc'; // 默认降序 // IP地理位置缓存 let ipGeolocationCache = {}; const GEOLOCATION_CACHE_EXPIRY = 24 * 60 * 60 * 1000; // 缓存有效期24小时 // WebSocket连接和重连计时器 let logsWsConnection = null; let logsWsReconnectTimer = null; // 初始化查询日志页面 function initLogsPage() { console.log('初始化查询日志页面'); // 加载日志统计数据 loadLogsStats(); // 加载日志详情 loadLogs(); // 初始化图表 initLogsChart(); // 绑定事件 bindLogsEvents(); // 建立WebSocket连接,用于实时更新统计数据和图表 connectLogsWebSocket(); // 在页面卸载时清理资源 window.addEventListener('beforeunload', cleanupLogsResources); } // 清理资源 function cleanupLogsResources() { // 清除WebSocket连接 if (logsWsConnection) { logsWsConnection.close(); logsWsConnection = null; } // 清除重连计时器 if (logsWsReconnectTimer) { clearTimeout(logsWsReconnectTimer); logsWsReconnectTimer = null; } } // 绑定事件 function bindLogsEvents() { // 搜索按钮 const searchBtn = document.getElementById('logs-search-btn'); if (searchBtn) { searchBtn.addEventListener('click', () => { currentSearch = document.getElementById('logs-search').value; currentPage = 1; loadLogs(); }); } // 搜索框回车事件 const searchInput = document.getElementById('logs-search'); if (searchInput) { searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { currentSearch = searchInput.value; currentPage = 1; loadLogs(); } }); } // 结果过滤 const resultFilter = document.getElementById('logs-result-filter'); if (resultFilter) { resultFilter.addEventListener('change', () => { currentFilter = resultFilter.value; currentPage = 1; loadLogs(); }); } // 自定义记录数量 const perPageSelect = document.getElementById('logs-per-page'); if (perPageSelect) { perPageSelect.addEventListener('change', () => { logsPerPage = parseInt(perPageSelect.value); currentPage = 1; loadLogs(); }); } // 分页按钮 const prevBtn = document.getElementById('logs-prev-page'); const nextBtn = document.getElementById('logs-next-page'); if (prevBtn) { prevBtn.addEventListener('click', () => { if (currentPage > 1) { currentPage--; loadLogs(); } }); } if (nextBtn) { nextBtn.addEventListener('click', () => { if (currentPage < totalPages) { currentPage++; loadLogs(); } }); } // 时间范围切换 const timeRangeBtns = document.querySelectorAll('.time-range-btn'); timeRangeBtns.forEach(btn => { btn.addEventListener('click', () => { // 更新按钮样式 timeRangeBtns.forEach(b => { b.classList.remove('bg-primary', 'text-white'); b.classList.add('bg-gray-200', 'text-gray-700'); }); btn.classList.remove('bg-gray-200', 'text-gray-700'); btn.classList.add('bg-primary', 'text-white'); // 更新图表 const range = btn.getAttribute('data-range'); updateLogsChart(range); }); }); // 刷新按钮事件 const refreshBtn = document.getElementById('logs-refresh-btn'); if (refreshBtn) { refreshBtn.addEventListener('click', () => { // 重新加载日志 currentPage = 1; loadLogs(); }); } // 排序按钮事件 const sortHeaders = document.querySelectorAll('th[data-sort]'); sortHeaders.forEach(header => { header.addEventListener('click', () => { const sortField = header.getAttribute('data-sort'); // 如果点击的是当前排序字段,则切换排序方向 if (sortField === currentSortField) { currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc'; } else { // 否则,设置新的排序字段,默认降序 currentSortField = sortField; currentSortDirection = 'desc'; } // 更新排序图标 updateSortIcons(); // 重新加载日志 currentPage = 1; loadLogs(); }); }); } // 更新排序图标 function updateSortIcons() { const sortHeaders = document.querySelectorAll('th[data-sort]'); sortHeaders.forEach(header => { const sortField = header.getAttribute('data-sort'); const icon = header.querySelector('i'); // 重置所有图标 icon.className = 'fa fa-sort ml-1 text-xs'; // 设置当前排序字段的图标 if (sortField === currentSortField) { if (currentSortDirection === 'asc') { icon.className = 'fa fa-sort-asc ml-1 text-xs'; } else { icon.className = 'fa fa-sort-desc ml-1 text-xs'; } } }); } // 加载日志统计数据 function loadLogsStats() { // 使用封装的apiRequest函数进行API调用 apiRequest('/logs/stats') .then(data => { if (data && data.error) { console.error('加载日志统计数据失败:', data.error); return; } // 更新统计卡片 document.getElementById('logs-total-queries').textContent = data.totalQueries; document.getElementById('logs-avg-response-time').textContent = data.avgResponseTime.toFixed(2) + 'ms'; document.getElementById('logs-active-ips').textContent = data.activeIPs; // 计算屏蔽率 const blockRate = data.totalQueries > 0 ? (data.blockedQueries / data.totalQueries * 100).toFixed(1) : '0'; document.getElementById('logs-block-rate').textContent = blockRate + '%'; }) .catch(error => { console.error('加载日志统计数据失败:', error); }); } // 加载日志详情 function loadLogs() { // 显示加载状态 const loadingEl = document.getElementById('logs-loading'); if (loadingEl) { loadingEl.classList.remove('hidden'); } // 构建请求URL let endpoint = `/logs/query?limit=${logsPerPage}&offset=${(currentPage - 1) * logsPerPage}`; // 添加过滤条件 if (currentFilter) { endpoint += `&result=${currentFilter}`; } // 添加搜索条件 if (currentSearch) { endpoint += `&search=${encodeURIComponent(currentSearch)}`; } // 添加排序条件 if (currentSortField) { endpoint += `&sort=${currentSortField}&direction=${currentSortDirection}`; } // 使用封装的apiRequest函数进行API调用 apiRequest(endpoint) .then(data => { if (data && data.error) { console.error('加载日志详情失败:', data.error); // 隐藏加载状态 if (loadingEl) { loadingEl.classList.add('hidden'); } return; } // 加载日志总数 return apiRequest('/logs/count').then(countData => { return { logs: data, count: countData.count }; }); }) .then(result => { if (!result || !result.logs) { console.error('加载日志详情失败: 无效的响应数据'); // 隐藏加载状态 if (loadingEl) { loadingEl.classList.add('hidden'); } return; } const logs = result.logs; const totalLogs = result.count; // 计算总页数 totalPages = Math.ceil(totalLogs / logsPerPage); // 更新日志表格 updateLogsTable(logs); // 更新分页信息 updateLogsPagination(); // 隐藏加载状态 if (loadingEl) { loadingEl.classList.add('hidden'); } }) .catch(error => { console.error('加载日志详情失败:', error); // 隐藏加载状态 if (loadingEl) { loadingEl.classList.add('hidden'); } }); } // 更新日志表格 function updateLogsTable(logs) { const tableBody = document.getElementById('logs-table-body'); if (!tableBody) return; // 清空表格 tableBody.innerHTML = ''; if (logs.length === 0) { // 显示空状态 const emptyRow = document.createElement('tr'); emptyRow.innerHTML = `