2 Commits

Author SHA1 Message Date
Alex Yang
356310ae75 版本更新 2025-12-24 10:59:29 +08:00
Alex Yang
fe77539da1 增加日志查询页面跟踪器显示 2025-12-24 10:08:35 +08:00
11 changed files with 50976 additions and 8763 deletions

View File

@@ -2,6 +2,21 @@
所有对本项目的显著更改都将记录在此文件中。
## [1.2.0] - 2025-12-24
### 添加
- 在查询日志详情的域名左侧添加DNSSEC状态锁图标和跟踪器状态图标
- 实现跟踪器状态显示匹配tracker/trackers.json数据库
- 添加跟踪器详情浮窗鼠标悬停在眼睛图标上时显示跟踪器名称、类别、URL、来源等信息
- 实现日志页面页码跳转功能(输入框+"前往"按钮)
- 实现日志页面显示数量选择功能(下拉框)
### 修改
- 异步加载跟踪器数据库并缓存,优化性能
- 将日志渲染逻辑改为支持异步操作的for...of循环
- 修复跟踪器浮窗CSS样式语法错误
- 在后端添加/tracker目录静态文件服务路由
## [1.1.4] - 2025-12-21
### 修复

View File

@@ -35,6 +35,9 @@
],
"microsoft.com": [
"4.2.2.1:53"
],
"steam": [
"4.2.2.1:53"
]
},
"noDNSSECDomains": [

View File

@@ -154,6 +154,17 @@ func (s *Server) Start() error {
http.ServeFile(w, r, "./static/login.html")
})
// Tracker目录静态文件服务
trackerFileServer := http.FileServer(http.Dir("./tracker"))
mux.HandleFunc("/tracker/", s.loginRequired(func(w http.ResponseWriter, r *http.Request) {
// 添加Cache-Control头禁用浏览器缓存
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", "Thu, 01 Jan 1970 00:00:00 GMT")
// 使用StripPrefix处理路径
http.StripPrefix("/tracker", trackerFileServer).ServeHTTP(w, r)
}))
// 其他静态文件需要登录
mux.HandleFunc("/", s.loginRequired(func(w http.ResponseWriter, r *http.Request) {
// 添加Cache-Control头禁用浏览器缓存

8670
server.log

File diff suppressed because it is too large Load Diff

View File

@@ -1 +0,0 @@
132708

View File

@@ -1064,3 +1064,78 @@ tr:hover {
display: flex;
gap: 0.5rem;
}
/* 跟踪器状态图标容器 */
.tracker-icon-container {
position: relative;
display: inline-flex;
align-items: center;
cursor: help;
}
/* 跟踪器浮窗样式 */
.tracker-tooltip {
position: absolute;
top: -10px;
left: 100%;
margin-left: 10px;
background-color: white;
border: 1px solid #e2e8f0;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding: 12px;
min-width: 250px;
z-index: 50;
font-size: 14px;
color: #333;
/* 添加箭头 */
}
/* 浮窗箭头 */
.tracker-tooltip::before {
content: '';
position: absolute;
top: 15px;
left: -10px;
width: 0;
height: 0;
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
border-right: 10px solid white;
}
.tracker-tooltip::after {
content: '';
position: absolute;
top: 15px;
left: -11px;
width: 0;
height: 0;
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
border-right: 10px solid #e2e8f0;
z-index: -1;
}
/* 浮窗标题 */
.tracker-tooltip .font-semibold {
font-weight: 600;
color: #2d3748;
margin-bottom: 8px;
display: block;
}
/* 浮窗内容项 */
.tracker-tooltip > div {
margin-bottom: 6px;
}
/* 浮窗链接样式 */
.tracker-tooltip a {
color: #3182ce;
text-decoration: none;
}
.tracker-tooltip a:hover {
text-decoration: underline;
}

View File

@@ -76,7 +76,7 @@
<!-- 底部信息 -->
<div class="p-4 border-t border-gray-200 text-center text-gray-500 text-sm">
<p>DNS服务器 v1.0.0</p>
<p>DNS服务器 v1.2.0</p>
<p class="mt-1" id="uptime">正常运行中</p>
</div>
</aside>
@@ -995,6 +995,24 @@
<div class="text-sm text-gray-500">
显示 <span id="logs-current-page">1</span> / <span id="logs-total-pages">1</span>
</div>
<div class="flex items-center space-x-4">
<div class="flex items-center space-x-2">
<span class="text-sm text-gray-500">每页显示:</span>
<select id="logs-per-page" class="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
<option value="10">10条</option>
<option value="20">20条</option>
<option value="30" selected>30条</option>
<option value="50">50条</option>
<option value="100">100条</option>
</select>
</div>
<div class="flex items-center space-x-2">
<span class="text-sm text-gray-500">页码:</span>
<input type="number" id="logs-page-input" min="1" max="1" value="1" class="w-16 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-center">
<button id="logs-go-page" class="px-4 py-2 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors">
前往
</button>
</div>
<div class="flex space-x-2">
<button id="logs-prev-page" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:border-transparent" disabled>
<i class="fa fa-chevron-left"></i>
@@ -1006,6 +1024,7 @@
</div>
</div>
</div>
</div>
<div id="config-content" class="hidden">
<!-- 系统设置页面内容 -->

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,6 +487,10 @@ function updateLogsTable(logs) {
statusClass = '';
}
// 检查域名是否在跟踪器数据库中
const trackerInfo = await isDomainInTrackerDatabase(log.Domain);
const isTracker = trackerInfo !== null;
// 构建行内容 - 两行显示,时间列显示时间和日期,请求列显示域名和类型状态
// 添加缓存状态显示
const cacheStatusClass = log.FromCache ? 'text-primary' : 'text-gray-500';
@@ -394,6 +499,17 @@ function updateLogsTable(logs) {
// 检查域名是否被拦截
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>
@@ -404,7 +520,14 @@ function updateLogsTable(logs) {
<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="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>
@@ -418,6 +541,23 @@ function updateLogsTable(logs) {
</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';
});
}
}
// 绑定按钮事件
const blockBtn = row.querySelector('.block-btn');
if (blockBtn) {
@@ -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');

View File

@@ -1,52 +0,0 @@
{
"dns": {
"port": 5353,
"upstreamDNS": [
"223.5.5.5:53",
"223.6.6.6:53",
"117.50.10.10:53",
"10.35.10.200:53"
],
"dnssecUpstreamDNS": [
"117.50.10.10:53",
"101.226.4.6:53",
"218.30.118.6:53",
"208.67.220.220:53",
"208.67.222.222:53"
],
"timeout": 5000,
"statsFile": "data/stats.json",
"saveInterval": 300,
"cacheTTL": 30,
"enableDNSSEC": true,
"queryMode": "parallel",
"domainSpecificDNS": {
"amazehome.xyz": ["10.35.10.200:53"]
}
},
"http": {
"port": 8081,
"host": "0.0.0.0",
"enableAPI": true,
"username": "admin",
"password": "admin"
},
"shield": {
"localRulesFile": "data/rules.txt",
"blacklists": [],
"updateInterval": 3600,
"hostsFile": "data/hosts.txt",
"blockMethod": "NXDOMAIN",
"customBlockIP": "",
"statsFile": "./data/shield_stats.json",
"statsSaveInterval": 60,
"remoteRulesCacheDir": "data/remote_rules"
},
"log": {
"file": "logs/dns-server-5353.log",
"level": "debug",
"maxSize": 100,
"maxBackups": 10,
"maxAge": 30
}
}

25333
tracker/trackers.json Normal file

File diff suppressed because it is too large Load Diff

25333
tracker/trackers.json.bak Normal file

File diff suppressed because it is too large Load Diff