实现了日志查询功能
This commit is contained in:
@@ -859,6 +859,15 @@
|
|||||||
<option value="error">错误</option>
|
<option value="error">错误</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="w-32">
|
||||||
|
<select id="logs-per-page" class="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
|
||||||
|
<option value="10">10条/页</option>
|
||||||
|
<option value="20">20条/页</option>
|
||||||
|
<option value="30" selected>30条/页</option>
|
||||||
|
<option value="50">50条/页</option>
|
||||||
|
<option value="100">100条/页</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<button id="logs-search-btn" class="px-6 py-3 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors">
|
<button id="logs-search-btn" class="px-6 py-3 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors">
|
||||||
<i class="fa fa-search mr-2"></i>搜索
|
<i class="fa fa-search mr-2"></i>搜索
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -797,17 +797,20 @@ function updateStatsCards(stats) {
|
|||||||
const originalStyle = element.getAttribute('style') || '';
|
const originalStyle = element.getAttribute('style') || '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 复制原始元素的样式到新元素,确保大小完全一致
|
||||||
|
const computedStyle = getComputedStyle(element);
|
||||||
|
|
||||||
// 配置翻页容器样式,确保与原始元素大小完全一致
|
// 配置翻页容器样式,确保与原始元素大小完全一致
|
||||||
const containerStyle =
|
const containerStyle =
|
||||||
'position: relative; '
|
'position: relative; ' +
|
||||||
+ 'display: ' + computedStyle.display + '; '
|
'display: ' + computedStyle.display + '; ' +
|
||||||
+ 'overflow: hidden; '
|
'overflow: hidden; ' +
|
||||||
+ 'height: ' + element.offsetHeight + 'px; '
|
'height: ' + element.offsetHeight + 'px; ' +
|
||||||
+ 'width: ' + element.offsetWidth + 'px; '
|
'width: ' + element.offsetWidth + 'px; ' +
|
||||||
+ 'margin: ' + computedStyle.margin + '; '
|
'margin: ' + computedStyle.margin + '; ' +
|
||||||
+ 'padding: ' + computedStyle.padding + '; '
|
'padding: ' + computedStyle.padding + '; ' +
|
||||||
+ 'box-sizing: ' + computedStyle.boxSizing + '; '
|
'box-sizing: ' + computedStyle.boxSizing + '; ' +
|
||||||
+ 'line-height: ' + computedStyle.lineHeight + ';';
|
'line-height: ' + computedStyle.lineHeight + ';';
|
||||||
|
|
||||||
// 创建翻页容器
|
// 创建翻页容器
|
||||||
const flipContainer = document.createElement('div');
|
const flipContainer = document.createElement('div');
|
||||||
@@ -844,9 +847,6 @@ function updateStatsCards(stats) {
|
|||||||
'transition: transform 400ms ease-in-out; ' +
|
'transition: transform 400ms ease-in-out; ' +
|
||||||
'transform-origin: center; ' +
|
'transform-origin: center; ' +
|
||||||
'transform: translateY(100%);';
|
'transform: translateY(100%);';
|
||||||
|
|
||||||
// 复制原始元素的样式到新元素,确保大小完全一致
|
|
||||||
const computedStyle = getComputedStyle(element);
|
|
||||||
[oldValueElement, newValueElement].forEach(el => {
|
[oldValueElement, newValueElement].forEach(el => {
|
||||||
el.style.fontSize = computedStyle.fontSize;
|
el.style.fontSize = computedStyle.fontSize;
|
||||||
el.style.fontWeight = computedStyle.fontWeight;
|
el.style.fontWeight = computedStyle.fontWeight;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// 全局变量
|
// 全局变量
|
||||||
let currentPage = 1;
|
let currentPage = 1;
|
||||||
let totalPages = 1;
|
let totalPages = 1;
|
||||||
let logsPerPage = 20;
|
let logsPerPage = 30; // 默认显示30条记录
|
||||||
let currentFilter = '';
|
let currentFilter = '';
|
||||||
let currentSearch = '';
|
let currentSearch = '';
|
||||||
let logsChart = null;
|
let logsChart = null;
|
||||||
@@ -23,6 +23,27 @@ function initLogsPage() {
|
|||||||
|
|
||||||
// 绑定事件
|
// 绑定事件
|
||||||
bindLogsEvents();
|
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 prevBtn = document.getElementById('logs-prev-page');
|
||||||
const nextBtn = document.getElementById('logs-next-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(() => {
|
setInterval(() => {
|
||||||
// 只有在查询日志页面时才更新
|
// 只有在查询日志页面时才更新
|
||||||
if (window.location.hash === '#logs') {
|
if (window.location.hash === '#logs') {
|
||||||
loadLogsStats();
|
loadLogsStats();
|
||||||
loadLogs();
|
// 不自动更新日志详情,只更新统计数据
|
||||||
}
|
}
|
||||||
}, 30000); // 每30秒更新一次
|
}, 30000); // 每30秒更新一次
|
||||||
|
|||||||
@@ -22,16 +22,6 @@ function setupNavigation() {
|
|||||||
if (window.innerWidth < 768) {
|
if (window.innerWidth < 768) {
|
||||||
closeSidebar();
|
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() {
|
function init() {
|
||||||
// 设置导航
|
// 设置导航
|
||||||
setupNavigation();
|
setupNavigation();
|
||||||
|
|
||||||
// 加载仪表盘数据
|
// 初始化页面
|
||||||
if (typeof loadDashboardData === 'function') {
|
initPageByHash();
|
||||||
loadDashboardData();
|
|
||||||
}
|
// 添加hashchange事件监听,处理浏览器前进/后退按钮
|
||||||
|
window.addEventListener('hashchange', initPageByHash);
|
||||||
|
|
||||||
// 定期更新系统状态
|
// 定期更新系统状态
|
||||||
setInterval(updateSystemStatus, 5000);
|
setInterval(updateSystemStatus, 5000);
|
||||||
|
|||||||
Reference in New Issue
Block a user