增加日志查询页面跟踪器显示

This commit is contained in:
Alex Yang
2025-12-24 10:08:35 +08:00
parent 1f8bd6b97f
commit fe77539da1
11 changed files with 50975 additions and 8762 deletions

View File

@@ -14,10 +14,83 @@ let currentSortDirection = 'desc'; // 默认降序
let ipGeolocationCache = {};
const GEOLOCATION_CACHE_EXPIRY = 24 * 60 * 60 * 1000; // 缓存有效期24小时
// 跟踪器数据库缓存
let trackersDatabase = null;
let trackersLoaded = false;
let trackersLoading = false;
// 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('/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 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 || trackerUrl.hostname.includes(domain)) {
return tracker;
}
} catch (e) {
// 忽略无效URL
}
}
}
}
return null;
}
// 初始化查询日志页面
function initLogsPage() {
console.log('初始化查询日志页面');
@@ -122,6 +195,34 @@ function bindLogsEvents() {
});
}
// 页码跳转
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 => {
@@ -304,7 +405,7 @@ function loadLogs() {
}
// 更新日志表格
function updateLogsTable(logs) {
async function updateLogsTable(logs) {
const tableBody = document.getElementById('logs-table-body');
if (!tableBody) return;
@@ -325,7 +426,7 @@ function updateLogsTable(logs) {
}
// 填充表格
logs.forEach(log => {
for (const log of logs) {
const row = document.createElement('tr');
row.className = 'border-b border-gray-100 hover:bg-gray-50 transition-colors';
@@ -386,37 +487,76 @@ function updateLogsTable(logs) {
statusClass = '';
}
// 构建行内容 - 两行显示,时间列显示时间和日期,请求列显示域名和类型状态
// 添加缓存状态显示
const cacheStatusClass = log.FromCache ? 'text-primary' : 'text-gray-500';
const cacheStatusText = log.FromCache ? '缓存' : '非缓存';
// 检查域名是否在跟踪器数据库中
const trackerInfo = await isDomainInTrackerDatabase(log.Domain);
const isTracker = trackerInfo !== null;
// 检查域名是否被拦截
const isBlocked = log.Result === 'blocked';
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">${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>`
// 构建行内容 - 两行显示,时间列显示时间和日期,请求列显示域名和类型状态
// 添加缓存状态显示
const cacheStatusClass = log.FromCache ? 'text-primary' : 'text-gray-500';
const cacheStatusText = log.FromCache ? '缓存' : '非缓存';
// 检查域名是否被拦截
const isBlocked = log.Result === 'blocked';
// 构建跟踪器浮窗内容
const trackerTooltip = isTracker ? `
<div class="tracker-tooltip absolute z-50 bg-white shadow-lg rounded-md border p-3 min-w-64 text-sm">
<div class="font-semibold mb-1">已知跟踪器</div>
<div class="mb-1">名称: ${trackerInfo.name}</div>
<div class="mb-1">类别: ${trackersDatabase.categories[trackerInfo.categoryId] || '未知'}</div>
${trackerInfo.url ? `<div class="mb-1">URL: <a href="${trackerInfo.url}" target="_blank" class="text-blue-500 hover:underline">${trackerInfo.url}</a></div>` : ''}
${trackerInfo.source ? `<div class="mb-1">源: ${trackerInfo.source}</div>` : ''}
</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}
</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) {
const iconContainer = row.querySelector('.tracker-icon-container');
const tooltip = iconContainer.querySelector('.tracker-tooltip');
if (iconContainer && tooltip) {
tooltip.style.display = 'none';
iconContainer.addEventListener('mouseenter', () => {
tooltip.style.display = 'block';
});
iconContainer.addEventListener('mouseleave', () => {
tooltip.style.display = 'none';
});
}
</td>
`;
}
// 绑定按钮事件
const blockBtn = row.querySelector('.block-btn');
@@ -438,7 +578,7 @@ function updateLogsTable(logs) {
}
tableBody.appendChild(row);
});
}
}
// 更新分页信息
@@ -447,6 +587,13 @@ function updateLogsPagination() {
document.getElementById('logs-current-page').textContent = currentPage;
document.getElementById('logs-total-pages').textContent = totalPages;
// 更新页码输入框
const pageInput = document.getElementById('logs-page-input');
if (pageInput) {
pageInput.max = totalPages;
pageInput.value = currentPage;
}
// 更新按钮状态
const prevBtn = document.getElementById('logs-prev-page');
const nextBtn = document.getElementById('logs-next-page');