Compare commits
2 Commits
dnssever1.
...
Qimeng-DNS
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
356310ae75 | ||
|
|
fe77539da1 |
15
CHANGELOG.md
15
CHANGELOG.md
@@ -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
|
||||
|
||||
### 修复
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
],
|
||||
"microsoft.com": [
|
||||
"4.2.2.1:53"
|
||||
],
|
||||
"steam": [
|
||||
"4.2.2.1:53"
|
||||
]
|
||||
},
|
||||
"noDNSSECDomains": [
|
||||
|
||||
@@ -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
8670
server.log
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
132708
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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">
|
||||
<!-- 系统设置页面内容 -->
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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
25333
tracker/trackers.json
Normal file
File diff suppressed because it is too large
Load Diff
25333
tracker/trackers.json.bak
Normal file
25333
tracker/trackers.json.bak
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user