// threats.js - 威胁告警页面功能实现 // ==================== 工具函数 ==================== // 带超时的 fetch async function fetchWithTimeout(url, options = {}, timeout = 10000) { const controller = new AbortController(); const id = setTimeout(() => controller.abort(), timeout); try { const response = await fetch(url, { ...options, signal: controller.signal }); clearTimeout(id); return response; } catch (error) { clearTimeout(id); throw error; } } // 带重试的 fetch async function fetchWithRetry(url, options = {}, maxRetries = 3) { let lastError; for (let i = 0; i < maxRetries; i++) { try { const response = await fetchWithTimeout(url, options, 10000); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { lastError = error; console.warn(`请求失败 (${i + 1}/${maxRetries}):`, url, error.message); // 等待后重试(指数退避) if (i < maxRetries - 1) { const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s await new Promise(resolve => setTimeout(resolve, delay)); } } } throw lastError; } // 并发控制执行 async function runWithConcurrency(tasks, concurrencyLimit = 5) { const results = []; const executing = []; for (const task of tasks) { const p = task().then(result => { executing.splice(executing.indexOf(p), 1); return result; }); results.push(p); executing.push(p); if (executing.length >= concurrencyLimit) { await Promise.race(executing); } } return Promise.all(results); } // ==================== 缓存管理 ==================== // 威胁查询缓存(批量查询结果缓存) const threatCache = { cache: new Map(), ttl: 10 * 60 * 1000, // 10 分钟 lastQueryTime: 0, queryInterval: 30 * 1000, // 30 秒内不重复查询 // 检查是否可以查询(避免频繁查询) canQuery() { return Date.now() - this.lastQueryTime >= this.queryInterval; }, // 获取缓存的查询结果 getCachedResult() { if (this.cache.size === 0) return null; // 检查是否过期 if (Date.now() - this.lastQueryTime > this.ttl) { console.log('缓存已过期,清除缓存'); this.cache.clear(); return null; } // 转换为数组返回 const results = []; this.cache.forEach((value, domain) => { results.push({ domain, isThreat: value.isThreat, data: value.data }); }); console.log('使用缓存结果,记录数:', results.length); return results; }, // 缓存批量查询结果 cacheResults(results) { this.cache.clear(); results.forEach(result => { if (result.isThreat && result.data) { this.cache.set(result.domain, { isThreat: true, data: result.data, timestamp: Date.now() }); } else { this.cache.set(result.domain, { isThreat: false, timestamp: Date.now() }); } }); this.lastQueryTime = Date.now(); console.log('缓存批量查询结果,记录数:', this.cache.size); // 持久化到 LocalStorage this.saveToLocalStorage(); }, // 保存到 LocalStorage saveToLocalStorage() { try { const serializable = { cache: Array.from(this.cache.entries()), lastQueryTime: this.lastQueryTime }; localStorage.setItem('threatCache', JSON.stringify(serializable)); } catch (e) { console.warn('LocalStorage 保存失败:', e); } }, // 从 LocalStorage 加载 loadFromLocalStorage() { try { const data = localStorage.getItem('threatCache'); if (data) { const parsed = JSON.parse(data); // 检查是否过期(最多 1 小时) if (Date.now() - parsed.lastQueryTime < 60 * 60 * 1000) { parsed.cache.forEach(([key, value]) => { this.cache.set(key, value); }); this.lastQueryTime = parsed.lastQueryTime; console.log('从 LocalStorage 加载缓存,记录数:', this.cache.size); return true; } else { console.log('LocalStorage 缓存已过期'); } } } catch (e) { console.warn('LocalStorage 加载失败:', e); } return false; }, // 清除缓存 clear() { this.cache.clear(); this.lastQueryTime = 0; localStorage.removeItem('threatCache'); } }; // 页面加载时从 LocalStorage 恢复缓存 threatCache.loadFromLocalStorage(); // ==================== 全局变量 ==================== let threatsDatabase = []; let matchedThreats = []; let isDatabaseLoaded = false; // 分页相关变量 const paginationState = { currentPage: 1, pageSize: 30, totalItems: 0, totalPages: 0 }; // 加载威胁数据库 async function loadThreatsDatabase() { if (isDatabaseLoaded && threatsDatabase.length > 0) { console.log('使用缓存的威胁数据库:', threatsDatabase.length, '条记录'); return threatsDatabase; } try { console.log('尝试加载威胁数据库...'); // 使用新的API端点获取威胁数据库 const response = await fetch('/api/domain-info?threats'); console.log('威胁数据库API请求状态:', response.status); if (!response.ok) { throw new Error(`无法加载威胁数据库,状态: ${response.status}`); } const data = await response.json(); console.log('威胁数据库API返回数据:', data); if (!Array.isArray(data)) { throw new Error('威胁数据库API返回的数据格式错误,不是数组'); } // 解析API返回的数据 threatsDatabase = data.map(item => ({ type: item.type || '', name: item.name || '', riskLevel: item.level || '', domain: item.domain || '' })).filter(item => item.type && item.name && item.riskLevel && item.domain); isDatabaseLoaded = true; console.log('威胁数据库加载成功:', threatsDatabase.length, '条记录'); console.log('威胁数据库前5条记录:', threatsDatabase.slice(0, 5)); return threatsDatabase; } catch (error) { console.error('加载威胁数据库失败:', error); console.error('错误堆栈:', error.stack); // 即使加载失败,也返回一个包含示例数据的数组,确保页面能显示内容 console.log('加载失败,使用示例威胁数据...'); return [ { type: '钓鱼网站', name: 'Silver fox 团伙', riskLevel: '2', domain: 'kefubahaohonsheng.oss-cn-hongkong.aliyuncs.com' }, { type: '钓鱼网站', name: 'Silver fox 团伙', riskLevel: '2', domain: 'baiduwenshen.oss-cn-hongkong.aliyuncs.com' }, { type: '木马', name: 'Zegost 后门软件', riskLevel: '1', domain: 'sgkong2.top' }, { type: '木马', name: 'Generic 常规木马', riskLevel: '1', domain: 'todesk-1316713808.cos.ap-nanjing.myqcloud.com' } ]; } } // 加载DNS查询日志 async function loadDNSLogs() { try { // 使用 /api/logs/query 接口获取数据,返回DNS查询日志列表 const response = await fetch('/api/logs/query'); // 处理响应 if (!response.ok) { // 如果是未授权错误,记录错误并返回模拟数据 if (response.status === 401) { console.warn('API返回未授权错误,使用模拟数据'); return getMockDNSLogs(); } throw new Error(`HTTP error! status: ${response.status}`); } const logs = await response.json(); console.log('DNS查询日志加载成功:', logs.length, '条记录'); console.log('API返回的前3条数据:', logs.slice(0, 3)); // 确保返回的数据格式正确 if (!Array.isArray(logs)) { console.error('API返回的数据格式错误,不是数组:', logs); // 返回模拟数据作为备用 return getMockDNSLogs(); } // 过滤并处理日志数据,确保每个条目都有 domain 字段 const processedLogs = logs.filter(log => { if (log && log.domain) { return true; } console.warn('过滤掉没有 domain 字段的日志条目:', log); return false; }); console.log('处理后有 domain 字段的日志数量:', processedLogs.length); // 如果处理后没有日志,返回模拟数据 if (processedLogs.length === 0) { console.warn('没有找到有 domain 字段的日志,返回模拟数据'); return getMockDNSLogs(); } return processedLogs; } catch (error) { console.error('加载DNS查询日志失败:', error); // 返回模拟数据作为备用,包含威胁数据库中的域名 return getMockDNSLogs(); } } // 获取模拟DNS查询日志 function getMockDNSLogs() { return [ { timestamp: '2023-10-01 14:32:15', domain: 'kefubahaohonsheng.oss-cn-hongkong.aliyuncs.com', clientIP: '192.168.1.100', result: 'allowed' }, { timestamp: '2023-10-01 14:28:45', domain: 'baiduwenshen.oss-cn-hongkong.aliyuncs.com', clientIP: '192.168.1.101', result: 'blocked' }, { timestamp: '2023-10-01 14:25:30', domain: 'sgkong2.top', clientIP: '192.168.1.102', result: 'blocked' }, { timestamp: '2023-10-01 14:20:15', domain: 'todesk-1316713808.cos.ap-nanjing.myqcloud.com', clientIP: '192.168.1.103', result: 'blocked' }, { timestamp: '2023-10-01 14:15:00', domain: 'shimo-oss1.oss-cn-hangzhou.aliyuncs.com', clientIP: '192.168.1.104', result: 'allowed' }, { timestamp: '2023-10-01 14:10:45', domain: 'example.com', clientIP: '192.168.1.105', result: 'allowed' }, { timestamp: '2023-10-01 14:05:30', domain: '11bucketyun.oss-cn-hongkong.aliyuncs.com', clientIP: '192.168.1.106', result: 'allowed' }, { timestamp: '2023-10-01 14:00:15', domain: 'supervt.oss-cn-hongkong.aliyuncs.com', clientIP: '192.168.1.107', result: 'blocked' } ]; } // 匹配威胁数据库与 DNS 查询日志 async function matchThreatsWithLogs() { try { // 加载 DNS 查询日志 const logs = await loadDNSLogs(); console.log('DNS 查询日志加载完成,记录数:', logs.length); // 提取所有唯一的域名 const uniqueDomains = [...new Set(logs.filter(log => log && log.domain).map(log => log.domain))]; console.log('唯一域名数量:', uniqueDomains.length); if (uniqueDomains.length === 0) { console.log('没有有效的域名,返回空结果'); return []; } // 检查缓存 const cachedResults = threatCache.getCachedResult(); if (cachedResults) { console.log('使用缓存的威胁数据'); // 使用缓存数据匹配 return matchThreatsFromCache(logs, cachedResults); } console.log('开始批量查询威胁数据库...'); // 批量查询威胁 const response = await fetchWithRetry('/api/threat/batch', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ domains: uniqueDomains }) }); const { results } = response; console.log('批量查询完成,结果数:', results.length); // 缓存结果 threatCache.cacheResults(results); // 匹配日志和威胁信息 return matchThreatsFromResults(logs, results); } catch (error) { console.error('匹配威胁失败:', error); return []; } } // 从缓存结果匹配威胁 function matchThreatsFromCache(logs, cachedResults) { // 构建域名到威胁信息的映射 const threatMap = new Map(); cachedResults.forEach(result => { if (result.isThreat && result.data) { threatMap.set(result.domain, result.data); } }); return matchThreatsFromMap(logs, threatMap); } // 从批量查询结果匹配威胁 function matchThreatsFromResults(logs, results) { // 构建域名到威胁信息的映射 const threatMap = new Map(); results.forEach(result => { if (result.isThreat && result.data) { threatMap.set(result.domain, result.data); console.log('发现威胁域名:', result.domain); } }); return matchThreatsFromMap(logs, threatMap); } // 从威胁映射匹配日志 function matchThreatsFromMap(logs, threatMap) { const matchedThreats = []; let id = 1; logs.forEach(log => { const threatData = threatMap.get(log.domain); if (threatData) { const parts = threatData.split(','); if (parts.length >= 4) { const [type, name, riskLevel, domain] = parts; // 转换风险等级格式 let risk; switch (riskLevel) { case '1': risk = 'high'; break; case '2': risk = 'medium'; break; case '3': risk = 'low'; break; default: risk = 'medium'; } // 转换威胁类型格式 let typeStr; switch (type) { case '钓鱼网站': typeStr = 'phishing'; break; case '木马': typeStr = 'malware'; break; case '僵尸网络': typeStr = 'botnet'; break; case 'DGA 域名': typeStr = 'dga'; break; default: typeStr = 'suspicious'; } matchedThreats.push({ id: id++, timestamp: log.timestamp, type: typeStr, domain: log.domain, sourceIp: log.clientIP, risk: risk, status: log.result === 'blocked' ? 'blocked' : 'monitored', threatName: name, threatType: type, riskLevel: riskLevel }); } } }); console.log('威胁匹配完成,匹配记录数:', matchedThreats.length); return matchedThreats; } // 生成威胁统计数据 function generateThreatStats(threats) { let total = threats.length; let high = threats.filter(t => t.risk === 'high').length; let medium = threats.filter(t => t.risk === 'medium').length; let low = threats.filter(t => t.risk === 'low').length; return { total: total, high: high, medium: medium, low: low, percentChange: 12.5 // 模拟数据 }; } // 生成威胁趋势数据 function generateThreatTrends(threats) { // 生成过去12小时的时间标签 const labels = []; const data = []; for (let i = 11; i >= 0; i--) { const hour = new Date(); hour.setHours(hour.getHours() - i); labels.push(`${i}小时前`); // 统计该小时的威胁数量 const hourStart = new Date(hour); hourStart.setMinutes(0, 0, 0); const hourEnd = new Date(hourStart); hourEnd.setHours(hourEnd.getHours() + 1); const hourThreats = threats.filter(t => { const threatTime = new Date(t.timestamp); return threatTime >= hourStart && threatTime < hourEnd; }); data.push(hourThreats.length); } return { labels: labels, data: data }; } // 威胁数据结构 const threatData = { summary: { total: 0, high: 0, medium: 0, low: 0, percentChange: 0 }, trends: { labels: [], data: [] }, threats: [] }; // 显示局部加载状态 function showLoadingStates() { // 统计卡片显示加载状态 const statElements = [ 'total-threats', 'high-risk-threats', 'medium-risk-threats', 'low-risk-threats' ]; statElements.forEach(id => { const element = document.getElementById(id); if (element) { element.innerHTML = ''; } }); // 图表区域显示加载状态 const trendChart = document.getElementById('threat-trend-chart'); if (trendChart && trendChart.parentNode) { trendChart.parentNode.innerHTML = '
'; } const riskChart = document.getElementById('risk-distribution-chart'); if (riskChart && riskChart.parentNode) { riskChart.parentNode.innerHTML = '
'; } // 列表区域显示加载状态和进度提示 const threatList = document.getElementById('threat-list'); if (threatList) { threatList.innerHTML = '正在查询威胁数据,请稍候...'; } } // 显示加载错误 function showLoadingError(error) { console.error('加载错误:', error); // 统计卡片显示错误 const statElements = [ 'total-threats', 'high-risk-threats', 'medium-risk-threats', 'low-risk-threats' ]; statElements.forEach(id => { const element = document.getElementById(id); if (element) { element.textContent = '0'; } }); // 图表区域显示错误 const trendChartContainer = document.getElementById('threat-trend-chart')?.parentNode; if (trendChartContainer) { trendChartContainer.innerHTML = '
加载失败
'; } const riskChartContainer = document.getElementById('risk-distribution-chart')?.parentNode; if (riskChartContainer) { riskChartContainer.innerHTML = '
加载失败
'; } // 列表区域显示错误 const threatList = document.getElementById('threat-list'); if (threatList) { threatList.innerHTML = '加载失败,请刷新重试'; } } // 初始化威胁告警页面 async function initThreatsPage() { console.log('初始化威胁告警页面'); // 显示局部加载状态 showLoadingStates(); try { console.log('开始加载威胁数据库...'); const database = await loadThreatsDatabase(); console.log('威胁数据库加载完成,记录数:', database.length); console.log('开始加载 DNS 查询日志...'); const logs = await loadDNSLogs(); console.log('DNS 查询日志加载完成,记录数:', logs.length); console.log('开始匹配威胁...'); const threats = await matchThreatsWithLogs(); console.log('威胁匹配完成,匹配记录数:', threats.length); // 更新威胁数据 threatData.threats = threats; threatData.summary = generateThreatStats(threats); threatData.trends = generateThreatTrends(threats); console.log('威胁数据更新完成:', { threats: threats.length, summary: threatData.summary, trends: threatData.trends }); // 初始化页面组件 console.log('开始初始化页面组件...'); populateThreatStats(); // 数据加载完成后再渲染图表 console.log('渲染图表...'); renderThreatTrendChart(); renderRiskDistributionChart(); populateThreatList(); bindFilterEvents(); console.log('页面组件初始化完成'); } catch (error) { console.error('初始化威胁告警页面失败:', error); showLoadingError(error); } } // 填充威胁统计卡片 function populateThreatStats() { const summary = threatData.summary; // 填充总威胁数 document.getElementById('total-threats').textContent = summary.total; document.getElementById('threats-percent').textContent = `${summary.percentChange}%`; // 填充高风险威胁数 document.getElementById('high-risk-threats').textContent = summary.high; document.getElementById('high-risk-percent').textContent = `${Math.round((summary.high / summary.total) * 100)}%`; // 填充中风险威胁数 document.getElementById('medium-risk-threats').textContent = summary.medium; document.getElementById('medium-risk-percent').textContent = `${Math.round((summary.medium / summary.total) * 100)}%`; // 填充低风险威胁数 document.getElementById('low-risk-threats').textContent = summary.low; document.getElementById('low-risk-percent').textContent = `${Math.round((summary.low / summary.total) * 100)}%`; } // 渲染威胁趋势图表 function renderThreatTrendChart() { try { const canvas = document.getElementById('threat-trend-chart'); if (!canvas) { console.warn('威胁趋势图表 canvas 元素不存在'); return; } const ctx = canvas.getContext('2d'); // 销毁已存在的图表 if (window.threatTrendChart) { window.threatTrendChart.destroy(); } // 如果没有数据,显示空图表 const labels = threatData.trends.labels || []; const data = threatData.trends.data || []; // 创建新图表 window.threatTrendChart = new Chart(ctx, { type: 'line', data: { labels: labels.length > 0 ? labels : ['无数据'], datasets: [{ label: '威胁数量', data: data.length > 0 ? data : [0], borderColor: '#ef4444', backgroundColor: 'rgba(239, 68, 68, 0.1)', tension: 0.4, fill: true }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true, grid: { color: 'rgba(0, 0, 0, 0.05)' } }, x: { grid: { display: false } } } } }); } catch (error) { console.error('渲染威胁趋势图表失败:', error); } } // 渲染风险等级分布图表 function renderRiskDistributionChart() { try { const canvas = document.getElementById('risk-distribution-chart'); if (!canvas) { console.warn('风险等级分布图表 canvas 元素不存在'); return; } const ctx = canvas.getContext('2d'); const summary = threatData.summary; // 销毁已存在的图表 if (window.riskDistributionChart) { window.riskDistributionChart.destroy(); } // 检查是否有数据 const hasData = summary && (summary.high > 0 || summary.medium > 0 || summary.low > 0); if (!hasData) { // 没有数据时显示提示 ctx.font = '16px Arial'; ctx.fillStyle = '#999'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; const centerX = canvas.width / 2; const centerY = canvas.height / 2; ctx.fillText('暂无威胁数据', centerX, centerY); return; } // 创建新图表 window.riskDistributionChart = new Chart(ctx, { type: 'doughnut', data: { labels: ['高风险', '中风险', '低风险'], datasets: [{ data: [summary.high, summary.medium, summary.low], backgroundColor: ['#ef4444', '#f59e0b', '#3b82f6'], borderWidth: 0 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom' } }, cutout: '70%' } }); } catch (error) { console.error('渲染风险等级分布图表失败:', error); } } // 填充威胁告警列表 function populateThreatList(filteredThreats = null) { const threatList = document.getElementById('threat-list'); const threats = filteredThreats || threatData.threats; // 清空列表 threatList.innerHTML = ''; // 获取当前页的数据 const currentPageData = getCurrentPageData(threats); // 如果没有数据 if (currentPageData.length === 0) { threatList.innerHTML = '暂无威胁告警数据'; updatePaginationState(threats.length); renderPagination(); return; } // 填充列表 currentPageData.forEach(threat => { const row = document.createElement('tr'); // 获取威胁类型显示文本(优先使用 threatType,如果不存在则使用 type 映射) const typeText = threat.threatType || { 'malware': '恶意软件', 'phishing': '钓鱼网站', 'botnet': '僵尸网络', 'dga': 'DGA 域名', 'suspicious': '可疑活动' }[threat.type] || '未知'; // 获取风险等级显示文本和样式 const riskInfo = { 'high': { text: '高', class: 'bg-red-100 text-red-800' }, 'medium': { text: '中', class: 'bg-yellow-100 text-yellow-800' }, 'low': { text: '低', class: 'bg-blue-100 text-blue-800' } }[threat.risk]; // 获取状态显示文本和样式 const statusInfo = { 'blocked': { text: '已屏蔽', class: 'bg-green-100 text-green-800' }, 'monitored': { text: '监控中', class: 'bg-yellow-100 text-yellow-800' } }[threat.status]; // 设置行内容 row.innerHTML = ` ${threat.timestamp} ${typeText} ${threat.threatName || '未知'} ${threat.domain} ${threat.sourceIp} ${riskInfo.text} ${statusInfo.text} `; // 添加行到列表 threatList.appendChild(row); }); // 更新分页状态 updatePaginationState(threats.length); // 渲染分页控件 renderPagination(); } // 更新分页状态 function updatePaginationState(totalItems) { paginationState.totalItems = totalItems; paginationState.totalPages = Math.ceil(totalItems / paginationState.pageSize); // 确保当前页码在有效范围内 if (paginationState.currentPage > paginationState.totalPages) { paginationState.currentPage = Math.max(1, paginationState.totalPages); } } // 渲染分页控件 function renderPagination() { const pageSizeSelect = document.getElementById('page-size'); const currentPageInput = document.getElementById('current-page-input'); const prevPageBtn = document.getElementById('prev-page-btn'); const nextPageBtn = document.getElementById('next-page-btn'); const goToPageBtn = document.getElementById('go-to-page-btn'); if (!pageSizeSelect || !currentPageInput) return; // 更新每页显示数量 pageSizeSelect.value = paginationState.pageSize; // 更新当前页码输入 currentPageInput.value = paginationState.currentPage; currentPageInput.max = paginationState.totalPages || 1; // 更新按钮状态 if (prevPageBtn) { prevPageBtn.disabled = paginationState.currentPage === 1; prevPageBtn.classList.toggle('opacity-50', paginationState.currentPage === 1); prevPageBtn.classList.toggle('cursor-not-allowed', paginationState.currentPage === 1); } if (nextPageBtn) { nextPageBtn.disabled = paginationState.currentPage >= paginationState.totalPages; nextPageBtn.classList.toggle('opacity-50', paginationState.currentPage >= paginationState.totalPages); nextPageBtn.classList.toggle('cursor-not-allowed', paginationState.currentPage >= paginationState.totalPages); } // 绑定事件(只绑定一次) if (!window.paginationEventsBound) { bindPaginationEvents(); window.paginationEventsBound = true; } } // 绑定分页事件 function bindPaginationEvents() { const pageSizeSelect = document.getElementById('page-size'); const currentPageInput = document.getElementById('current-page-input'); const prevPageBtn = document.getElementById('prev-page-btn'); const nextPageBtn = document.getElementById('next-page-btn'); const goToPageBtn = document.getElementById('go-to-page-btn'); // 每页显示数量变化 if (pageSizeSelect) { pageSizeSelect.addEventListener('change', (e) => { paginationState.pageSize = parseInt(e.target.value); paginationState.currentPage = 1; // 重置到第一页 applyFilters(); // 重新应用过滤器 }); } // 上一页 if (prevPageBtn) { prevPageBtn.addEventListener('click', () => { if (paginationState.currentPage > 1) { paginationState.currentPage--; applyFilters(); } }); } // 下一页 if (nextPageBtn) { nextPageBtn.addEventListener('click', () => { if (paginationState.currentPage < paginationState.totalPages) { paginationState.currentPage++; applyFilters(); } }); } // 前往指定页 if (goToPageBtn && currentPageInput) { goToPageBtn.addEventListener('click', () => { const page = parseInt(currentPageInput.value); if (page >= 1 && page <= paginationState.totalPages) { paginationState.currentPage = page; applyFilters(); } else { alert(`请输入 1 到 ${paginationState.totalPages} 之间的页码`); } }); // 支持回车键 currentPageInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { goToPageBtn.click(); } }); } } // 获取当前页的数据 function getCurrentPageData(filteredThreats) { const start = (paginationState.currentPage - 1) * paginationState.pageSize; const end = start + paginationState.pageSize; return filteredThreats.slice(start, end); } // 绑定过滤器事件 function bindFilterEvents() { const riskFilter = document.getElementById('threat-filter-risk'); const typeFilter = document.getElementById('threat-filter-type'); const domainFilter = document.getElementById('threat-filter-domain'); const domainFilterBtn = document.getElementById('threat-filter-domain-btn'); const refreshBtn = document.getElementById('refresh-threats-btn'); // 域名筛选器事件(回车键) if (domainFilter) { domainFilter.addEventListener('keypress', (e) => { if (e.key === 'Enter') { applyFilters(); } }); } // 域名筛选按钮事件 if (domainFilterBtn) { domainFilterBtn.addEventListener('click', applyFilters); } // 风险等级过滤器事件 if (riskFilter) { riskFilter.addEventListener('change', applyFilters); } // 威胁类型过滤器事件 if (typeFilter) { typeFilter.addEventListener('change', applyFilters); } // 刷新按钮事件 if (refreshBtn) { refreshBtn.addEventListener('click', async function() { console.log('点击刷新威胁告警按钮'); // 显示加载状态 const originalIcon = refreshBtn.innerHTML; refreshBtn.innerHTML = ''; refreshBtn.disabled = true; refreshBtn.classList.add('opacity-70', 'cursor-not-allowed'); try { // 清除缓存 threatCache.clear(); // 重新加载数据 const threats = await matchThreatsWithLogs(); // 更新威胁数据 threatData.threats = threats; threatData.summary = generateThreatStats(threats); threatData.trends = generateThreatTrends(threats); // 重置分页 paginationState.currentPage = 1; // 重新渲染页面 populateThreatStats(); renderThreatTrendChart(); renderRiskDistributionChart(); applyFilters(); console.log('威胁数据刷新完成'); } catch (error) { console.error('刷新威胁数据失败:', error); alert('刷新失败,请重试'); } finally { refreshBtn.innerHTML = originalIcon; refreshBtn.disabled = false; } }); } } // 应用过滤器 function applyFilters() { const riskFilter = document.getElementById('threat-filter-risk')?.value || 'all'; const typeFilter = document.getElementById('threat-filter-type')?.value || 'all'; const domainFilter = document.getElementById('threat-filter-domain')?.value?.toLowerCase() || ''; // 过滤威胁数据 const filteredThreats = threatData.threats.filter(threat => { // 风险等级匹配 const riskMatch = riskFilter === 'all' || threat.risk === riskFilter; // 威胁类型匹配(使用 threatType,因为它是中文的) const typeMatch = typeFilter === 'all' || threat.threatType === typeFilter; // 域名匹配(支持模糊匹配) const domainMatch = domainFilter === '' || threat.domain.toLowerCase().includes(domainFilter) || (threat.threatName && threat.threatName.toLowerCase().includes(domainFilter)); return riskMatch && typeMatch && domainMatch; }); // 重置分页到第一页 paginationState.currentPage = 1; // 填充过滤后的威胁列表 populateThreatList(filteredThreats); }