// logs.js - 查询日志页面功能 // 全局变量 let currentPage = 1; let totalPages = 1; let logsPerPage = 30; // 默认显示30条记录 let currentFilter = ''; let currentSearch = ''; let logsChart = null; let currentSortField = 'timestamp'; // 默认按时间排序,显示最新记录 let currentSortDirection = 'desc'; // 默认降序 // 内存使用监控 let memoryMonitor = { enabled: true, interval: null, history: [], maxHistory: 50, // 开始监控 start() { if (this.enabled && !this.interval) { this.interval = setInterval(() => { this.checkMemoryUsage(); }, 30000); // 每30秒检查一次 } }, // 停止监控 stop() { if (this.interval) { clearInterval(this.interval); this.interval = null; } }, // 检查内存使用情况 checkMemoryUsage() { if (performance && performance.memory) { const memory = performance.memory; const usage = { timestamp: Date.now(), used: Math.round(memory.usedJSHeapSize / 1024 / 1024 * 100) / 100, // MB total: Math.round(memory.totalJSHeapSize / 1024 / 1024 * 100) / 100, // MB limit: Math.round(memory.jsHeapSizeLimit / 1024 / 1024 * 100) / 100, // MB usagePercent: Math.round((memory.usedJSHeapSize / memory.jsHeapSizeLimit) * 100 * 100) / 100 // % }; this.history.push(usage); // 限制历史记录大小 if (this.history.length > this.maxHistory) { this.history.shift(); } // 内存使用过高时的处理 if (usage.usagePercent > 80) { console.warn('内存使用过高:', usage); // 可以在这里添加自动清理机制 this.triggerMemoryCleanup(); } console.log('内存使用情况:', usage); } }, // 触发内存清理 triggerMemoryCleanup() { console.log('触发内存清理...'); // 清理IP地理位置缓存 if (ipGeolocationCache && typeof ipGeolocationCache === 'object') { const cacheSize = Object.keys(ipGeolocationCache).length; console.log('清理前IP地理位置缓存大小:', cacheSize); // 清理超出大小限制的缓存 if (GEOLOCATION_CACHE_ORDER && GEOLOCATION_CACHE_ORDER.length > GEOLOCATION_CACHE_MAX_SIZE) { while (GEOLOCATION_CACHE_ORDER.length > GEOLOCATION_CACHE_MAX_SIZE) { const oldestIp = GEOLOCATION_CACHE_ORDER.shift(); if (oldestIp) { delete ipGeolocationCache[oldestIp]; } } console.log('清理后IP地理位置缓存大小:', Object.keys(ipGeolocationCache).length); } } // 清理域名信息缓存 if (domainInfoCache && domainInfoCache.size > 0) { const cacheSize = domainInfoCache.size; console.log('清理前域名信息缓存大小:', cacheSize); // 清理超出大小限制的缓存 if (domainInfoCache.size > DOMAIN_INFO_CACHE_MAX_SIZE) { while (domainInfoCache.size > DOMAIN_INFO_CACHE_MAX_SIZE) { const firstKey = domainInfoCache.keys().next().value; domainInfoCache.delete(firstKey); } console.log('清理后域名信息缓存大小:', domainInfoCache.size); } } }, // 获取内存使用统计 getStats() { if (this.history.length === 0) { return null; } const recent = this.history[this.history.length - 1]; const avg = this.history.reduce((sum, item) => sum + item.used, 0) / this.history.length; const max = Math.max(...this.history.map(item => item.used)); const min = Math.min(...this.history.map(item => item.used)); return { recent, avg: Math.round(avg * 100) / 100, max: Math.round(max * 100) / 100, min: Math.round(min * 100) / 100, history: this.history }; } }; // IP地理位置缓存(检查是否已经存在,避免重复声明) if (typeof ipGeolocationCache === 'undefined') { var ipGeolocationCache = {}; var GEOLOCATION_CACHE_EXPIRY = 24 * 60 * 60 * 1000; // 缓存有效期24小时 var GEOLOCATION_CACHE_MAX_SIZE = 1000; // 缓存最大大小 var GEOLOCATION_CACHE_ORDER = []; // 用于LRU策略的访问顺序 } // 清理IP地理位置缓存,保持在最大大小以内 function cleanupGeolocationCache() { if (GEOLOCATION_CACHE_ORDER.length > GEOLOCATION_CACHE_MAX_SIZE) { // 移除最旧的缓存项 const oldestIp = GEOLOCATION_CACHE_ORDER.shift(); if (oldestIp) { delete ipGeolocationCache[oldestIp]; } } } // 更新缓存访问顺序(用于LRU策略) function updateCacheAccessOrder(ip) { // 移除现有的位置 const index = GEOLOCATION_CACHE_ORDER.indexOf(ip); if (index > -1) { GEOLOCATION_CACHE_ORDER.splice(index, 1); } // 添加到末尾(表示最近访问) GEOLOCATION_CACHE_ORDER.push(ip); // 清理超出大小限制的缓存 cleanupGeolocationCache(); } // 获取IP地理位置信息 async function getIpGeolocation(ip) { // 检查是否为内网IP if (isPrivateIP(ip)) { return "内网 内网"; } // 检查缓存 const now = Date.now(); if (ipGeolocationCache[ip] && (now - ipGeolocationCache[ip].timestamp) < GEOLOCATION_CACHE_EXPIRY) { // 更新缓存访问顺序 updateCacheAccessOrder(ip); 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 }; // 更新缓存访问顺序 updateCacheAccessOrder(ip); 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; // 域名信息查询缓存 let domainInfoCache = new Map(); let DOMAIN_INFO_CACHE_MAX_SIZE = 500; // 缓存最大大小 // 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() { if (domainInfoLoaded) { return domainInfoDatabase; } if (domainInfoLoading) { // 等待正在进行的加载完成 while (domainInfoLoading) { await new Promise(resolve => setTimeout(resolve, 100)); } return domainInfoDatabase; } domainInfoLoading = true; try { 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; } domainInfoDatabase = await response.json(); domainInfoLoaded = true; return domainInfoDatabase; } catch (error) { console.error('加载域名信息数据库失败,错误信息:', error.message); console.error('错误堆栈:', error.stack); domainInfoDatabase = { domains: {}, categories: {} }; return domainInfoDatabase; } finally { domainInfoLoading = false; } } // 检查域名是否在跟踪器数据库中,并返回跟踪器信息 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) { // 规范化域名,移除可能的端口号 const normalizedDomain = domain.replace(/:\d+$/, '').toLowerCase(); // 检查缓存 if (domainInfoCache.has(normalizedDomain)) { return domainInfoCache.get(normalizedDomain); } if (!domainInfoDatabase || !domainInfoLoaded) { await loadDomainInfoDatabase(); } if (!domainInfoDatabase || !domainInfoDatabase.domains) { console.error('域名信息数据库无效或为空'); return null; } // 遍历所有公司 for (const companyKey in domainInfoDatabase.domains) { if (domainInfoDatabase.domains.hasOwnProperty(companyKey)) { const companyData = domainInfoDatabase.domains[companyKey]; const companyName = companyData.company || companyKey; // 遍历公司下的所有网站和类别 for (const websiteKey in companyData) { if (companyData.hasOwnProperty(websiteKey) && websiteKey !== 'company') { const website = companyData[websiteKey]; // 如果有URL属性,直接检查域名 if (website.url) { // 处理字符串类型的URL if (typeof website.url === 'string') { if (isDomainMatch(website.url, normalizedDomain, website.categoryId)) { const result = { name: website.name, icon: website.icon, categoryId: website.categoryId, categoryName: domainInfoDatabase.categories[website.categoryId] || '未知', company: website.company || companyName }; // 存入缓存 addToDomainInfoCache(normalizedDomain, result); return result; } } // 处理对象类型的URL else if (typeof website.url === 'object') { for (const urlKey in website.url) { if (website.url.hasOwnProperty(urlKey)) { const urlValue = website.url[urlKey]; if (isDomainMatch(urlValue, normalizedDomain, website.categoryId)) { const result = { name: website.name, icon: website.icon, categoryId: website.categoryId, categoryName: domainInfoDatabase.categories[website.categoryId] || '未知', company: website.company || companyName }; // 存入缓存 addToDomainInfoCache(normalizedDomain, result); return result; } } } } } else if (typeof website === 'object' && website !== null) { // 没有URL属性,可能是嵌套的类别 for (const nestedWebsiteKey in website) { if (website.hasOwnProperty(nestedWebsiteKey) && nestedWebsiteKey !== 'company') { const nestedWebsite = website[nestedWebsiteKey]; if (nestedWebsite.url) { // 处理字符串类型的URL if (typeof nestedWebsite.url === 'string') { if (isDomainMatch(nestedWebsite.url, normalizedDomain, nestedWebsite.categoryId)) { const result = { name: nestedWebsite.name, icon: nestedWebsite.icon, categoryId: nestedWebsite.categoryId, categoryName: domainInfoDatabase.categories[nestedWebsite.categoryId] || '未知', company: nestedWebsite.company || companyName }; // 存入缓存 addToDomainInfoCache(normalizedDomain, result); return result; } } // 处理对象类型的URL else if (typeof nestedWebsite.url === 'object') { for (const urlKey in nestedWebsite.url) { if (nestedWebsite.url.hasOwnProperty(urlKey)) { const urlValue = nestedWebsite.url[urlKey]; if (isDomainMatch(urlValue, normalizedDomain, nestedWebsite.categoryId)) { const result = { name: nestedWebsite.name, icon: nestedWebsite.icon, categoryId: nestedWebsite.categoryId, categoryName: domainInfoDatabase.categories[nestedWebsite.categoryId] || '未知', company: nestedWebsite.company || companyName }; // 存入缓存 addToDomainInfoCache(normalizedDomain, result); return result; } } } } } else if (typeof nestedWebsite === 'object' && nestedWebsite !== null) { // 嵌套类别中的嵌套类别,递归检查 for (const secondNestedWebsiteKey in nestedWebsite) { if (nestedWebsite.hasOwnProperty(secondNestedWebsiteKey) && secondNestedWebsiteKey !== 'company') { const secondNestedWebsite = nestedWebsite[secondNestedWebsiteKey]; if (secondNestedWebsite.url) { // 处理字符串类型的URL if (typeof secondNestedWebsite.url === 'string') { if (isDomainMatch(secondNestedWebsite.url, normalizedDomain, secondNestedWebsite.categoryId)) { const result = { name: secondNestedWebsite.name, icon: secondNestedWebsite.icon, categoryId: secondNestedWebsite.categoryId, categoryName: domainInfoDatabase.categories[secondNestedWebsite.categoryId] || '未知', company: secondNestedWebsite.company || companyName }; // 存入缓存 addToDomainInfoCache(normalizedDomain, result); return result; } } // 处理对象类型的URL else if (typeof secondNestedWebsite.url === 'object') { for (const urlKey in secondNestedWebsite.url) { if (secondNestedWebsite.url.hasOwnProperty(urlKey)) { const urlValue = secondNestedWebsite.url[urlKey]; if (isDomainMatch(urlValue, normalizedDomain, secondNestedWebsite.categoryId)) { const result = { name: secondNestedWebsite.name, icon: secondNestedWebsite.icon, categoryId: secondNestedWebsite.categoryId, categoryName: domainInfoDatabase.categories[secondNestedWebsite.categoryId] || '未知', company: secondNestedWebsite.company || companyName }; // 存入缓存 addToDomainInfoCache(normalizedDomain, result); return result; } } } } } } } } } } } } } } } return null; } // 添加到域名信息缓存 function addToDomainInfoCache(domain, info) { // 检查缓存大小 if (domainInfoCache.size >= DOMAIN_INFO_CACHE_MAX_SIZE) { // 移除最早的缓存项 const firstKey = domainInfoCache.keys().next().value; domainInfoCache.delete(firstKey); } // 添加新的缓存项 domainInfoCache.set(domain, info); } // 检查域名是否匹配 function isDomainMatch(urlValue, targetDomain, categoryId) { // 规范化目标域名,去除末尾的点 const normalizedTargetDomain = targetDomain.replace(/\.$/, '').toLowerCase(); try { // 尝试将URL值解析为完整URL const url = new URL(urlValue); let hostname = url.hostname.toLowerCase(); // 规范化主机名,去除末尾的点 hostname = hostname.replace(/\.$/, ''); // 根据类别ID选择匹配方式 if (categoryId === 2) { // CDN类别,使用域名后缀匹配 if (normalizedTargetDomain.endsWith('.' + hostname) || normalizedTargetDomain === hostname) { return true; } else { return false; } } else { // 其他类别,使用完整域名匹配 if (hostname === normalizedTargetDomain) { return true; } else { return false; } } } catch (e) { // 如果是纯域名而不是完整URL let urlDomain = urlValue.toLowerCase(); // 规范化纯域名,去除末尾的点 urlDomain = urlDomain.replace(/\.$/, ''); // 根据类别ID选择匹配方式 if (categoryId === 2) { // CDN类别,使用域名后缀匹配 if (normalizedTargetDomain.endsWith('.' + urlDomain) || normalizedTargetDomain === urlDomain) { return true; } else { return false; } } else { // 其他类别,使用完整域名匹配 if (urlDomain === normalizedTargetDomain) { return true; } else { return false; } } } } // 提取主域名 function extractPrimaryDomain(domain) { const parts = domain.split('.'); if (parts.length <= 2) { return domain; } // 处理常见的三级域名 const commonSubdomains = ['www', 'mail', 'news', 'map', 'image', 'video', 'cdn', 'api', 'blog', 'shop', 'cloud', 'docs', 'help', 'support', 'dev', 'test', 'staging']; if (commonSubdomains.includes(parts[0])) { const result = parts.slice(1).join('.'); 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']; for (const tld of countryTLDs) { if (domain.endsWith('.' + tld)) { const mainParts = domain.split('.'); const result = mainParts.slice(-tld.split('.').length - 1).join('.'); return result; } } // 默认情况:返回最后两个部分 const result = parts.slice(-2).join('.'); 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; // 遍历部分数据行(最多前20行),找到该列的最大宽度 // 这样可以在保证准确性的同时提高性能 const maxRowsToCheck = Math.min(20, rows.length); for (let i = 0; i < maxRowsToCheck; i++) { const row = rows[i]; 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; }); // 恢复表格布局 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 performSearch(searchTerm) { // 获取搜索文本框 const searchInput = document.getElementById('logs-search'); if (searchInput) { // 设置搜索内容 searchInput.value = searchTerm; // 更新currentSearch变量 currentSearch = searchTerm; // 重置页码到第一页 currentPage = 1; // 重新加载日志 loadLogs(); } } // 初始化查询日志页面 function initLogsPage() { // 启动内存监控 memoryMonitor.start(); // 加载日志统计数据 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); // 清除图表实例 if (logsChart) { logsChart.destroy(); logsChart = null; } // 清除表格事件委托 const tableBody = document.getElementById('logs-table-body'); if (tableBody) { tableBody.removeEventListener('click', handleTableClick); } // 清理缓存数据(保留但限制大小) if (ipGeolocationCache && typeof ipGeolocationCache === 'object') { // 清理超出大小限制的缓存 if (GEOLOCATION_CACHE_ORDER && GEOLOCATION_CACHE_ORDER.length > GEOLOCATION_CACHE_MAX_SIZE) { while (GEOLOCATION_CACHE_ORDER.length > GEOLOCATION_CACHE_MAX_SIZE) { const oldestIp = GEOLOCATION_CACHE_ORDER.shift(); if (oldestIp) { delete ipGeolocationCache[oldestIp]; } } } } // 清理跟踪器和域名信息数据库(可选,根据内存使用情况决定) // 注意:如果这些数据在其他地方也使用,不要在这里清理 // if (trackersDatabase) { // trackersDatabase = null; // trackersLoaded = false; // } // if (domainInfoDatabase) { // domainInfoDatabase = null; // domainInfoLoaded = false; // } // 清除模态框和工具提示 const modals = document.querySelectorAll('.fixed.inset-0.bg-black.bg-opacity-50'); modals.forEach(modal => { modal.remove(); }); // 清除事件监听器(如果有其他全局事件监听器) window.removeEventListener('beforeunload', cleanupLogsResources); // 停止内存监控 memoryMonitor.stop(); console.log('Resources cleaned up successfully'); // 输出最终内存使用统计 if (memoryMonitor && memoryMonitor.getStats) { const stats = memoryMonitor.getStats(); if (stats) { console.log('最终内存使用统计:', { avg: stats.avg, max: stats.max, min: stats.min, recent: stats.recent }); } } } // 绑定事件 function bindLogsEvents() { // 搜索按钮 const searchBtn = document.getElementById('logs-search-btn'); const searchInput = document.getElementById('logs-search'); const clearSearchBtn = document.getElementById('logs-clear-search'); // 显示或隐藏清除按钮 function toggleClearSearchBtn() { if (searchInput && clearSearchBtn) { if (searchInput.value.trim() !== '') { clearSearchBtn.classList.remove('hidden'); } else { clearSearchBtn.classList.add('hidden'); } } } if (searchBtn) { searchBtn.addEventListener('click', () => { currentSearch = document.getElementById('logs-search').value.trim(); toggleClearSearchBtn(); currentPage = 1; loadLogs(); }); } // 搜索框事件 if (searchInput) { // 搜索框回车事件 searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { currentSearch = searchInput.value.trim(); toggleClearSearchBtn(); currentPage = 1; loadLogs(); } }); // 搜索框输入事件,用于显示/隐藏清除按钮 searchInput.addEventListener('input', toggleClearSearchBtn); } // 清除搜索按钮事件 if (clearSearchBtn) { clearSearchBtn.addEventListener('click', () => { if (searchInput) { searchInput.value = ''; currentSearch = ''; toggleClearSearchBtn(); 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'); const perPageSelectBottom = document.getElementById('logs-per-page-bottom'); // 同步两个下拉选择框的值 function syncPerPageSelects(value) { if (perPageSelect) { perPageSelect.value = value; } if (perPageSelectBottom) { perPageSelectBottom.value = value; } } // 为顶部下拉选择框添加事件处理 if (perPageSelect) { perPageSelect.addEventListener('change', () => { logsPerPage = parseInt(perPageSelect.value); syncPerPageSelects(logsPerPage); currentPage = 1; loadLogs(); }); } // 为底部下拉选择框添加事件处理 if (perPageSelectBottom) { perPageSelectBottom.addEventListener('change', () => { logsPerPage = parseInt(perPageSelectBottom.value); syncPerPageSelects(logsPerPage); 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(); }); }); // 初始化排序图标 updateSortIcons(); } // 更新排序图标 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 = `