增加查询日志详情界面点击域名列表,显示解析日志的详细信息.
This commit is contained in:
@@ -107,6 +107,9 @@ function initLogsPage() {
|
||||
// 绑定事件
|
||||
bindLogsEvents();
|
||||
|
||||
// 初始化日志详情弹窗
|
||||
initLogDetailModal();
|
||||
|
||||
// 建立WebSocket连接,用于实时更新统计数据和图表
|
||||
connectLogsWebSocket();
|
||||
|
||||
@@ -425,6 +428,9 @@ async function updateLogsTable(logs) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检测是否为移动设备
|
||||
const isMobile = window.innerWidth <= 768;
|
||||
|
||||
// 填充表格
|
||||
for (const log of logs) {
|
||||
const row = document.createElement('tr');
|
||||
@@ -491,7 +497,7 @@ async function updateLogsTable(logs) {
|
||||
const trackerInfo = await isDomainInTrackerDatabase(log.Domain);
|
||||
const isTracker = trackerInfo !== null;
|
||||
|
||||
// 构建行内容 - 两行显示,时间列显示时间和日期,请求列显示域名和类型状态
|
||||
// 构建行内容 - 根据设备类型决定显示内容
|
||||
// 添加缓存状态显示
|
||||
const cacheStatusClass = log.FromCache ? 'text-primary' : 'text-gray-500';
|
||||
const cacheStatusText = log.FromCache ? '缓存' : '非缓存';
|
||||
@@ -510,36 +516,59 @@ async function updateLogsTable(logs) {
|
||||
</div>
|
||||
` : '';
|
||||
|
||||
row.innerHTML = `
|
||||
<td class="py-3 px-4">
|
||||
<div class="text-sm font-medium">${formattedTime}</div>
|
||||
<div class="text-xs text-gray-500 mt-1">${formattedDate}</div>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-sm">
|
||||
<div class="font-medium">${log.ClientIP}</div>
|
||||
<div class="text-xs text-gray-500 mt-1">${log.Location || '未知 未知'}</div>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-sm">
|
||||
<div class="font-medium flex items-center relative">
|
||||
${log.DNSSEC ? '<i class="fa fa-lock text-green-500 mr-1" title="DNSSEC已启用"></i>' : ''}
|
||||
<div class="tracker-icon-container relative">
|
||||
${isTracker ? '<i class="fa fa-eye text-red-500 mr-1"></i>' : '<i class="fa fa-eye-slash text-gray-300 mr-1"></i>'}
|
||||
${trackerTooltip}
|
||||
if (isMobile) {
|
||||
// 移动设备只显示时间和请求信息
|
||||
row.innerHTML = `
|
||||
<td class="py-3 px-4">
|
||||
<div class="text-sm font-medium">${formattedTime}</div>
|
||||
<div class="text-xs text-gray-500 mt-1">${formattedDate}</div>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-sm" colspan="5">
|
||||
<div class="font-medium flex items-center relative">
|
||||
${log.DNSSEC ? '<i class="fa fa-lock text-green-500 mr-1" title="DNSSEC已启用"></i>' : ''}
|
||||
<div class="tracker-icon-container relative">
|
||||
${isTracker ? '<i class="fa fa-eye text-red-500 mr-1"></i>' : '<i class="fa fa-eye-slash text-gray-300 mr-1"></i>'}
|
||||
${trackerTooltip}
|
||||
</div>
|
||||
${log.Domain}
|
||||
</div>
|
||||
${log.Domain}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 mt-1">类型: ${log.QueryType}, <span class="${statusClass}">${statusText}</span>, <span class="${cacheStatusClass}">${log.FromCache ? '缓存' : '实时'}</span>${log.DNSSEC ? ', <span class="text-green-500"><i class="fa fa-lock"></i> DNSSEC</span>' : ''}${log.EDNS ? ', <span class="text-blue-500"><i class="fa fa-exchange"></i> EDNS</span>' : ''}</div>
|
||||
<div class="text-xs text-gray-500 mt-1">DNS 服务器: ${log.DNSServer || '无'}, DNSSEC专用: ${log.DNSSECServer || '无'}</div>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-sm">${log.ResponseTime}ms</td>
|
||||
<td class="py-3 px-4 text-sm text-gray-500">${log.BlockRule || '-'}</td>
|
||||
<td class="py-3 px-4 text-sm text-center">
|
||||
${isBlocked ?
|
||||
`<button class="unblock-btn px-3 py-1 bg-green-500 text-white rounded-md hover:bg-green-600 transition-colors text-xs" data-domain="${log.Domain}">放行</button>` :
|
||||
`<button class="block-btn px-3 py-1 bg-red-500 text-white rounded-md hover:bg-red-600 transition-colors text-xs" data-domain="${log.Domain}">拦截</button>`
|
||||
}
|
||||
</td>
|
||||
`;
|
||||
<div class="text-xs text-gray-500 mt-1">类型: ${log.QueryType}, <span class="${statusClass}">${statusText}</span></div>
|
||||
<div class="text-xs text-gray-500 mt-1">客户端: ${log.ClientIP}</div>
|
||||
</td>
|
||||
`;
|
||||
} else {
|
||||
// 桌面设备显示完整信息
|
||||
row.innerHTML = `
|
||||
<td class="py-3 px-4">
|
||||
<div class="text-sm font-medium">${formattedTime}</div>
|
||||
<div class="text-xs text-gray-500 mt-1">${formattedDate}</div>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-sm">
|
||||
<div class="font-medium">${log.ClientIP}</div>
|
||||
<div class="text-xs text-gray-500 mt-1">${log.Location || '未知 未知'}</div>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-sm">
|
||||
<div class="font-medium flex items-center relative">
|
||||
${log.DNSSEC ? '<i class="fa fa-lock text-green-500 mr-1" title="DNSSEC已启用"></i>' : ''}
|
||||
<div class="tracker-icon-container relative">
|
||||
${isTracker ? '<i class="fa fa-eye text-red-500 mr-1"></i>' : '<i class="fa fa-eye-slash text-gray-300 mr-1"></i>'}
|
||||
${trackerTooltip}
|
||||
</div>
|
||||
${log.Domain}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 mt-1">类型: ${log.QueryType}, <span class="${statusClass}">${statusText}</span>, <span class="${cacheStatusClass}">${log.FromCache ? '缓存' : '实时'}</span>${log.DNSSEC ? ', <span class="text-green-500"><i class="fa fa-lock"></i> DNSSEC</span>' : ''}${log.EDNS ? ', <span class="text-blue-500"><i class="fa fa-exchange"></i> EDNS</span>' : ''}</div>
|
||||
<div class="text-xs text-gray-500 mt-1">DNS 服务器: ${log.DNSServer || '无'}, DNSSEC专用: ${log.DNSSECServer || '无'}</div>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-sm">${log.ResponseTime}ms</td>
|
||||
<td class="py-3 px-4 text-sm text-gray-500">${log.BlockRule || '-'}</td>
|
||||
<td class="py-3 px-4 text-sm text-center">
|
||||
${isBlocked ?
|
||||
`<button class="unblock-btn px-3 py-1 bg-green-500 text-white rounded-md hover:bg-green-600 transition-colors text-xs" data-domain="${log.Domain}">放行</button>` :
|
||||
`<button class="block-btn px-3 py-1 bg-red-500 text-white rounded-md hover:bg-red-600 transition-colors text-xs" data-domain="${log.Domain}">拦截</button>`
|
||||
}
|
||||
</td>
|
||||
`;
|
||||
}
|
||||
|
||||
// 添加跟踪器图标悬停事件
|
||||
if (isTracker) {
|
||||
@@ -576,6 +605,16 @@ async function updateLogsTable(logs) {
|
||||
unblockDomain(domain);
|
||||
});
|
||||
}
|
||||
|
||||
// 绑定日志详情点击事件
|
||||
row.addEventListener('click', (e) => {
|
||||
// 如果点击的是按钮,不触发详情弹窗
|
||||
if (e.target.closest('button')) {
|
||||
return;
|
||||
}
|
||||
console.log('Row clicked, log object:', log);
|
||||
showLogDetailModal(log);
|
||||
});
|
||||
|
||||
tableBody.appendChild(row);
|
||||
}
|
||||
@@ -932,6 +971,663 @@ async function unblockDomain(domain) {
|
||||
}
|
||||
}
|
||||
|
||||
// 显示日志详情弹窗
|
||||
async function showLogDetailModal(log) {
|
||||
console.log('showLogDetailModal called with log:', JSON.stringify(log, null, 2)); // 输出完整的log对象
|
||||
|
||||
// 确保log对象存在
|
||||
if (!log) {
|
||||
console.error('No log data provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 简化版本,直接创建一个新的模态框
|
||||
const modalContainer = document.createElement('div');
|
||||
modalContainer.className = 'fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center';
|
||||
modalContainer.style.zIndex = '9999'; // 确保z-index足够高
|
||||
|
||||
// 创建模态框内容
|
||||
const modalContent = document.createElement('div');
|
||||
modalContent.className = 'bg-white rounded-lg shadow-xl p-6 w-full max-w-md';
|
||||
|
||||
// 添加关闭按钮
|
||||
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 ml-auto';
|
||||
closeButton.onclick = function() {
|
||||
document.body.removeChild(modalContainer);
|
||||
};
|
||||
|
||||
// 创建标题栏
|
||||
const titleBar = document.createElement('div');
|
||||
titleBar.className = 'flex justify-between items-center mb-4';
|
||||
|
||||
// 添加标题
|
||||
const title = document.createElement('h3');
|
||||
title.className = 'text-xl font-semibold';
|
||||
title.textContent = '日志详情';
|
||||
|
||||
// 将标题和关闭按钮添加到标题栏
|
||||
titleBar.appendChild(title);
|
||||
titleBar.appendChild(closeButton);
|
||||
|
||||
// 创建详情内容
|
||||
const details = document.createElement('div');
|
||||
details.className = 'space-y-4';
|
||||
|
||||
// 安全获取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 dnsResponse = log.Response || '无';
|
||||
// 添加调试信息,查看log对象结构
|
||||
console.log('=== DNS日志对象结构 ===');
|
||||
console.log('log对象:', log);
|
||||
console.log('log字段列表:', Object.keys(log));
|
||||
console.log('Answers字段值:', log.Answers);
|
||||
console.log('answers字段值:', log.answers);
|
||||
console.log('Response字段值:', log.Response);
|
||||
console.log('Answer字段值:', log.Answer);
|
||||
// 处理Answers字段,确保正确解析
|
||||
let dnsAnswers = log.Answers || log.answers || [];
|
||||
|
||||
// 添加更多调试信息
|
||||
console.log('=== 解析记录提取调试信息 ===');
|
||||
console.log('日志对象:', log);
|
||||
console.log('日志字段:', Object.keys(log));
|
||||
|
||||
// 检查所有可能的解析记录字段
|
||||
const potentialFields = ['Answers', 'answers', 'Answer', 'answer', 'Records', 'records', 'Response'];
|
||||
potentialFields.forEach(field => {
|
||||
console.log(`${field}:`, log[field]);
|
||||
});
|
||||
|
||||
// 关键修复:如果Answers是字符串类型的JSON数组,强制解析
|
||||
if (typeof dnsAnswers === 'string') {
|
||||
// 先检查是否是有效的JSON数组格式
|
||||
if (dnsAnswers.startsWith('[') && dnsAnswers.endsWith(']')) {
|
||||
try {
|
||||
dnsAnswers = JSON.parse(dnsAnswers);
|
||||
} catch (e) {
|
||||
console.error('解析Answers JSON数组失败:', e);
|
||||
dnsAnswers = [];
|
||||
}
|
||||
} else {
|
||||
// 如果不是数组格式,尝试解析为单个对象
|
||||
try {
|
||||
dnsAnswers = JSON.parse(dnsAnswers);
|
||||
// 如果解析后是单个对象,转换为数组
|
||||
if (typeof dnsAnswers === 'object' && !Array.isArray(dnsAnswers)) {
|
||||
dnsAnswers = [dnsAnswers];
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('解析Answers JSON失败:', e);
|
||||
dnsAnswers = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('处理后的dnsAnswers:', dnsAnswers);
|
||||
|
||||
// 添加基本信息,使用安全获取的值
|
||||
details.innerHTML = `
|
||||
<!-- 第1组:基本信息 -->
|
||||
<div class="text-xs">
|
||||
<div class="font-medium text-gray-700 mb-2">基本信息</div>
|
||||
<div class="grid grid-cols-2 gap-y-2 gap-x-4">
|
||||
<div>
|
||||
<div class="text-gray-500">日期:</div>
|
||||
<div class="text-gray-800">${dateStr}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-gray-500">时间:</div>
|
||||
<div class="text-gray-800">${timeStr}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-gray-500">状态:</div>
|
||||
<div class="${result === 'blocked' ? 'text-red-600' : result === 'allowed' ? 'text-green-600' : 'text-gray-500'}">
|
||||
${result === 'blocked' ? '已拦截' : result === 'allowed' ? '允许' : result}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-gray-500">域名:</div>
|
||||
<div class="text-gray-800">${domain}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-gray-500">类型:</div>
|
||||
<div class="text-gray-800">${queryType}</div>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<div class="text-gray-500">DNS特性:</div>
|
||||
<div class="text-gray-800">
|
||||
${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>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<div class="text-gray-500">跟踪器信息:</div>
|
||||
<div class="text-gray-800">
|
||||
${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>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<div class="text-gray-500">解析记录:</div>
|
||||
<div class="text-gray-800 whitespace-pre-wrap break-all text-left">
|
||||
${result === 'blocked' ? '无' : (() => {
|
||||
// 尝试从不同字段获取解析记录
|
||||
let records = '';
|
||||
|
||||
// 1. 尝试使用Answers数组 - 始终优先使用Answers
|
||||
if (dnsAnswers && Array.isArray(dnsAnswers) && dnsAnswers.length > 0) {
|
||||
records = dnsAnswers.map(answer => {
|
||||
// 处理不同格式的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 || '未知';
|
||||
|
||||
// 增强的记录值提取逻辑:处理多种DNS记录格式
|
||||
if (typeof value === 'string') {
|
||||
// 如果value是完整的DNS记录字符串,提取出实际值
|
||||
if (value.includes('\t') || value.includes('\\t')) {
|
||||
// 处理实际制表符或转义的制表符
|
||||
const parts = value.replace(/\\t/g, '\t').split('\t');
|
||||
// 兼容不同长度的记录格式
|
||||
if (parts.length >= 4) {
|
||||
// 对于标准DNS响应格式:domain\tttl\tIN\ttype\tvalue
|
||||
value = parts[parts.length - 1].trim();
|
||||
} else if (parts.length >= 2) {
|
||||
// 对于其他格式,使用最后一个字段
|
||||
value = parts[parts.length - 1].trim();
|
||||
}
|
||||
}
|
||||
// 如果value是JSON字符串,尝试解析
|
||||
else if (value.startsWith('{') && value.endsWith('}')) {
|
||||
try {
|
||||
const parsedValue = JSON.parse(value);
|
||||
value = parsedValue.data || parsedValue.value || value;
|
||||
// 解析后的值也需要trim
|
||||
if (typeof value === 'string') {
|
||||
value = value.trim();
|
||||
}
|
||||
} catch (e) {
|
||||
// 解析失败,保持原值但trim
|
||||
value = value.trim();
|
||||
}
|
||||
}
|
||||
// 对于其他所有字符串类型的值,直接trim
|
||||
else {
|
||||
value = value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
return `${type}: ${value} (ttl=${ttl})`;
|
||||
}).join('\n').trim();
|
||||
}
|
||||
// 2. 尝试解析字符串类型的Answers - 增强的容错处理
|
||||
else if (typeof dnsAnswers === 'string') {
|
||||
try {
|
||||
const parsedAnswers = JSON.parse(dnsAnswers);
|
||||
if (Array.isArray(parsedAnswers)) {
|
||||
records = parsedAnswers.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 || '未知';
|
||||
|
||||
// 增强的记录值提取逻辑:处理多种DNS记录格式
|
||||
if (typeof value === 'string') {
|
||||
// 如果value是完整的DNS记录字符串,提取出实际值
|
||||
if (value.includes('\t') || value.includes('\\t')) {
|
||||
// 处理实际制表符或转义的制表符
|
||||
const parts = value.replace(/\\t/g, '\t').split('\t');
|
||||
// 兼容不同长度的记录格式
|
||||
if (parts.length >= 4) {
|
||||
// 对于标准DNS响应格式:domain\tttl\tIN\ttype\tvalue
|
||||
value = parts[parts.length - 1].trim();
|
||||
} else if (parts.length >= 2) {
|
||||
// 对于其他格式,使用最后一个字段
|
||||
value = parts[parts.length - 1].trim();
|
||||
}
|
||||
}
|
||||
// 对于其他所有字符串类型的值,直接trim
|
||||
else {
|
||||
value = value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
return `${type}: ${value} (ttl=${ttl})`;
|
||||
}).join('\n').trim();
|
||||
}
|
||||
} catch (e) {
|
||||
// 解析失败,继续尝试其他字段
|
||||
}
|
||||
}
|
||||
// 3. 尝试从log.Answer字段获取(单数形式)
|
||||
if (!records && log.Answer) {
|
||||
if (Array.isArray(log.Answer)) {
|
||||
records = log.Answer.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 || '未知';
|
||||
|
||||
// 增强的记录值提取逻辑:处理多种DNS记录格式
|
||||
if (typeof value === 'string') {
|
||||
// 如果value是完整的DNS记录字符串,提取出实际值
|
||||
if (value.includes('\t') || value.includes('\\t')) {
|
||||
// 处理实际制表符或转义的制表符
|
||||
const parts = value.replace(/\\t/g, '\t').split('\t');
|
||||
// 兼容不同长度的记录格式
|
||||
if (parts.length >= 4) {
|
||||
// 对于标准DNS响应格式:domain\tttl\tIN\ttype\tvalue
|
||||
value = parts[parts.length - 1].trim();
|
||||
} else if (parts.length >= 2) {
|
||||
// 对于其他格式,使用最后一个字段
|
||||
value = parts[parts.length - 1].trim();
|
||||
}
|
||||
}
|
||||
// 对于其他所有字符串类型的值,直接trim
|
||||
else {
|
||||
value = value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
return `${type}: ${value} (ttl=${ttl})`;
|
||||
}).join('\n').trim();
|
||||
} else if (typeof log.Answer === 'object') {
|
||||
// 单个answer对象
|
||||
const type = log.Answer.type || log.Answer.Type || '未知';
|
||||
let value = log.Answer.value || log.Answer.Value || log.Answer.data || log.Answer.Data || '未知';
|
||||
const ttl = log.Answer.TTL || log.Answer.ttl || log.Answer.expires || '未知';
|
||||
|
||||
// 增强的记录值提取逻辑:处理多种DNS记录格式
|
||||
if (typeof value === 'string') {
|
||||
// 如果value是完整的DNS记录字符串,提取出实际值
|
||||
if (value.includes('\t') || value.includes('\\t')) {
|
||||
// 处理实际制表符或转义的制表符
|
||||
const parts = value.replace(/\\t/g, '\t').split('\t');
|
||||
// 兼容不同长度的记录格式
|
||||
if (parts.length >= 4) {
|
||||
// 对于标准DNS响应格式:domain\tttl\tIN\ttype\tvalue
|
||||
value = parts[parts.length - 1].trim();
|
||||
} else if (parts.length >= 2) {
|
||||
// 对于其他格式,使用最后一个字段
|
||||
value = parts[parts.length - 1].trim();
|
||||
}
|
||||
}
|
||||
// 对于其他所有字符串类型的值,直接trim
|
||||
else {
|
||||
value = value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
records = `${type}: ${value} (ttl=${ttl})`;
|
||||
} else if (typeof log.Answer === 'string') {
|
||||
// 字符串类型的Answer - 处理每行缩进
|
||||
records = log.Answer.split('\n').map(line => line.trim()).filter(line => line !== '').join('\n');
|
||||
}
|
||||
}
|
||||
// 4. 尝试从log.answer字段获取(小写单数形式)
|
||||
if (!records && log.answer) {
|
||||
if (Array.isArray(log.answer)) {
|
||||
records = log.answer.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 || '未知';
|
||||
|
||||
// 增强的记录值提取逻辑:处理多种DNS记录格式
|
||||
if (typeof value === 'string') {
|
||||
// 如果value是完整的DNS记录字符串,提取出实际值
|
||||
if (value.includes('\t') || value.includes('\\t')) {
|
||||
// 处理实际制表符或转义的制表符
|
||||
const parts = value.replace(/\\t/g, '\t').split('\t');
|
||||
// 兼容不同长度的记录格式
|
||||
if (parts.length >= 4) {
|
||||
// 对于标准DNS响应格式:domain\tttl\tIN\ttype\tvalue
|
||||
value = parts[parts.length - 1].trim();
|
||||
} else if (parts.length >= 2) {
|
||||
// 对于其他格式,使用最后一个字段
|
||||
value = parts[parts.length - 1].trim();
|
||||
}
|
||||
}
|
||||
// 对于其他所有字符串类型的值,直接trim
|
||||
else {
|
||||
value = value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
return `${type}: ${value} (ttl=${ttl})`;
|
||||
}).join('\n').trim();
|
||||
} else if (typeof log.answer === 'object') {
|
||||
// 单个answer对象
|
||||
const type = log.answer.type || log.answer.Type || '未知';
|
||||
let value = log.answer.value || log.answer.Value || log.answer.data || log.answer.Data || '未知';
|
||||
const ttl = log.answer.TTL || log.answer.ttl || log.answer.expires || '未知';
|
||||
|
||||
// 增强的记录值提取逻辑:处理多种DNS记录格式
|
||||
if (typeof value === 'string') {
|
||||
// 如果value是完整的DNS记录字符串,提取出实际值
|
||||
if (value.includes('\t') || value.includes('\\t')) {
|
||||
// 处理实际制表符或转义的制表符
|
||||
const parts = value.replace(/\\t/g, '\t').split('\t');
|
||||
// 兼容不同长度的记录格式
|
||||
if (parts.length >= 4) {
|
||||
// 对于标准DNS响应格式:domain\tttl\tIN\ttype\tvalue
|
||||
value = parts[parts.length - 1].trim();
|
||||
} else if (parts.length >= 2) {
|
||||
// 对于其他格式,使用最后一个字段
|
||||
value = parts[parts.length - 1].trim();
|
||||
}
|
||||
}
|
||||
// 对于其他所有字符串类型的值,直接trim
|
||||
else {
|
||||
value = value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
records = `${type}: ${value} (ttl=${ttl})`;
|
||||
} else if (typeof log.answer === 'string') {
|
||||
// 字符串类型的answer - 处理每行缩进
|
||||
records = log.answer.split('\n').map(line => line.trim()).filter(line => line !== '').join('\n');
|
||||
}
|
||||
}
|
||||
// 5. 尝试从log.Records字段获取
|
||||
if (!records && log.Records) {
|
||||
if (Array.isArray(log.Records)) {
|
||||
records = log.Records.map(record => {
|
||||
const type = record.type || record.Type || '未知';
|
||||
let value = record.value || record.Value || record.data || record.Data || '未知';
|
||||
const ttl = record.TTL || record.ttl || record.expires || '未知';
|
||||
|
||||
// 增强的记录值提取逻辑:处理多种DNS记录格式
|
||||
if (typeof value === 'string') {
|
||||
// 如果value是完整的DNS记录字符串,提取出实际值
|
||||
if (value.includes('\t') || value.includes('\\t')) {
|
||||
// 处理实际制表符或转义的制表符
|
||||
const parts = value.replace(/\\t/g, '\t').split('\t');
|
||||
// 兼容不同长度的记录格式
|
||||
if (parts.length >= 4) {
|
||||
// 对于标准DNS响应格式:domain\tttl\tIN\ttype\tvalue
|
||||
value = parts[parts.length - 1].trim();
|
||||
} else if (parts.length >= 2) {
|
||||
// 对于其他格式,使用最后一个字段
|
||||
value = parts[parts.length - 1].trim();
|
||||
}
|
||||
}
|
||||
// 对于其他所有字符串类型的值,直接trim
|
||||
else {
|
||||
value = value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
return `${type}: ${value} (ttl=${ttl})`;
|
||||
}).join('\n').trim();
|
||||
} else if (typeof log.Records === 'string') {
|
||||
// 字符串类型的Records - 处理每行缩进
|
||||
records = log.Records.split('\n').map(line => line.trim()).filter(line => line !== '').join('\n');
|
||||
}
|
||||
}
|
||||
// 6. 尝试从log.records字段获取(小写形式)
|
||||
if (!records && log.records) {
|
||||
if (Array.isArray(log.records)) {
|
||||
records = log.records.map(record => {
|
||||
const type = record.type || record.Type || '未知';
|
||||
let value = record.value || record.Value || record.data || record.Data || '未知';
|
||||
const ttl = record.TTL || record.ttl || record.expires || '未知';
|
||||
|
||||
// 增强的记录值提取逻辑:处理多种DNS记录格式
|
||||
if (typeof value === 'string') {
|
||||
// 如果value是完整的DNS记录字符串,提取出实际值
|
||||
if (value.includes('\t') || value.includes('\\t')) {
|
||||
// 处理实际制表符或转义的制表符
|
||||
const parts = value.replace(/\\t/g, '\t').split('\t');
|
||||
// 兼容不同长度的记录格式
|
||||
if (parts.length >= 4) {
|
||||
// 对于标准DNS响应格式:domain\tttl\tIN\ttype\tvalue
|
||||
value = parts[parts.length - 1].trim();
|
||||
} else if (parts.length >= 2) {
|
||||
// 对于其他格式,使用最后一个字段
|
||||
value = parts[parts.length - 1].trim();
|
||||
}
|
||||
}
|
||||
// 对于其他所有字符串类型的值,直接trim
|
||||
else {
|
||||
value = value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
return `${type}: ${value} (ttl=${ttl})`;
|
||||
}).join('\n').trim();
|
||||
} else if (typeof log.records === 'string') {
|
||||
// 字符串类型的records - 处理每行缩进
|
||||
records = log.records.split('\n').map(line => line.trim()).filter(line => line !== '').join('\n');
|
||||
}
|
||||
}
|
||||
// 7. 尝试从Response字段获取(兼容旧格式)
|
||||
if (!records && dnsResponse && dnsResponse !== '无') {
|
||||
// 如果Response是JSON字符串,尝试解析
|
||||
if (dnsResponse.startsWith('[') && dnsResponse.endsWith(']')) {
|
||||
try {
|
||||
const parsedResponse = JSON.parse(dnsResponse);
|
||||
if (Array.isArray(parsedResponse)) {
|
||||
records = parsedResponse.map(item => {
|
||||
const type = item.type || item.Type || '未知';
|
||||
let value = item.value || item.Value || item.data || item.Data || '未知';
|
||||
const ttl = item.TTL || item.ttl || item.expires || '未知';
|
||||
|
||||
// 增强的记录值提取逻辑:处理多种DNS记录格式
|
||||
if (typeof value === 'string') {
|
||||
// 如果value是完整的DNS记录字符串,提取出实际值
|
||||
if (value.includes('\t') || value.includes('\\t')) {
|
||||
// 处理实际制表符或转义的制表符
|
||||
const parts = value.replace(/\\t/g, '\t').split('\t');
|
||||
// 兼容不同长度的记录格式
|
||||
if (parts.length >= 4) {
|
||||
// 对于标准DNS响应格式:domain\tttl\tIN\ttype\tvalue
|
||||
value = parts[parts.length - 1].trim();
|
||||
} else if (parts.length >= 2) {
|
||||
// 对于其他格式,使用最后一个字段
|
||||
value = parts[parts.length - 1].trim();
|
||||
}
|
||||
}
|
||||
// 如果value是JSON字符串,尝试解析
|
||||
else if (value.startsWith('{') && value.endsWith('}')) {
|
||||
try {
|
||||
const parsedValue = JSON.parse(value);
|
||||
value = parsedValue.data || parsedValue.value || value;
|
||||
// 解析后的值也需要trim
|
||||
if (typeof value === 'string') {
|
||||
value = value.trim();
|
||||
}
|
||||
} catch (e) {
|
||||
// 解析失败,保持原值但trim
|
||||
value = value.trim();
|
||||
}
|
||||
}
|
||||
// 对于其他所有字符串类型的值,直接trim
|
||||
else {
|
||||
value = value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
return `${type}: ${value} (ttl=${ttl})`;
|
||||
}).join('\n').trim();
|
||||
}
|
||||
} catch (e) {
|
||||
// 解析失败,直接显示Response内容 - 处理每行缩进
|
||||
records = dnsResponse.split('\n').map(line => line.trim()).filter(line => line !== '').join('\n');
|
||||
}
|
||||
} else {
|
||||
// Response不是JSON数组 - 处理每行缩进
|
||||
records = dnsResponse.split('\n').map(line => line.trim()).filter(line => line !== '').join('\n');
|
||||
}
|
||||
}
|
||||
// 8. 如果还是没有解析记录,显示友好提示
|
||||
if (!records) {
|
||||
records = '无解析记录';
|
||||
}
|
||||
|
||||
return records;
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<div class="text-gray-500">DNS服务器:</div>
|
||||
<div class="text-gray-800">${dnsServer}</div>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<div class="text-gray-500">DNSSEC专用服务器:</div>
|
||||
<div class="text-gray-800">${dnssecServer}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<div class="border-t border-gray-200 my-3"></div>
|
||||
|
||||
<!-- 第2组:响应细节 -->
|
||||
<div class="text-xs">
|
||||
<div class="font-medium text-gray-700 mb-2">响应细节</div>
|
||||
<div class="grid grid-cols-2 gap-y-2 gap-x-4">
|
||||
<div>
|
||||
<div class="text-gray-500">响应时间:</div>
|
||||
<div class="text-gray-800">${responseTime}毫秒</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-gray-500">规则:</div>
|
||||
<div class="text-gray-800">${blockRule}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-gray-500">响应代码:</div>
|
||||
<div class="text-gray-800">无</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-gray-500">缓存状态:</div>
|
||||
<div class="${fromCache ? 'text-primary' : 'text-gray-500'}">
|
||||
${fromCache ? '缓存' : '非缓存'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<div class="border-t border-gray-200 my-3"></div>
|
||||
|
||||
<!-- 第3组:客户端详情 -->
|
||||
<div class="text-xs">
|
||||
<div class="font-medium text-gray-700 mb-2">客户端详情</div>
|
||||
<div>
|
||||
<div class="text-gray-500">IP地址:</div>
|
||||
<div class="text-gray-800">${clientIP} (${location})</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 组装模态框
|
||||
modalContent.appendChild(titleBar);
|
||||
modalContent.appendChild(details);
|
||||
modalContainer.appendChild(modalContent);
|
||||
|
||||
// 添加到页面
|
||||
document.body.appendChild(modalContainer);
|
||||
|
||||
// 点击外部关闭
|
||||
modalContainer.addEventListener('click', function(e) {
|
||||
if (e.target === modalContainer) {
|
||||
document.body.removeChild(modalContainer);
|
||||
}
|
||||
});
|
||||
|
||||
// ESC键关闭
|
||||
document.addEventListener('keydown', function handleEsc(e) {
|
||||
if (e.key === 'Escape') {
|
||||
document.body.removeChild(modalContainer);
|
||||
document.removeEventListener('keydown', handleEsc);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in showLogDetailModal:', error);
|
||||
|
||||
// 显示错误提示
|
||||
const errorModal = document.createElement('div');
|
||||
errorModal.className = 'fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center';
|
||||
errorModal.style.zIndex = '9999';
|
||||
|
||||
const errorContent = document.createElement('div');
|
||||
errorContent.className = 'bg-white rounded-lg shadow-xl p-6 w-full max-w-md';
|
||||
|
||||
errorContent.innerHTML = `
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-xl font-semibold">错误</h3>
|
||||
<button onclick="document.body.removeChild(errorModal)" class="text-gray-500 hover:text-gray-700 focus:outline-none">
|
||||
<i class="fa fa-times text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-red-600">
|
||||
加载日志详情失败: ${error.message}
|
||||
</div>
|
||||
`;
|
||||
|
||||
errorModal.appendChild(errorContent);
|
||||
document.body.appendChild(errorModal);
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭日志详情弹窗
|
||||
function closeLogDetailModal() {
|
||||
const modal = document.getElementById('log-detail-modal');
|
||||
modal.classList.add('hidden');
|
||||
}
|
||||
|
||||
// 初始化日志详情弹窗事件
|
||||
function initLogDetailModal() {
|
||||
// 关闭按钮事件
|
||||
const closeBtn = document.getElementById('close-log-modal-btn');
|
||||
if (closeBtn) {
|
||||
closeBtn.addEventListener('click', closeLogDetailModal);
|
||||
}
|
||||
|
||||
// 点击模态框外部关闭
|
||||
const modal = document.getElementById('log-detail-modal');
|
||||
if (modal) {
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target === modal) {
|
||||
closeLogDetailModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ESC键关闭
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
closeLogDetailModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 定期更新日志统计数据(备用方案)
|
||||
setInterval(() => {
|
||||
// 只有在查询日志页面时才更新
|
||||
|
||||
Reference in New Issue
Block a user