// logs.js - 查询日志页面功能 // 全局变量 let currentPage = 1; let totalPages = 1; let logsPerPage = 30; // 默认显示30条记录 let currentFilter = ''; let currentSearch = ''; let logsChart = null; let currentSortField = ''; let currentSortDirection = 'desc'; // 默认降序 // IP地理位置缓存(检查是否已经存在,避免重复声明) if (typeof ipGeolocationCache === 'undefined') { var ipGeolocationCache = {}; var GEOLOCATION_CACHE_EXPIRY = 24 * 60 * 60 * 1000; // 缓存有效期24小时 } // 获取IP地理位置信息 async function getIpGeolocation(ip) { // 检查是否为内网IP if (isPrivateIP(ip)) { return "内网 内网"; } // 检查缓存 const now = Date.now(); if (ipGeolocationCache[ip] && (now - ipGeolocationCache[ip].timestamp) < GEOLOCATION_CACHE_EXPIRY) { return ipGeolocationCache[ip].location; } try { // 使用whois.pconline.com.cn API获取IP地理位置 const url = `https://whois.pconline.com.cn/ipJson.jsp?ip=${ip}&json=true`; const response = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' } }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } // 解析响应数据 const data = await response.json(); let location = "未知 未知"; if (data && data.addr) { // 直接使用addr字段作为完整的地理位置信息 location = data.addr; } // 保存到缓存 ipGeolocationCache[ip] = { location: location, timestamp: now }; return location; } catch (error) { console.error('获取IP地理位置失败:', error); return "未知 未知"; } } // 检查是否为内网IP function isPrivateIP(ip) { const parts = ip.split('.'); // 检查IPv4内网地址 if (parts.length === 4) { const first = parseInt(parts[0]); const second = parseInt(parts[1]); // 10.0.0.0/8 if (first === 10) { return true; } // 172.16.0.0/12 if (first === 172 && second >= 16 && second <= 31) { return true; } // 192.168.0.0/16 if (first === 192 && second === 168) { return true; } // 127.0.0.0/8 (localhost) if (first === 127) { return true; } // 169.254.0.0/16 (link-local) if (first === 169 && second === 254) { return true; } } // 检查IPv6内网地址 if (ip.includes(':')) { // ::1/128 (localhost) if (ip === '::1' || ip.startsWith('0:0:0:0:0:0:0:1')) { return true; } // fc00::/7 (unique local address) if (ip.startsWith('fc') || ip.startsWith('fd')) { return true; } // fe80::/10 (link-local) if (ip.startsWith('fe80:')) { return true; } } return false; } // 跟踪器数据库缓存(检查是否已经存在,避免重复声明) if (typeof trackersDatabase === 'undefined') { var trackersDatabase = null; var trackersLoaded = false; var trackersLoading = false; } // 域名信息数据库缓存 let domainInfoDatabase = null; let domainInfoLoaded = false; let domainInfoLoading = false; // WebSocket连接和重连计时器 let logsWsConnection = null; let logsWsReconnectTimer = null; // 加载跟踪器数据库 async function loadTrackersDatabase() { if (trackersLoaded) return trackersDatabase; if (trackersLoading) { // 等待正在进行的加载完成 while (trackersLoading) { await new Promise(resolve => setTimeout(resolve, 100)); } return trackersDatabase; } trackersLoading = true; try { const response = await fetch('domain-info/tracker/trackers.json'); if (!response.ok) { console.error('加载跟踪器数据库失败:', response.statusText); trackersDatabase = { trackers: {} }; return trackersDatabase; } trackersDatabase = await response.json(); trackersLoaded = true; return trackersDatabase; } catch (error) { console.error('加载跟踪器数据库失败:', error); trackersDatabase = { trackers: {} }; return trackersDatabase; } finally { trackersLoading = false; } } // 加载域名信息数据库 async function loadDomainInfoDatabase() { console.log('开始加载域名信息数据库'); if (domainInfoLoaded) { console.log('域名信息数据库已加载,直接返回'); return domainInfoDatabase; } if (domainInfoLoading) { console.log('域名信息数据库正在加载中,等待完成'); // 等待正在进行的加载完成 while (domainInfoLoading) { await new Promise(resolve => setTimeout(resolve, 100)); } return domainInfoDatabase; } domainInfoLoading = true; try { console.log('发起请求获取域名信息数据库'); const response = await fetch('domain-info/domains/domain-info.json'); if (!response.ok) { console.error('加载域名信息数据库失败,HTTP状态:', response.status, response.statusText); console.error('请求URL:', response.url); domainInfoDatabase = { domains: {}, categories: {} }; return domainInfoDatabase; } console.log('域名信息数据库请求成功,开始解析JSON'); domainInfoDatabase = await response.json(); console.log('域名信息数据库解析成功,包含', Object.keys(domainInfoDatabase.domains || {}).length, '个公司'); domainInfoLoaded = true; return domainInfoDatabase; } catch (error) { console.error('加载域名信息数据库失败,错误信息:', error.message); console.error('错误堆栈:', error.stack); domainInfoDatabase = { domains: {}, categories: {} }; return domainInfoDatabase; } finally { domainInfoLoading = false; console.log('域名信息数据库加载完成'); } } // 检查域名是否在跟踪器数据库中,并返回跟踪器信息 async function isDomainInTrackerDatabase(domain) { if (!trackersDatabase || !trackersLoaded) { await loadTrackersDatabase(); } if (!trackersDatabase || !trackersDatabase.trackers) { return null; } // 检查域名是否直接作为跟踪器键存在 if (trackersDatabase.trackers.hasOwnProperty(domain)) { return trackersDatabase.trackers[domain]; } // 检查域名是否在跟踪器URL中 for (const trackerKey in trackersDatabase.trackers) { if (trackersDatabase.trackers.hasOwnProperty(trackerKey)) { const tracker = trackersDatabase.trackers[trackerKey]; if (tracker && tracker.url) { try { const trackerUrl = new URL(tracker.url); if (trackerUrl.hostname === domain) { return tracker; } } catch (e) { // 忽略无效URL } } } } return null; } // 根据域名查找对应的网站信息 async function getDomainInfo(domain) { console.log('开始查找域名信息,域名:', domain); if (!domainInfoDatabase || !domainInfoLoaded) { console.log('域名信息数据库未加载,调用loadDomainInfoDatabase'); await loadDomainInfoDatabase(); } if (!domainInfoDatabase || !domainInfoDatabase.domains) { console.error('域名信息数据库无效或为空'); return null; } // 规范化域名,移除可能的端口号 const normalizedDomain = domain.replace(/:\d+$/, '').toLowerCase(); console.log('规范化后的域名:', normalizedDomain); // 遍历所有公司 console.log('开始遍历公司,总公司数:', Object.keys(domainInfoDatabase.domains).length); for (const companyKey in domainInfoDatabase.domains) { if (domainInfoDatabase.domains.hasOwnProperty(companyKey)) { console.log('检查公司:', companyKey); const companyData = domainInfoDatabase.domains[companyKey]; const companyName = companyData.company || companyKey; // 遍历公司下的所有网站和类别 for (const websiteKey in companyData) { if (companyData.hasOwnProperty(websiteKey) && websiteKey !== 'company') { console.log(' 检查网站/类别:', websiteKey); const website = companyData[websiteKey]; // 如果有URL属性,直接检查域名 if (website.url) { // 处理字符串类型的URL if (typeof website.url === 'string') { console.log(' 检查字符串URL:', website.url); if (isDomainMatch(website.url, normalizedDomain, website.categoryId)) { console.log(' 匹配成功,返回网站信息'); return { name: website.name, icon: website.icon, categoryId: website.categoryId, categoryName: domainInfoDatabase.categories[website.categoryId] || '未知', company: website.company || companyName }; } } // 处理对象类型的URL else if (typeof website.url === 'object') { console.log(' 检查对象类型URL,包含', Object.keys(website.url).length, '个URL'); for (const urlKey in website.url) { if (website.url.hasOwnProperty(urlKey)) { const urlValue = website.url[urlKey]; console.log(' 检查URL', urlKey, ':', urlValue); if (isDomainMatch(urlValue, normalizedDomain, website.categoryId)) { console.log(' 匹配成功,返回网站信息'); return { name: website.name, icon: website.icon, categoryId: website.categoryId, categoryName: domainInfoDatabase.categories[website.categoryId] || '未知', company: website.company || companyName }; } } } } } else if (typeof website === 'object' && website !== null) { // 没有URL属性,可能是嵌套的类别 console.log(' 发现嵌套类别,进一步检查'); for (const nestedWebsiteKey in website) { if (website.hasOwnProperty(nestedWebsiteKey) && nestedWebsiteKey !== 'company') { console.log(' 检查嵌套网站/类别:', nestedWebsiteKey); const nestedWebsite = website[nestedWebsiteKey]; if (nestedWebsite.url) { // 处理字符串类型的URL if (typeof nestedWebsite.url === 'string') { console.log(' 检查字符串URL:', nestedWebsite.url); if (isDomainMatch(nestedWebsite.url, normalizedDomain, nestedWebsite.categoryId)) { console.log(' 匹配成功,返回网站信息'); return { name: nestedWebsite.name, icon: nestedWebsite.icon, categoryId: nestedWebsite.categoryId, categoryName: domainInfoDatabase.categories[nestedWebsite.categoryId] || '未知', company: nestedWebsite.company || companyName }; } } // 处理对象类型的URL else if (typeof nestedWebsite.url === 'object') { console.log(' 检查对象类型URL,包含', Object.keys(nestedWebsite.url).length, '个URL'); for (const urlKey in nestedWebsite.url) { if (nestedWebsite.url.hasOwnProperty(urlKey)) { const urlValue = nestedWebsite.url[urlKey]; console.log(' 检查URL', urlKey, ':', urlValue); if (isDomainMatch(urlValue, normalizedDomain, nestedWebsite.categoryId)) { console.log(' 匹配成功,返回网站信息'); return { name: nestedWebsite.name, icon: nestedWebsite.icon, categoryId: nestedWebsite.categoryId, categoryName: domainInfoDatabase.categories[nestedWebsite.categoryId] || '未知', company: nestedWebsite.company || companyName }; } } } } } else if (typeof nestedWebsite === 'object' && nestedWebsite !== null) { // 嵌套类别中的嵌套类别,递归检查 console.log(' 发现二级嵌套类别,进一步检查'); for (const secondNestedWebsiteKey in nestedWebsite) { if (nestedWebsite.hasOwnProperty(secondNestedWebsiteKey) && secondNestedWebsiteKey !== 'company') { console.log(' 检查二级嵌套网站:', secondNestedWebsiteKey); const secondNestedWebsite = nestedWebsite[secondNestedWebsiteKey]; if (secondNestedWebsite.url) { // 处理字符串类型的URL if (typeof secondNestedWebsite.url === 'string') { console.log(' 检查字符串URL:', secondNestedWebsite.url); if (isDomainMatch(secondNestedWebsite.url, normalizedDomain, secondNestedWebsite.categoryId)) { console.log(' 匹配成功,返回网站信息'); return { name: secondNestedWebsite.name, icon: secondNestedWebsite.icon, categoryId: secondNestedWebsite.categoryId, categoryName: domainInfoDatabase.categories[secondNestedWebsite.categoryId] || '未知', company: secondNestedWebsite.company || companyName }; } } // 处理对象类型的URL else if (typeof secondNestedWebsite.url === 'object') { console.log(' 检查对象类型URL,包含', Object.keys(secondNestedWebsite.url).length, '个URL'); for (const urlKey in secondNestedWebsite.url) { if (secondNestedWebsite.url.hasOwnProperty(urlKey)) { const urlValue = secondNestedWebsite.url[urlKey]; console.log(' 检查URL', urlKey, ':', urlValue); if (isDomainMatch(urlValue, normalizedDomain, secondNestedWebsite.categoryId)) { console.log(' 匹配成功,返回网站信息'); return { name: secondNestedWebsite.name, icon: secondNestedWebsite.icon, categoryId: secondNestedWebsite.categoryId, categoryName: domainInfoDatabase.categories[secondNestedWebsite.categoryId] || '未知', company: secondNestedWebsite.company || companyName }; } } } } } } } } else { console.log(' 嵌套网站没有URL属性且不是对象类型'); } } } } else { console.log(' 网站没有URL属性'); } } } } } console.log('未找到匹配的域名信息'); return null; } // 检查域名是否匹配 function isDomainMatch(urlValue, targetDomain, categoryId) { console.log(' 开始匹配URL:', urlValue, '目标域名:', targetDomain, '类别ID:', categoryId); // 规范化目标域名,去除末尾的点 const normalizedTargetDomain = targetDomain.replace(/\.$/, '').toLowerCase(); try { // 尝试将URL值解析为完整URL console.log(' 尝试解析URL为完整URL'); const url = new URL(urlValue); let hostname = url.hostname.toLowerCase(); // 规范化主机名,去除末尾的点 hostname = hostname.replace(/\.$/, ''); console.log(' 解析成功,主机名:', hostname, '规范化目标域名:', normalizedTargetDomain); // 根据类别ID选择匹配方式 if (categoryId === 2) { // CDN类别,使用域名后缀匹配 if (normalizedTargetDomain.endsWith('.' + hostname) || normalizedTargetDomain === hostname) { console.log(' CDN域名后缀匹配成功'); return true; } else { console.log(' CDN域名后缀不匹配'); return false; } } else { // 其他类别,使用完整域名匹配 if (hostname === normalizedTargetDomain) { console.log(' 完整域名匹配成功'); return true; } else { console.log(' 完整域名不匹配'); return false; } } } catch (e) { console.log(' 解析URL失败,将其视为纯域名处理,错误信息:', e.message); // 如果是纯域名而不是完整URL let urlDomain = urlValue.toLowerCase(); // 规范化纯域名,去除末尾的点 urlDomain = urlDomain.replace(/\.$/, ''); console.log(' 处理为纯域名:', urlDomain, '规范化目标域名:', normalizedTargetDomain); // 根据类别ID选择匹配方式 if (categoryId === 2) { // CDN类别,使用域名后缀匹配 if (normalizedTargetDomain.endsWith('.' + urlDomain) || normalizedTargetDomain === urlDomain) { console.log(' CDN域名后缀匹配成功'); return true; } else { console.log(' CDN域名后缀不匹配'); return false; } } else { // 其他类别,使用完整域名匹配 if (urlDomain === normalizedTargetDomain) { console.log(' 完整域名匹配成功'); return true; } else { console.log(' 完整域名不匹配'); return false; } } } } // 提取主域名 function extractPrimaryDomain(domain) { console.log(' 开始提取主域名,原始域名:', domain); const parts = domain.split('.'); console.log(' 域名分割为:', parts); if (parts.length <= 2) { console.log(' 域名长度小于等于2,直接返回:', domain); return domain; } // 处理常见的三级域名 const commonSubdomains = ['www', 'mail', 'news', 'map', 'image', 'video', 'cdn', 'api', 'blog', 'shop', 'cloud', 'docs', 'help', 'support', 'dev', 'test', 'staging']; console.log(' 检查是否为常见三级域名'); if (commonSubdomains.includes(parts[0])) { const result = parts.slice(1).join('.'); console.log(' 是常见三级域名,返回:', result); return result; } // 处理特殊情况,如co.uk, co.jp等 const countryTLDs = ['co.uk', 'co.jp', 'co.kr', 'co.in', 'co.ca', 'co.au', 'co.nz', 'co.th', 'co.sg', 'co.my', 'co.id', 'co.za', 'com.cn', 'org.cn', 'net.cn', 'gov.cn', 'edu.cn']; console.log(' 检查是否为特殊国家TLD'); for (const tld of countryTLDs) { if (domain.endsWith('.' + tld)) { const mainParts = domain.split('.'); const result = mainParts.slice(-tld.split('.').length - 1).join('.'); console.log(' 是特殊国家TLD,返回:', result); return result; } } // 默认情况:返回最后两个部分 const result = parts.slice(-2).join('.'); console.log(' 默认情况,返回最后两个部分:', result); return result; } // 初始化列宽调节功能 function initResizableColumns() { const table = document.querySelector('.resizable-table'); if (!table) return; // 为每个表头添加调整手柄元素 function addResizeHandles() { const headers = table.querySelectorAll('th'); headers.forEach(header => { // 移除已存在的手柄 const existingHandle = header.querySelector('.resize-handle'); if (existingHandle) { existingHandle.remove(); } // 创建新的调整手柄 const resizeHandle = document.createElement('div'); resizeHandle.className = 'resize-handle'; resizeHandle.style.cssText = ` position: absolute; top: 0; right: 0; width: 10px; height: 100%; cursor: col-resize; background: rgba(59, 130, 246, 0.1); z-index: 10; transition: background-color 0.2s ease; `; // 添加悬停效果 resizeHandle.addEventListener('mouseenter', () => { resizeHandle.style.background = 'rgba(59, 130, 246, 0.3)'; }); resizeHandle.addEventListener('mouseleave', () => { if (!resizeHandle.classList.contains('dragging')) { resizeHandle.style.background = 'rgba(59, 130, 246, 0.1)'; } }); header.style.position = 'relative'; header.appendChild(resizeHandle); }); } // 计算列宽并设置固定宽度 function calculateAndSetColumnWidths() { // 确保表格可见 table.style.visibility = 'visible'; // 保存当前表格布局 const originalLayout = table.style.tableLayout; table.style.tableLayout = 'auto'; // 获取所有表头和数据行 const headers = table.querySelectorAll('th'); const rows = table.querySelectorAll('tbody tr'); // 计算每列的最大宽度 const columnWidths = []; headers.forEach((header, index) => { // 获取表头宽度 let maxWidth = header.offsetWidth; // 遍历所有数据行,找到该列的最大宽度 rows.forEach(row => { const cell = row.children[index]; if (cell) { maxWidth = Math.max(maxWidth, cell.offsetWidth); } }); // 添加一些 padding maxWidth += 20; // 保存最大宽度 columnWidths[index] = maxWidth; }); // 设置每列的固定宽度 headers.forEach((header, index) => { const width = `${columnWidths[index]}px`; header.style.width = width; header.style.minWidth = width; header.style.maxWidth = width; // 找到对应的数据列并设置宽度 rows.forEach(row => { const cell = row.children[index]; if (cell) { cell.style.width = width; cell.style.minWidth = width; cell.style.maxWidth = width; } }); }); // 恢复表格布局 table.style.tableLayout = 'fixed'; } // 保存列宽设置的函数 function saveColumnWidths() { const headers = table.querySelectorAll('th'); const columnWidths = {}; headers.forEach((header, index) => { columnWidths[index] = header.style.width; }); localStorage.setItem('logsTableColumnWidths', JSON.stringify(columnWidths)); } // 恢复列宽设置的函数 function restoreColumnWidths() { const headers = table.querySelectorAll('th'); const savedWidths = localStorage.getItem('logsTableColumnWidths'); if (savedWidths) { const columnWidths = JSON.parse(savedWidths); // 设置表格布局为fixed table.style.tableLayout = 'fixed'; headers.forEach((header, index) => { if (columnWidths[index]) { const width = columnWidths[index]; header.style.width = width; header.style.minWidth = width; header.style.maxWidth = width; // 找到对应的数据列并设置宽度 const rows = table.querySelectorAll('tbody tr'); rows.forEach(row => { const cell = row.children[index]; if (cell) { cell.style.width = width; cell.style.minWidth = width; cell.style.maxWidth = width; } }); } }); } else { // 没有保存的宽度,计算并设置列宽 calculateAndSetColumnWidths(); } } // 恢复保存的列宽设置或计算初始列宽 restoreColumnWidths(); // 添加调整手柄 addResizeHandles(); // 拖拽状态变量 let currentHeader = null; let startX = 0; let startWidth = 0; let isDragging = false; // 鼠标按下事件 table.addEventListener('mousedown', (e) => { const resizeHandle = e.target.closest('.resize-handle'); if (resizeHandle) { currentHeader = resizeHandle.parentElement; startX = e.clientX; startWidth = currentHeader.offsetWidth; isDragging = true; // 添加拖拽状态类 currentHeader.classList.add('dragging'); resizeHandle.classList.add('dragging'); // 改变拖拽手柄样式 resizeHandle.style.background = 'rgba(59, 130, 246, 0.6)'; // 阻止默认事件和冒泡 e.preventDefault(); e.stopPropagation(); // 阻止文本选择 document.addEventListener('selectstart', preventSelect, { capture: true }); document.addEventListener('copy', preventCopy, { capture: true }); // 添加全局事件监听器 document.addEventListener('mousemove', onMouseMove, { capture: true }); document.addEventListener('mouseup', onMouseUp, { capture: true }); } }); // 鼠标移动事件处理函数 function onMouseMove(e) { if (!currentHeader) return; // 阻止默认事件 e.preventDefault(); e.stopPropagation(); // 计算新宽度 const deltaX = e.clientX - startX; const newWidth = Math.max(50, startWidth + deltaX); // 设置新宽度 const width = `${newWidth}px`; currentHeader.style.width = width; currentHeader.style.minWidth = width; currentHeader.style.maxWidth = width; // 找到对应的数据列并设置宽度 const headers = table.querySelectorAll('th'); const index = Array.from(headers).indexOf(currentHeader); const rows = table.querySelectorAll('tbody tr'); rows.forEach(row => { const cell = row.children[index]; if (cell) { cell.style.width = width; cell.style.minWidth = width; cell.style.maxWidth = width; } }); } // 鼠标释放事件处理函数 function onMouseUp(e) { if (!currentHeader) return; // 阻止默认事件 e.preventDefault(); e.stopPropagation(); // 获取调整手柄 const resizeHandle = currentHeader.querySelector('.resize-handle'); // 移除拖拽状态类 currentHeader.classList.remove('dragging'); resizeHandle.classList.remove('dragging'); // 恢复拖拽手柄样式 resizeHandle.style.background = 'rgba(59, 130, 246, 0.1)'; // 保存列宽设置 saveColumnWidths(); // 重置状态 currentHeader = null; isDragging = false; // 移除事件监听器 document.removeEventListener('selectstart', preventSelect, { capture: true }); document.removeEventListener('copy', preventCopy, { capture: true }); document.removeEventListener('mousemove', onMouseMove, { capture: true }); document.removeEventListener('mouseup', onMouseUp, { capture: true }); } // 阻止文本选择和复制 function preventSelect(e) { e.preventDefault(); } function preventCopy(e) { e.preventDefault(); } } // 初始化查询日志页面 function initLogsPage() { console.log('初始化查询日志页面'); // 加载日志统计数据 loadLogsStats(); // 加载日志详情 loadLogs(); // 初始化图表 initLogsChart(); // 绑定事件 bindLogsEvents(); // 初始化日志详情弹窗 initLogDetailModal(); // 建立WebSocket连接,用于实时更新统计数据和图表 connectLogsWebSocket(); // 初始化列宽调节功能 initResizableColumns(); // 窗口大小改变时重新加载日志表格 window.addEventListener('resize', handleWindowResize); // 在页面卸载时清理资源 window.addEventListener('beforeunload', cleanupLogsResources); } // 处理窗口大小改变 function handleWindowResize() { // 重新加载日志表格,以适应新的屏幕尺寸 loadLogs(); } // 清理资源 function cleanupLogsResources() { // 清除WebSocket连接 if (logsWsConnection) { logsWsConnection.close(); logsWsConnection = null; } // 清除重连计时器 if (logsWsReconnectTimer) { clearTimeout(logsWsReconnectTimer); logsWsReconnectTimer = null; } // 清除窗口大小改变事件监听器 window.removeEventListener('resize', handleWindowResize); } // 绑定事件 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 pageInput = document.getElementById('logs-page-input'); const goBtn = document.getElementById('logs-go-page'); if (pageInput) { // 页码输入框回车事件 pageInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { const page = parseInt(pageInput.value); if (page >= 1 && page <= totalPages) { currentPage = page; loadLogs(); } } }); } if (goBtn) { // 前往按钮点击事件 goBtn.addEventListener('click', () => { const page = parseInt(pageInput.value); if (page >= 1 && page <= totalPages) { currentPage = page; 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); }); }); // 刷新按钮事件 const refreshBtn = document.getElementById('logs-refresh-btn'); if (refreshBtn) { refreshBtn.addEventListener('click', () => { // 重新加载日志 currentPage = 1; loadLogs(); }); } // 排序按钮事件 const sortHeaders = document.querySelectorAll('th[data-sort]'); sortHeaders.forEach(header => { header.addEventListener('click', () => { const sortField = header.getAttribute('data-sort'); // 如果点击的是当前排序字段,则切换排序方向 if (sortField === currentSortField) { currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc'; } else { // 否则,设置新的排序字段,默认降序 currentSortField = sortField; currentSortDirection = 'desc'; } // 更新排序图标 updateSortIcons(); // 重新加载日志 currentPage = 1; loadLogs(); }); }); } // 更新排序图标 function updateSortIcons() { const sortHeaders = document.querySelectorAll('th[data-sort]'); sortHeaders.forEach(header => { const sortField = header.getAttribute('data-sort'); const icon = header.querySelector('i'); // 重置所有图标 icon.className = 'fa fa-sort ml-1 text-xs'; // 设置当前排序字段的图标 if (sortField === currentSortField) { if (currentSortDirection === 'asc') { icon.className = 'fa fa-sort-asc ml-1 text-xs'; } else { icon.className = 'fa fa-sort-desc ml-1 text-xs'; } } }); } // 加载日志统计数据 function loadLogsStats() { // 使用封装的apiRequest函数进行API调用 apiRequest('/logs/stats') .then(data => { if (data && data.error) { console.error('加载日志统计数据失败:', data.error); return; } // 更新统计卡片 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); }); } // 加载日志详情 async function loadLogs() { // 显示加载状态 const loadingEl = document.getElementById('logs-loading'); if (loadingEl) { loadingEl.classList.remove('hidden'); } // 构建请求URL let endpoint = `/logs/query?limit=${logsPerPage}&offset=${(currentPage - 1) * logsPerPage}`; // 添加过滤条件 if (currentFilter) { endpoint += `&result=${currentFilter}`; } // 添加搜索条件 if (currentSearch) { endpoint += `&search=${encodeURIComponent(currentSearch)}`; } // 添加排序条件 if (currentSortField) { endpoint += `&sort=${currentSortField}&direction=${currentSortDirection}`; } try { // 使用封装的apiRequest函数进行API调用 const logsData = await apiRequest(endpoint); if (logsData && logsData.error) { console.error('加载日志详情失败:', logsData.error); // 隐藏加载状态 if (loadingEl) { loadingEl.classList.add('hidden'); } return; } // 加载日志总数 const [logs, countData] = await Promise.all([ Promise.resolve(logsData || []), // 确保logsData是数组 apiRequest('/logs/count') ]); // 确保logs是数组 const logsArray = Array.isArray(logs) ? logs : []; // 确保countData是有效的 const totalLogs = countData && countData.count ? countData.count : logsArray.length; // 计算总页数 totalPages = Math.ceil(totalLogs / logsPerPage); // 更新日志表格 await updateLogsTable(logsArray); // 绑定操作按钮事件 bindActionButtonsEvents(); // 更新分页信息 updateLogsPagination(); // 重新初始化列宽调节功能,确保新添加的行也能继承列宽设置 initResizableColumns(); // 隐藏加载状态 if (loadingEl) { loadingEl.classList.add('hidden'); } } catch (error) { console.error('加载日志详情失败:', error); // 隐藏加载状态 if (loadingEl) { loadingEl.classList.add('hidden'); } // 显示空状态 const tableBody = document.getElementById('logs-table-body'); if (tableBody) { tableBody.innerHTML = `