diff --git a/static/index.html b/static/index.html index 4bca6b3..79966b4 100644 --- a/static/index.html +++ b/static/index.html @@ -859,6 +859,15 @@ +
+ +
diff --git a/static/js/dashboard.js b/static/js/dashboard.js index 48efde7..9e977d9 100644 --- a/static/js/dashboard.js +++ b/static/js/dashboard.js @@ -797,17 +797,20 @@ function updateStatsCards(stats) { const originalStyle = element.getAttribute('style') || ''; try { + // 复制原始元素的样式到新元素,确保大小完全一致 + const computedStyle = getComputedStyle(element); + // 配置翻页容器样式,确保与原始元素大小完全一致 const containerStyle = - 'position: relative; ' - + 'display: ' + computedStyle.display + '; ' - + 'overflow: hidden; ' - + 'height: ' + element.offsetHeight + 'px; ' - + 'width: ' + element.offsetWidth + 'px; ' - + 'margin: ' + computedStyle.margin + '; ' - + 'padding: ' + computedStyle.padding + '; ' - + 'box-sizing: ' + computedStyle.boxSizing + '; ' - + 'line-height: ' + computedStyle.lineHeight + ';'; + 'position: relative; ' + + 'display: ' + computedStyle.display + '; ' + + 'overflow: hidden; ' + + 'height: ' + element.offsetHeight + 'px; ' + + 'width: ' + element.offsetWidth + 'px; ' + + 'margin: ' + computedStyle.margin + '; ' + + 'padding: ' + computedStyle.padding + '; ' + + 'box-sizing: ' + computedStyle.boxSizing + '; ' + + 'line-height: ' + computedStyle.lineHeight + ';'; // 创建翻页容器 const flipContainer = document.createElement('div'); @@ -844,9 +847,6 @@ function updateStatsCards(stats) { 'transition: transform 400ms ease-in-out; ' + 'transform-origin: center; ' + 'transform: translateY(100%);'; - - // 复制原始元素的样式到新元素,确保大小完全一致 - const computedStyle = getComputedStyle(element); [oldValueElement, newValueElement].forEach(el => { el.style.fontSize = computedStyle.fontSize; el.style.fontWeight = computedStyle.fontWeight; diff --git a/static/js/logs.js b/static/js/logs.js index 74b5229..f01712d 100644 --- a/static/js/logs.js +++ b/static/js/logs.js @@ -3,7 +3,7 @@ // 全局变量 let currentPage = 1; let totalPages = 1; -let logsPerPage = 20; +let logsPerPage = 30; // 默认显示30条记录 let currentFilter = ''; let currentSearch = ''; let logsChart = null; @@ -23,6 +23,27 @@ function initLogsPage() { // 绑定事件 bindLogsEvents(); + + // 建立WebSocket连接,用于实时更新统计数据和图表 + connectLogsWebSocket(); + + // 在页面卸载时清理资源 + window.addEventListener('beforeunload', cleanupLogsResources); +} + +// 清理资源 +function cleanupLogsResources() { + // 清除WebSocket连接 + if (wsConnection) { + wsConnection.close(); + wsConnection = null; + } + + // 清除重连计时器 + if (wsReconnectTimer) { + clearTimeout(wsReconnectTimer); + wsReconnectTimer = null; + } } // 绑定事件 @@ -59,6 +80,16 @@ function bindLogsEvents() { }); } + // 自定义记录数量 + 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'); @@ -349,11 +380,103 @@ function updateLogsChart(range) { }); } -// 定期更新日志数据 +// 建立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(); - loadLogs(); + // 不自动更新日志详情,只更新统计数据 } }, 30000); // 每30秒更新一次 diff --git a/static/js/main.js b/static/js/main.js index 7f8760f..81fa2b8 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -22,16 +22,6 @@ function setupNavigation() { if (window.innerWidth < 768) { closeSidebar(); } - - // 页面特定初始化 - 保留这部分逻辑,因为它不会与hashchange事件处理逻辑冲突 - const target = item.getAttribute('href').substring(1); - if (target === 'shield' && typeof initShieldPage === 'function') { - initShieldPage(); - } else if (target === 'hosts' && typeof initHostsPage === 'function') { - initHostsPage(); - } else if (target === 'logs' && typeof initLogsPage === 'function') { - initLogsPage(); - } }); }); @@ -119,15 +109,84 @@ function setupNavigation() { }); } +// 页面初始化函数 - 根据当前hash值初始化对应页面 +function initPageByHash() { + const hash = window.location.hash.substring(1); + + // 隐藏所有内容区域 + const contentSections = [ + document.getElementById('dashboard-content'), + document.getElementById('shield-content'), + document.getElementById('hosts-content'), + document.getElementById('query-content'), + document.getElementById('logs-content'), + document.getElementById('config-content') + ]; + + contentSections.forEach(section => { + if (section) { + section.classList.add('hidden'); + } + }); + + // 显示当前页面内容 + const currentSection = document.getElementById(`${hash}-content`); + if (currentSection) { + currentSection.classList.remove('hidden'); + } + + // 更新页面标题 + const pageTitle = document.getElementById('page-title'); + if (pageTitle) { + const titles = { + 'dashboard': '仪表盘', + 'shield': '屏蔽管理', + 'hosts': 'Hosts管理', + 'query': 'DNS屏蔽查询', + 'logs': '查询日志', + 'config': '系统设置' + }; + pageTitle.textContent = titles[hash] || '仪表盘'; + } + + // 页面特定初始化 - 使用setTimeout延迟调用,确保所有脚本文件都已加载完成 + if (hash === 'shield') { + setTimeout(() => { + if (typeof initShieldPage === 'function') { + initShieldPage(); + } + }, 0); + } else if (hash === 'hosts') { + setTimeout(() => { + if (typeof initHostsPage === 'function') { + initHostsPage(); + } + }, 0); + } else if (hash === 'logs') { + setTimeout(() => { + if (typeof initLogsPage === 'function') { + initLogsPage(); + } + }, 0); + } else if (hash === 'dashboard') { + setTimeout(() => { + if (typeof loadDashboardData === 'function') { + loadDashboardData(); + } + }, 0); + } +} + // 初始化函数 function init() { // 设置导航 setupNavigation(); - // 加载仪表盘数据 - if (typeof loadDashboardData === 'function') { - loadDashboardData(); - } + // 初始化页面 + initPageByHash(); + + // 添加hashchange事件监听,处理浏览器前进/后退按钮 + window.addEventListener('hashchange', initPageByHash); // 定期更新系统状态 setInterval(updateSystemStatus, 5000);