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);