// logs.js - 查询日志页面功能 // 全局变量 let currentPage = 1; let totalPages = 1; let logsPerPage = 20; let currentFilter = ''; let currentSearch = ''; let logsChart = null; // 初始化查询日志页面 function initLogsPage() { console.log('初始化查询日志页面'); // 加载日志统计数据 loadLogsStats(); // 加载日志详情 loadLogs(); // 初始化图表 initLogsChart(); // 绑定事件 bindLogsEvents(); } // 绑定事件 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 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); }); } // 定期更新日志数据 setInterval(() => { // 只有在查询日志页面时才更新 if (window.location.hash === '#logs') { loadLogsStats(); loadLogs(); } }, 30000); // 每30秒更新一次