// logs.js - 查询日志页面功能 // 全局变量 let currentPage = 1; let totalPages = 1; let logsPerPage = 30; // 默认显示30条记录 let currentFilter = ''; let currentSearch = ''; let logsChart = null; // 初始化查询日志页面 function initLogsPage() { console.log('初始化查询日志页面'); // 加载日志统计数据 loadLogsStats(); // 加载日志详情 loadLogs(); // 初始化图表 initLogsChart(); // 绑定事件 bindLogsEvents(); // 建立WebSocket连接,用于实时更新统计数据和图表 connectLogsWebSocket(); // 在页面卸载时清理资源 window.addEventListener('beforeunload', cleanupLogsResources); } // 清理资源 function cleanupLogsResources() { // 清除WebSocket连接 if (wsConnection) { wsConnection.close(); wsConnection = null; } // 清除重连计时器 if (wsReconnectTimer) { clearTimeout(wsReconnectTimer); wsReconnectTimer = 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); }); }); } // 加载日志统计数据 function loadLogsStats() { fetch('/api/logs/stats') .then(response => response.json()) .then(data => { // 更新统计卡片 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 url = `/api/logs/query?limit=${logsPerPage}&offset=${(currentPage - 1) * logsPerPage}`; // 添加过滤条件 if (currentFilter) { url += `&result=${currentFilter}`; } // 添加搜索条件 if (currentSearch) { url += `&search=${encodeURIComponent(currentSearch)}`; } fetch(url) .then(response => response.json()) .then(data => { // 加载日志总数 return fetch('/api/logs/count').then(response => response.json()).then(countData => { return { logs: data, count: countData.count }; }); }) .then(result => { 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 = `
暂无查询日志
`; tableBody.appendChild(emptyRow); return; } // 填充表格 logs.forEach(log => { const row = document.createElement('tr'); row.className = 'border-b border-gray-100 hover:bg-gray-50 transition-colors'; // 格式化时间 const time = new Date(log.Timestamp); const formattedTime = time.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); // 结果样式 let resultClass = ''; let resultText = ''; switch (log.Result) { case 'allowed': resultClass = 'text-success'; resultText = '允许'; break; case 'blocked': resultClass = 'text-danger'; resultText = '屏蔽'; break; case 'error': resultClass = 'text-warning'; resultText = '错误'; break; } // 构建行内容 row.innerHTML = ` ${formattedTime} ${log.ClientIP} ${log.Domain} ${log.QueryType} ${resultText} ${log.ResponseTime}ms ${log.BlockRule || '-'} `; tableBody.appendChild(row); }); } // 更新分页信息 function updateLogsPagination() { // 更新页码显示 document.getElementById('logs-current-page').textContent = currentPage; document.getElementById('logs-total-pages').textContent = totalPages; // 更新按钮状态 const prevBtn = document.getElementById('logs-prev-page'); const nextBtn = document.getElementById('logs-next-page'); if (prevBtn) { prevBtn.disabled = currentPage === 1; } if (nextBtn) { nextBtn.disabled = currentPage === totalPages; } } // 初始化日志图表 function initLogsChart() { const ctx = document.getElementById('logs-trend-chart'); if (!ctx) return; // 获取24小时统计数据 fetch('/api/hourly-stats') .then(response => response.json()) .then(data => { // 创建图表 logsChart = new Chart(ctx, { type: 'line', data: { labels: data.labels, datasets: [{ label: '查询数', data: data.data, borderColor: '#3b82f6', backgroundColor: 'rgba(59, 130, 246, 0.1)', tension: 0.4, fill: true }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: true, position: 'top' }, tooltip: { mode: 'index', intersect: false } }, scales: { y: { beginAtZero: true, ticks: { precision: 0 } } } } }); }) .catch(error => { console.error('初始化日志图表失败:', error); }); } // 更新日志图表 function updateLogsChart(range) { if (!logsChart) return; let url = ''; switch (range) { case '24h': url = '/api/hourly-stats'; break; case '7d': url = '/api/daily-stats'; break; case '30d': url = '/api/monthly-stats'; break; default: url = '/api/hourly-stats'; } fetch(url) .then(response => response.json()) .then(data => { // 更新图表数据 logsChart.data.labels = data.labels; logsChart.data.datasets[0].data = data.data; logsChart.update(); }) .catch(error => { console.error('更新日志图表失败:', error); }); } // 建立WebSocket连接 function connectLogsWebSocket() { try { // 构建WebSocket URL const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${wsProtocol}//${window.location.host}/ws/stats`; console.log('正在连接WebSocket:', wsUrl); // 创建WebSocket连接 wsConnection = new WebSocket(wsUrl); // 连接打开事件 wsConnection.onopen = function() { console.log('WebSocket连接已建立'); }; // 接收消息事件 wsConnection.onmessage = function(event) { try { const data = JSON.parse(event.data); if (data.type === 'initial_data' || data.type === 'stats_update') { console.log('收到实时数据更新'); // 只更新统计数据,不更新日志详情 updateLogsStatsFromWebSocket(data.data); } } catch (error) { console.error('处理WebSocket消息失败:', error); } }; // 连接关闭事件 wsConnection.onclose = function(event) { console.warn('WebSocket连接已关闭,代码:', event.code); wsConnection = null; // 设置重连 setupLogsReconnect(); }; // 连接错误事件 wsConnection.onerror = function(error) { console.error('WebSocket连接错误:', error); }; } catch (error) { console.error('创建WebSocket连接失败:', error); } } // 设置重连逻辑 function setupLogsReconnect() { if (wsReconnectTimer) { return; // 已经有重连计时器在运行 } const reconnectDelay = 5000; // 5秒后重连 console.log(`将在${reconnectDelay}ms后尝试重新连接WebSocket`); wsReconnectTimer = setTimeout(() => { connectLogsWebSocket(); }, reconnectDelay); } // 从WebSocket更新日志统计数据 function updateLogsStatsFromWebSocket(stats) { try { // 更新统计卡片 if (stats.dns) { // 适配不同的数据结构 const totalQueries = stats.dns.Queries || 0; const blockedQueries = stats.dns.Blocked || 0; const allowedQueries = stats.dns.Allowed || 0; const errorQueries = stats.dns.Errors || 0; const avgResponseTime = stats.dns.AvgResponseTime || 0; const activeIPs = stats.activeIPs || Object.keys(stats.dns.SourceIPs || {}).length; // 更新统计卡片 document.getElementById('logs-total-queries').textContent = totalQueries; document.getElementById('logs-avg-response-time').textContent = avgResponseTime.toFixed(2) + 'ms'; document.getElementById('logs-active-ips').textContent = activeIPs; // 计算屏蔽率 const blockRate = totalQueries > 0 ? (blockedQueries / totalQueries * 100).toFixed(1) : '0'; document.getElementById('logs-block-rate').textContent = blockRate + '%'; } } catch (error) { console.error('从WebSocket更新日志统计数据失败:', error); } } // 定期更新日志统计数据(备用方案) setInterval(() => { // 只有在查询日志页面时才更新 if (window.location.hash === '#logs') { loadLogsStats(); // 不自动更新日志详情,只更新统计数据 } }, 30000); // 每30秒更新一次