Files
dns-server/.trae/documents/重写DNS日志详情弹窗.md
2026-01-14 23:08:46 +08:00

16 KiB
Raw Blame History

重写DNS日志详情弹窗方案

1. 问题分析

当前弹窗代码存在以下问题:

  • 代码结构复杂,大量嵌套条件和重复逻辑
  • 样式不够现代,布局不够灵活
  • 响应式设计不足,在大屏幕上显示效果不佳
  • 代码冗余,重复的记录解析逻辑
  • 可维护性差HTML结构直接写在JavaScript字符串中

2. 设计思路

  • 组件化设计:将弹窗拆分为更小的可复用组件
  • 简化逻辑重构DNS解析记录处理逻辑减少重复代码
  • 现代样式使用更现代的CSS布局和样式
  • 响应式设计:优化在不同屏幕尺寸下的显示效果
  • 更好的用户体验:添加动画效果、改进交互体验
  • 更清晰的代码结构:提高可维护性

3. 实现方案

3.1 重构showLogDetailModal函数

  • 简化函数结构,分离关注点
  • 将DNS解析记录处理逻辑提取为独立函数
  • 使用更现代的DOM API创建元素

3.2 优化DNS解析记录处理

  • 创建独立的formatDNSRecords函数
  • 统一处理各种格式的DNS记录
  • 减少重复代码,提高可维护性

3.3 现代CSS样式

  • 使用更现代的CSS类名和布局
  • 优化响应式设计,支持不同屏幕尺寸
  • 添加动画效果,提升用户体验

3.4 改进HTML结构

  • 使用更清晰的语义化HTML
  • 优化布局结构,提高可读性
  • 支持更好的响应式设计

4. 代码实现

// 独立的DNS记录格式化函数
function formatDNSRecords(log, result) {
    if (result === 'blocked') return '无';
    
    let records = '';
    const sources = [
        log.answers,
        log.answer,
        log.Records,
        log.records,
        log.response
    ];
    
    for (const source of sources) {
        if (records) break;
        if (!source || source === '无') continue;
        
        // 处理数组类型
        if (Array.isArray(source)) {
            records = source.map(answer => {
                const type = answer.type || answer.Type || '未知';
                let value = answer.value || answer.Value || answer.data || answer.Data || '未知';
                const ttl = answer.TTL || answer.ttl || answer.expires || '未知';
                
                // 增强的记录值提取逻辑
                if (typeof value === 'string') {
                    value = value.trim();
                    // 处理制表符分隔的格式
                    if (value.includes('\t') || value.includes('\\t')) {
                        const parts = value.replace(/\\t/g, '\t').split('\t');
                        if (parts.length >= 4) {
                            value = parts[parts.length - 1].trim();
                        }
                    }
                    // 处理JSON格式
                    else if (value.startsWith('{') && value.endsWith('}')) {
                        try {
                            const parsed = JSON.parse(value);
                            value = parsed.data || parsed.value || value;
                        } catch (e) {}
                    }
                }
                
                return `${type}: ${value} (ttl=${ttl})`;
            }).join('\n').trim();
        }
        // 处理字符串类型
        else if (typeof source === 'string') {
            // 尝试解析为JSON数组
            if (source.startsWith('[') && source.endsWith(']')) {
                try {
                    const parsed = JSON.parse(source);
                    if (Array.isArray(parsed)) {
                        records = parsed.map(answer => {
                            const type = answer.type || answer.Type || '未知';
                            let value = answer.value || answer.Value || answer.data || answer.Data || '未知';
                            const ttl = answer.TTL || answer.ttl || answer.expires || '未知';
                            
                            if (typeof value === 'string') {
                                value = value.trim();
                            }
                            
                            return `${type}: ${value} (ttl=${ttl})`;
                        }).join('\n').trim();
                    }
                } catch (e) {
                    // 解析失败,尝试直接格式化
                    records = formatDNSString(source);
                }
            } else {
                // 直接格式化字符串
                records = formatDNSString(source);
            }
        }
    }
    
    return records || '无解析记录';
}

// 格式化DNS字符串记录
function formatDNSString(str) {
    const recordLines = str.split('\n').map(line => line.trim()).filter(line => line !== '');
    
    return recordLines.map(line => {
        // 检查是否已经是标准格式
        if (line.includes(':') && line.includes('(')) {
            return line;
        }
        // 尝试解析为标准DNS格式
        const parts = line.split(/\s+/);
        if (parts.length >= 5) {
            const type = parts[3];
            const value = parts.slice(4).join(' ');
            const ttl = parts[1];
            return `${type}: ${value} (ttl=${ttl})`;
        }
        // 无法解析,返回原始行
        return line;
    }).join('\n');
}

// 重写后的showLogDetailModal函数
async function showLogDetailModal(log) {
    if (!log) {
        console.error('No log data provided!');
        return;
    }
    
    try {
        // 安全获取log属性提供默认值
        const timestamp = log.timestamp ? new Date(log.timestamp) : null;
        const dateStr = timestamp ? timestamp.toLocaleDateString() : '未知';
        const timeStr = timestamp ? timestamp.toLocaleTimeString() : '未知';
        const domain = log.domain || '未知';
        const queryType = log.queryType || '未知';
        const result = log.result || '未知';
        const responseTime = log.responseTime || '未知';
        const clientIP = log.clientIP || '未知';
        const location = log.location || '未知';
        const fromCache = log.fromCache || false;
        const dnssec = log.dnssec || false;
        const edns = log.edns || false;
        const dnsServer = log.dnsServer || '无';
        const dnssecServer = log.dnssecServer || '无';
        const blockRule = log.blockRule || '无';
        
        // 检查域名是否在跟踪器数据库中
        const trackerInfo = await isDomainInTrackerDatabase(log.domain);
        const isTracker = trackerInfo !== null;
        
        // 格式化DNS解析记录
        const dnsRecords = formatDNSRecords(log, result);
        
        // 创建模态框容器
        const modalContainer = document.createElement('div');
        modalContainer.className = 'fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4 animate-fade-in';
        modalContainer.style.zIndex = '9999';
        
        // 创建模态框内容
        const modalContent = document.createElement('div');
        modalContent.className = 'bg-white rounded-xl shadow-2xl w-full max-w-2xl max-h-[90vh] overflow-y-auto animate-slide-in';
        
        // 创建标题栏
        const header = document.createElement('div');
        header.className = 'sticky top-0 bg-white border-b border-gray-200 px-6 py-4 flex justify-between items-center';
        
        const title = document.createElement('h3');
        title.className = 'text-xl font-semibold text-gray-900';
        title.textContent = '日志详情';
        
        const closeButton = document.createElement('button');
        closeButton.innerHTML = '<i class="fa fa-times text-xl"></i>';
        closeButton.className = 'text-gray-500 hover:text-gray-700 focus:outline-none transition-colors';
        closeButton.onclick = () => closeModal();
        
        header.appendChild(title);
        header.appendChild(closeButton);
        
        // 创建内容区域
        const content = document.createElement('div');
        content.className = 'p-6 space-y-6';
        
        // 基本信息部分
        const basicInfo = document.createElement('div');
        basicInfo.className = 'space-y-4';
        
        const basicInfoTitle = document.createElement('h4');
        basicInfoTitle.className = 'text-sm font-medium text-gray-700 uppercase tracking-wider';
        basicInfoTitle.textContent = '基本信息';
        
        const basicInfoGrid = document.createElement('div');
        basicInfoGrid.className = 'grid grid-cols-1 md:grid-cols-2 gap-4';
        
        // 添加基本信息项
        basicInfoGrid.innerHTML = `
            <div class="space-y-1">
                <div class="text-xs text-gray-500">日期</div>
                <div class="text-sm font-medium text-gray-900">${dateStr}</div>
            </div>
            <div class="space-y-1">
                <div class="text-xs text-gray-500">时间</div>
                <div class="text-sm font-medium text-gray-900">${timeStr}</div>
            </div>
            <div class="space-y-1">
                <div class="text-xs text-gray-500">状态</div>
                <div class="text-sm font-medium ${result === 'blocked' ? 'text-red-600' : result === 'allowed' ? 'text-green-600' : 'text-gray-500'}">
                    ${result === 'blocked' ? '已拦截' : result === 'allowed' ? '允许' : result}
                </div>
            </div>
            <div class="space-y-1">
                <div class="text-xs text-gray-500">域名</div>
                <div class="text-sm font-medium text-gray-900 break-all">${domain}</div>
            </div>
            <div class="space-y-1">
                <div class="text-xs text-gray-500">类型</div>
                <div class="text-sm font-medium text-gray-900">${queryType}</div>
            </div>
        `;
        
        // DNS特性
        const dnsFeatures = document.createElement('div');
        dnsFeatures.className = 'col-span-1 md:col-span-2 space-y-1';
        dnsFeatures.innerHTML = `
            <div class="text-xs text-gray-500">DNS特性</div>
            <div class="text-sm font-medium text-gray-900">
                ${dnssec ? '<i class="fa fa-lock text-green-500 mr-1" title="DNSSEC已启用"></i>DNSSEC ' : ''}
                ${edns ? '<i class="fa fa-exchange text-blue-500 mr-1" title="EDNS已启用"></i>EDNS' : ''}
                ${!dnssec && !edns ? '无' : ''}
            </div>
        `;
        
        // 跟踪器信息
        const trackerDiv = document.createElement('div');
        trackerDiv.className = 'col-span-1 md:col-span-2 space-y-1';
        trackerDiv.innerHTML = `
            <div class="text-xs text-gray-500">跟踪器信息</div>
            <div class="text-sm font-medium text-gray-900">
                ${isTracker ? `
                    <div class="flex items-center">
                        <i class="fa fa-eye text-red-500 mr-1"></i>
                        <span>${trackerInfo.name} (${trackersDatabase.categories[trackerInfo.categoryId] || '未知'})</span>
                    </div>
                ` : '无'}
            </div>
        `;
        
        // 解析记录
        const recordsDiv = document.createElement('div');
        recordsDiv.className = 'col-span-1 md:col-span-2 space-y-1';
        recordsDiv.innerHTML = `
            <div class="text-xs text-gray-500">解析记录</div>
            <div class="text-sm font-medium text-gray-900 whitespace-pre-wrap break-all bg-gray-50 p-3 rounded-md border border-gray-200">
                ${dnsRecords}
            </div>
        `;
        
        // DNS服务器
        const dnsServerDiv = document.createElement('div');
        dnsServerDiv.className = 'col-span-1 md:col-span-2 space-y-1';
        dnsServerDiv.innerHTML = `
            <div class="text-xs text-gray-500">DNS服务器</div>
            <div class="text-sm font-medium text-gray-900">${dnsServer}</div>
        `;
        
        // DNSSEC专用服务器
        const dnssecServerDiv = document.createElement('div');
        dnssecServerDiv.className = 'col-span-1 md:col-span-2 space-y-1';
        dnssecServerDiv.innerHTML = `
            <div class="text-xs text-gray-500">DNSSEC专用服务器</div>
            <div class="text-sm font-medium text-gray-900">${dnssecServer}</div>
        `;
        
        basicInfoGrid.appendChild(dnsFeatures);
        basicInfoGrid.appendChild(trackerDiv);
        basicInfoGrid.appendChild(recordsDiv);
        basicInfoGrid.appendChild(dnsServerDiv);
        basicInfoGrid.appendChild(dnssecServerDiv);
        
        basicInfo.appendChild(basicInfoTitle);
        basicInfo.appendChild(basicInfoGrid);
        
        // 响应细节部分
        const responseDetails = document.createElement('div');
        responseDetails.className = 'space-y-4 pt-4 border-t border-gray-200';
        
        const responseDetailsTitle = document.createElement('h4');
        responseDetailsTitle.className = 'text-sm font-medium text-gray-700 uppercase tracking-wider';
        responseDetailsTitle.textContent = '响应细节';
        
        const responseGrid = document.createElement('div');
        responseGrid.className = 'grid grid-cols-1 md:grid-cols-2 gap-4';
        responseGrid.innerHTML = `
            <div class="space-y-1">
                <div class="text-xs text-gray-500">响应时间</div>
                <div class="text-sm font-medium text-gray-900">${responseTime}毫秒</div>
            </div>
            <div class="space-y-1">
                <div class="text-xs text-gray-500">规则</div>
                <div class="text-sm font-medium text-gray-900">${blockRule}</div>
            </div>
            <div class="space-y-1">
                <div class="text-xs text-gray-500">响应代码</div>
                <div class="text-sm font-medium text-gray-900">${getResponseCodeText(log.responseCode)}</div>
            </div>
            <div class="space-y-1">
                <div class="text-xs text-gray-500">缓存状态</div>
                <div class="text-sm font-medium ${fromCache ? 'text-primary' : 'text-gray-500'}">
                    ${fromCache ? '缓存' : '非缓存'}
                </div>
            </div>
        `;
        
        responseDetails.appendChild(responseDetailsTitle);
        responseDetails.appendChild(responseGrid);
        
        // 客户端详情部分
        const clientDetails = document.createElement('div');
        clientDetails.className = 'space-y-4 pt-4 border-t border-gray-200';
        
        const clientDetailsTitle = document.createElement('h4');
        clientDetailsTitle.className = 'text-sm font-medium text-gray-700 uppercase tracking-wider';
        clientDetailsTitle.textContent = '客户端详情';
        
        const clientIPDiv = document.createElement('div');
        clientIPDiv.className = 'space-y-1';
        clientIPDiv.innerHTML = `
            <div class="text-xs text-gray-500">IP地址</div>
            <div class="text-sm font-medium text-gray-900">${clientIP} (${location})</div>
        `;
        
        clientDetails.appendChild(clientDetailsTitle);
        clientDetails.appendChild(clientIPDiv);
        
        // 组装内容
        content.appendChild(basicInfo);
        content.appendChild(responseDetails);
        content.appendChild(clientDetails);
        
        // 组装模态框
        modalContent.appendChild(header);
        modalContent.appendChild(content);
        modalContainer.appendChild(modalContent);
        
        // 添加到页面
        document.body.appendChild(modalContainer);
        
        // 关闭模态框函数
        function closeModal() {
            modalContainer.classList.add('animate-fade-out');
            modalContent.classList.add('animate-slide-out');
            
            // 等待动画结束后移除元素
            setTimeout(() => {
                document.body.removeChild(modalContainer);
            }, 300);
        }
        
        // 点击外部关闭
        modalContainer.addEventListener('click', (e) => {
            if (e.target === modalContainer) {
                closeModal();
            }
        });
        
        // ESC键关闭
        const handleEsc = (e) => {
            if (e.key === 'Escape') {
                closeModal();
                document.removeEventListener('keydown', handleEsc);
            }
        };
        document.addEventListener('keydown', handleEsc);
        
    } catch (error) {
        console.error('Error showing log detail modal:', error);
    }
}