实现日志详情域名信息显示

This commit is contained in:
Alex Yang
2025-12-30 10:41:09 +08:00
parent 43f0133886
commit 97413e88f0
13 changed files with 51681 additions and 1933 deletions

View File

@@ -19,6 +19,11 @@ let trackersDatabase = null;
let trackersLoaded = false;
let trackersLoading = false;
// 域名信息数据库缓存
let domainInfoDatabase = null;
let domainInfoLoaded = false;
let domainInfoLoading = false;
// WebSocket连接和重连计时器
let logsWsConnection = null;
let logsWsReconnectTimer = null;
@@ -37,7 +42,7 @@ async function loadTrackersDatabase() {
trackersLoading = true;
try {
const response = await fetch('/tracker/trackers.json');
const response = await fetch('domain-info/tracker/trackers.json');
if (!response.ok) {
console.error('加载跟踪器数据库失败:', response.statusText);
trackersDatabase = { trackers: {} };
@@ -56,6 +61,53 @@ async function loadTrackersDatabase() {
}
}
// 加载域名信息数据库
async function loadDomainInfoDatabase() {
console.log('开始加载域名信息数据库');
if (domainInfoLoaded) {
console.log('域名信息数据库已加载,直接返回');
return domainInfoDatabase;
}
if (domainInfoLoading) {
console.log('域名信息数据库正在加载中,等待完成');
// 等待正在进行的加载完成
while (domainInfoLoading) {
await new Promise(resolve => setTimeout(resolve, 100));
}
return domainInfoDatabase;
}
domainInfoLoading = true;
try {
console.log('发起请求获取域名信息数据库');
const response = await fetch('domain-info/domains/domain-info.json');
if (!response.ok) {
console.error('加载域名信息数据库失败HTTP状态:', response.status, response.statusText);
console.error('请求URL:', response.url);
domainInfoDatabase = { domains: {}, categories: {} };
return domainInfoDatabase;
}
console.log('域名信息数据库请求成功开始解析JSON');
domainInfoDatabase = await response.json();
console.log('域名信息数据库解析成功,包含', Object.keys(domainInfoDatabase.domains || {}).length, '个公司');
domainInfoLoaded = true;
return domainInfoDatabase;
} catch (error) {
console.error('加载域名信息数据库失败,错误信息:', error.message);
console.error('错误堆栈:', error.stack);
domainInfoDatabase = { domains: {}, categories: {} };
return domainInfoDatabase;
} finally {
domainInfoLoading = false;
console.log('域名信息数据库加载完成');
}
}
// 检查域名是否在跟踪器数据库中,并返回跟踪器信息
async function isDomainInTrackerDatabase(domain) {
if (!trackersDatabase || !trackersLoaded) {
@@ -78,7 +130,7 @@ async function isDomainInTrackerDatabase(domain) {
if (tracker && tracker.url) {
try {
const trackerUrl = new URL(tracker.url);
if (trackerUrl.hostname === domain || trackerUrl.hostname.includes(domain)) {
if (trackerUrl.hostname === domain) {
return tracker;
}
} catch (e) {
@@ -91,6 +143,164 @@ async function isDomainInTrackerDatabase(domain) {
return null;
}
// 根据域名查找对应的网站信息
async function getDomainInfo(domain) {
console.log('开始查找域名信息,域名:', domain);
if (!domainInfoDatabase || !domainInfoLoaded) {
console.log('域名信息数据库未加载调用loadDomainInfoDatabase');
await loadDomainInfoDatabase();
}
if (!domainInfoDatabase || !domainInfoDatabase.domains) {
console.error('域名信息数据库无效或为空');
return null;
}
// 规范化域名,移除可能的端口号
const normalizedDomain = domain.replace(/:\d+$/, '').toLowerCase();
console.log('规范化后的域名:', normalizedDomain);
// 遍历所有公司
console.log('开始遍历公司,总公司数:', Object.keys(domainInfoDatabase.domains).length);
for (const companyKey in domainInfoDatabase.domains) {
if (domainInfoDatabase.domains.hasOwnProperty(companyKey)) {
console.log('检查公司:', companyKey);
const companyData = domainInfoDatabase.domains[companyKey];
const companyName = companyData.company || companyKey;
// 遍历公司下的所有网站
for (const websiteKey in companyData) {
if (companyData.hasOwnProperty(websiteKey) && websiteKey !== 'company') {
console.log(' 检查网站:', websiteKey);
const website = companyData[websiteKey];
// 检查域名是否匹配网站的URL
if (website.url) {
// 处理字符串类型的URL
if (typeof website.url === 'string') {
console.log(' 检查字符串URL:', website.url);
if (isDomainMatch(website.url, normalizedDomain)) {
console.log(' 匹配成功,返回网站信息');
return {
name: website.name,
icon: website.icon,
categoryId: website.categoryId,
categoryName: domainInfoDatabase.categories[website.categoryId] || '未知',
company: companyName
};
}
}
// 处理对象类型的URL
else if (typeof website.url === 'object') {
console.log(' 检查对象类型URL包含', Object.keys(website.url).length, '个URL');
for (const urlKey in website.url) {
if (website.url.hasOwnProperty(urlKey)) {
const urlValue = website.url[urlKey];
console.log(' 检查URL', urlKey, ':', urlValue);
if (isDomainMatch(urlValue, normalizedDomain)) {
console.log(' 匹配成功,返回网站信息');
return {
name: website.name,
icon: website.icon,
categoryId: website.categoryId,
categoryName: domainInfoDatabase.categories[website.categoryId] || '未知',
company: companyName
};
}
}
}
}
} else {
console.log(' 网站没有URL属性');
}
}
}
}
}
console.log('未找到匹配的域名信息');
return null;
}
// 检查域名是否匹配
function isDomainMatch(urlValue, targetDomain) {
console.log(' 开始匹配URL:', urlValue, '目标域名:', targetDomain);
try {
// 尝试将URL值解析为完整URL
console.log(' 尝试解析URL为完整URL');
const url = new URL(urlValue);
const hostname = url.hostname.toLowerCase();
console.log(' 解析成功,主机名:', hostname);
// 只匹配完整域名,不进行主域名匹配
// 这是为了避免同一个公司下的不同网站(如微信和腾讯视频)因为主域名相同而错误匹配
if (hostname === targetDomain) {
console.log(' 完整域名匹配成功');
return true;
} else {
console.log(' 完整域名不匹配');
return false;
}
} catch (e) {
console.log(' 解析URL失败将其视为纯域名处理错误信息:', e.message);
// 如果是纯域名而不是完整URL
const urlDomain = urlValue.toLowerCase();
console.log(' 处理为纯域名:', urlDomain);
// 只匹配完整域名,不进行主域名匹配
if (urlDomain === targetDomain) {
console.log(' 完整域名匹配成功');
return true;
} else {
console.log(' 完整域名不匹配');
return false;
}
}
}
// 提取主域名
function extractPrimaryDomain(domain) {
console.log(' 开始提取主域名,原始域名:', domain);
const parts = domain.split('.');
console.log(' 域名分割为:', parts);
if (parts.length <= 2) {
console.log(' 域名长度小于等于2直接返回:', domain);
return domain;
}
// 处理常见的三级域名
const commonSubdomains = ['www', 'mail', 'news', 'map', 'image', 'video', 'cdn', 'api', 'blog', 'shop', 'cloud', 'docs', 'help', 'support', 'dev', 'test', 'staging'];
console.log(' 检查是否为常见三级域名');
if (commonSubdomains.includes(parts[0])) {
const result = parts.slice(1).join('.');
console.log(' 是常见三级域名,返回:', result);
return result;
}
// 处理特殊情况如co.uk, co.jp等
const countryTLDs = ['co.uk', 'co.jp', 'co.kr', 'co.in', 'co.ca', 'co.au', 'co.nz', 'co.th', 'co.sg', 'co.my', 'co.id', 'co.za', 'com.cn', 'org.cn', 'net.cn', 'gov.cn', 'edu.cn'];
console.log(' 检查是否为特殊国家TLD');
for (const tld of countryTLDs) {
if (domain.endsWith('.' + tld)) {
const mainParts = domain.split('.');
const result = mainParts.slice(-tld.split('.').length - 1).join('.');
console.log(' 是特殊国家TLD返回:', result);
return result;
}
}
// 默认情况:返回最后两个部分
const result = parts.slice(-2).join('.');
console.log(' 默认情况,返回最后两个部分:', result);
return result;
}
// 初始化查询日志页面
function initLogsPage() {
console.log('初始化查询日志页面');
@@ -1115,6 +1325,9 @@ async function showLogDetailModal(log) {
const trackerInfo = await isDomainInTrackerDatabase(log.domain);
const isTracker = trackerInfo !== null;
// 获取域名信息
const domainInfo = await getDomainInfo(domain);
// 格式化DNS解析记录
const dnsRecords = formatDNSRecords(log, result);
@@ -1196,6 +1409,31 @@ async function showLogDetailModal(log) {
</div>
`;
// 域名信息
const domainInfoDiv = document.createElement('div');
domainInfoDiv.className = 'col-span-1 md:col-span-2 space-y-1';
domainInfoDiv.innerHTML = `
<div class="text-xs text-gray-500">域名信息</div>
<div class="text-sm font-medium text-gray-900 p-3 bg-gray-50 rounded-md border border-gray-200">
${domainInfo ? `
<div class="flex items-center mb-2">
${domainInfo.icon ? `<img src="${domainInfo.icon}" alt="${domainInfo.name}" class="w-6 h-6 mr-2 rounded-sm" onerror="this.style.display='none'" />` : ''}
<span class="text-base font-semibold">${domainInfo.name || '未知'}</span>
</div>
<div class="ml-8 mt-1">
<div class="flex items-center mb-1">
<span class="text-gray-500 w-16">类别:</span>
<span>${domainInfo.categoryName || '未知'}</span>
</div>
<div class="flex items-center">
<span class="text-gray-500 w-16">所属公司:</span>
<span>${domainInfo.company || '未知'}</span>
</div>
</div>
` : '无'}
</div>
`;
// 跟踪器信息
const trackerDiv = document.createElement('div');
trackerDiv.className = 'col-span-1 md:col-span-2 space-y-1';
@@ -1236,6 +1474,7 @@ async function showLogDetailModal(log) {
`;
basicInfoGrid.appendChild(dnsFeatures);
basicInfoGrid.appendChild(domainInfoDiv);
basicInfoGrid.appendChild(trackerDiv);
basicInfoGrid.appendChild(recordsDiv);
basicInfoGrid.appendChild(dnsServerDiv);
@@ -1270,8 +1509,8 @@ async function showLogDetailModal(log) {
</div>
`;
// 只有被屏蔽或者有自定义规则时才显示规则信息
if (result === 'blocked' || (blockRule && blockRule !== '无' && blockRule !== '-')) {
// 只有被屏蔽时才显示规则信息
if (result === 'blocked') {
responseDetailsHTML += `
<div class="space-y-1">
<div class="text-xs text-gray-500">规则</div>