修复主题

This commit is contained in:
Alex Yang
2026-01-31 21:56:12 +08:00
parent 70c5cfdf50
commit 050aa421b1
21 changed files with 177022 additions and 114220 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +0,0 @@
CGO_ENABLED=1 \
GOOS=windows \
GOARCH=amd64 \
CC=gcc \
go build -o dns-server.exe main.go

View File

@@ -150,8 +150,8 @@
"statsSaveInterval": 60
},
"gfwList": {
"ip": "10.35.10.200",
"content": "/root/dns/data/gfwlist.txt",
"ip": "",
"content": "",
"enabled": false
},
"log": {

18
data/rules.txt Normal file
View File

@@ -0,0 +1,18 @@
||events-sandbox.data.msn.cn
||c.msn.cn
||ad.*
||clarity.microsoft.com
||reke.at.sohu.com
||e.so.com
||admin.zlhj.top
||vbng.at.sohu.com
||lb.e.so.com
||localhost.msn.cn
@@||www.csjplatform.com
@@||issuepcdn.baidupcs.com
@@||szminorshort.weixin.qq.com
@@||apd-pcdnwxlogin.teg.tencent-cloud.net
@@||eastday.com
@@||antpcdn.com
@@||mpcdn.weixin.qq.com
@@||api.tw06.xlmc.sec.miui.com

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
{
"blockedDomainsCount": {},
"resolvedDomainsCount": {},
"lastSaved": "2026-01-29T20:30:29.526984259+08:00"
"lastSaved": "2026-01-29T20:56:33.714598425+08:00"
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1362,13 +1362,6 @@ tr:hover {
/* 添加箭头 */
}
/* 显示跟踪器浮窗 */
.tracker-tooltip.visible {
display: block;
opacity: 1;
visibility: visible;
}
/* 浮窗箭头 */
.tracker-tooltip::before {
content: '';
@@ -1395,22 +1388,6 @@ tr:hover {
z-index: -1;
}
/* 深色主题下的跟踪器浮窗样式 */
.dark .tracker-tooltip {
background-color: var(--bg-secondary);
border: 1px solid var(--border-color);
color: var(--text-primary);
box-shadow: var(--shadow);
}
.dark .tracker-tooltip::before {
border-right-color: var(--bg-secondary);
}
.dark .tracker-tooltip::after {
border-right-color: var(--border-color);
}
/* 浮窗标题 */
.tracker-tooltip .font-semibold {
font-weight: 600;
@@ -1418,11 +1395,6 @@ tr:hover {
margin-bottom: 8px;
}
/* 深色主题下的浮窗标题 */
.dark .tracker-tooltip .font-semibold {
color: var(--text-primary);
}
/* 搜索框样式优化 */
#logs-search {
/* 确保搜索框在所有设备上都有合适的宽度 */
@@ -1455,6 +1427,78 @@ tr:hover {
text-decoration: underline;
}
/* 平滑过渡动画类 */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes slideOut {
from {
opacity: 1;
transform: translateY(0) scale(1);
}
to {
opacity: 0;
transform: translateY(20px) scale(0.95);
}
}
.animate-fade-in {
animation: fadeIn 0.3s ease-out forwards;
}
.animate-slide-in {
animation: slideIn 0.3s ease-out forwards;
}
.animate-fade-out {
animation: fadeOut 0.3s ease-in forwards;
}
.animate-slide-out {
animation: slideOut 0.3s ease-in forwards;
}
/* 跟踪器浮窗显示/隐藏的平滑过渡 */
.tracker-tooltip.visible {
display: block;
opacity: 1;
visibility: visible;
transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out;
}
.tracker-tooltip {
display: block;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out;
}
/* 滚动条样式优化 */
/* 基础滚动条样式 */
::-webkit-scrollbar {

View File

@@ -14,8 +14,6 @@
<script src="js/vendor/chart.umd.min.js" onerror="this.onerror=null;this.src='js/chart.umd.min.js';"></script>
<!-- Tailwind 配置 -->
<script src="js/vendor/tailwind.js"></script>
<!-- 内存管理模块 -->
<script src="js/memory-manager.js"></script>
<!-- 自定义工具类 -->
<style type="text/tailwindcss" src="css/index.css"></style>
</head>
@@ -926,11 +924,8 @@
<!-- 日志搜索和过滤 -->
<div class="bg-white rounded-lg p-4 sm:p-6 card-shadow">
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-3">
<div class="sm:col-span-2 relative">
<input type="text" id="logs-search" placeholder="搜索域名或客户端IP" class="w-full pl-3 pr-10 sm:pl-4 sm:pr-12 py-2 sm:py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-sm">
<button id="logs-clear-search" class="absolute right-2 sm:right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600 focus:outline-none hidden">
<i class="fa fa-times-circle"></i>
</button>
<div class="sm:col-span-2">
<input type="text" id="logs-search" placeholder="搜索域名或客户端IP" class="w-full px-3 sm:px-4 py-2 sm:py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-sm">
</div>
<div class="w-full">
<select id="logs-result-filter" class="w-full px-3 sm:px-4 py-2 sm:py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-sm">
@@ -1031,7 +1026,7 @@
<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-bottom" class="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
<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>
@@ -1061,7 +1056,7 @@
<div id="gfwlist-content" class="hidden">
<div class="bg-white rounded-lg p-6 card-shadow">
<h3 class="text-lg font-semibold mb-6">GFWList管理 <span class="inline-block px-2 py-0.5 text-xs font-medium bg-yellow-100 text-yellow-800 rounded-full">Dev</span></h3>
<h3 class="text-lg font-semibold mb-6">GFWList管理</h3>
<form id="gfwlist-form">
<div class="mb-8">

0
static/js/about.js Normal file
View File

View File

@@ -115,7 +115,7 @@ function fetchHostsCount() {
// 空实现,保留函数声明以避免引用错误
}
// 通用API请求函数 - 添加错误处理重试机制和缓存
// 通用API请求函数 - 添加错误处理重试机制
function apiRequest(endpoint, method = 'GET', data = null, maxRetries = 3) {
const headers = {
'Content-Type': 'application/json'
@@ -129,7 +129,6 @@ function apiRequest(endpoint, method = 'GET', data = null, maxRetries = 3) {
// 处理请求URL和参数
let url = `${API_BASE_URL}${endpoint}`;
let cacheKey = null;
if (data) {
if (method === 'GET') {
@@ -139,26 +138,10 @@ function apiRequest(endpoint, method = 'GET', data = null, maxRetries = 3) {
params.append(key, data[key]);
});
url += `?${params.toString()}`;
// 生成缓存键
cacheKey = `${endpoint}_${params.toString()}`;
} else if (method === 'POST' || method === 'PUT' || method === 'DELETE') {
// 为其他方法设置body
config.body = JSON.stringify(data);
}
} else {
// 无参数的GET请求
if (method === 'GET') {
cacheKey = endpoint;
}
}
// 尝试从缓存获取响应仅GET请求
if (method === 'GET' && cacheKey && window.memoryManager) {
const cachedResponse = memoryManager.getCacheItem('apiResponses', cacheKey);
if (cachedResponse) {
console.log('从缓存获取API响应:', cacheKey);
return Promise.resolve(cachedResponse);
}
}
let retries = 0;
@@ -176,14 +159,7 @@ function apiRequest(endpoint, method = 'GET', data = null, maxRetries = 3) {
// 使用.text()先获取响应文本处理可能的JSON解析错误
return response.text().then(text => {
try {
const responseData = JSON.parse(text);
// 缓存GET请求的响应
if (method === 'GET' && cacheKey && window.memoryManager) {
memoryManager.addCacheItem('apiResponses', cacheKey, responseData);
}
return responseData;
return JSON.parse(text);
} catch (e) {
console.error('JSON解析错误:', e, '响应文本:', text);
// 针对ERR_INCOMPLETE_CHUNKED_ENCODING错误进行重试

View File

@@ -366,13 +366,31 @@ function showNotification(message, type = 'info') {
// 初始化GFWList管理页面
function initGFWListPage() {
// 加载配置但不显示,因为功能未开发
// loadGFWListConfig();
// 显示"正在开发中"的提示
showNotification('GFWList管理功能正在开发中敬请期待', 'info');
// 禁用所有按钮和输入框
disableGFWListElements();
setupGFWListEventListeners();
// 提示功能待开发
setTimeout(() => {
showNotification('GFWList管理功能正在开发中敬请期待', 'info');
}, 500);
}
// 禁用GFWList页面的所有元素
function disableGFWListElements() {
const gfwlistContent = document.getElementById('gfwlist-content');
if (!gfwlistContent) return;
// 禁用所有按钮
const buttons = gfwlistContent.querySelectorAll('button');
buttons.forEach(button => {
button.disabled = true;
button.classList.add('cursor-not-allowed', 'opacity-50');
});
// 禁用所有输入框
const inputs = gfwlistContent.querySelectorAll('input');
inputs.forEach(input => {
input.disabled = true;
input.classList.add('opacity-50');
});
}
// 加载GFWList配置
@@ -420,8 +438,21 @@ function populateGFWListForm(config) {
// 保存GFWList配置
async function handleSaveGFWListConfig() {
// 提示功能待开发
showNotification('GFWList管理功能正在开发中敬请期待', 'info');
const formData = collectGFWListFormData();
if (!formData) return;
try {
const result = await api.saveConfig(formData);
if (result && result.error) {
showErrorMessage('保存配置失败: ' + result.error);
return;
}
showSuccessMessage('配置保存成功');
} catch (error) {
showErrorMessage('保存配置失败: ' + (error.message || '未知错误'));
}
}
// 收集GFWList表单数据
@@ -506,16 +537,24 @@ async function handleRestartGFWListService() {
function setupGFWListEventListeners() {
const saveBtn = getElement('gfwlist-save-btn');
if (saveBtn) {
saveBtn.addEventListener('click', handleSaveGFWListConfig);
saveBtn.addEventListener('click', function() {
showNotification('GFWList管理功能正在开发中敬请期待', 'info');
});
}
// 为所有按钮式开关添加点击事件监听器
const toggleBtns = document.querySelectorAll('.toggle-btn');
toggleBtns.forEach(btn => {
btn.addEventListener('click', function() {
// 提示功能待开发
showNotification('GFWList管理功能正在开发中敬请期待', 'info');
// 不执行实际操作
showNotification('GFWList管理功能正在开发中敬请期待', 'info');
});
});
// 为所有输入框添加点击事件监听器
const inputs = document.querySelectorAll('#gfwlist-content input');
inputs.forEach(input => {
input.addEventListener('click', function() {
showNotification('GFWList管理功能正在开发中敬请期待', 'info');
});
});
}

View File

@@ -18,38 +18,6 @@ window.dashboardHistoryData = window.dashboardHistoryData || {
prevActiveIPs: null,
prevTopQueryTypeCount: null
};
// 图表实例管理
function cleanupChartInstances() {
// 销毁现有图表实例
if (ratioChart) {
ratioChart.destroy();
ratioChart = null;
}
if (dnsRequestsChart) {
dnsRequestsChart.destroy();
dnsRequestsChart = null;
}
if (detailedDnsRequestsChart) {
detailedDnsRequestsChart.destroy();
detailedDnsRequestsChart = null;
}
if (queryTypeChart) {
queryTypeChart.destroy();
queryTypeChart = null;
}
// 销毁统计卡片图表实例
for (const key in statCardCharts) {
if (statCardCharts[key]) {
statCardCharts[key].destroy();
delete statCardCharts[key];
}
}
// 清空统计卡片历史数据
statCardHistoryData = {};
}
// 节流相关变量
let lastProcessedTime = 0;
const PROCESS_THROTTLE_INTERVAL = 1000; // 1秒节流间隔
@@ -66,10 +34,6 @@ let errorQueries = 0;
// 初始化仪表盘
async function initDashboard() {
try {
// 初始化内存管理器
if (window.memoryManager && typeof window.memoryManager.init === 'function') {
window.memoryManager.init();
}
// 优先加载初始数据,确保页面显示最新信息
@@ -99,21 +63,6 @@ function connectWebSocket() {
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${wsProtocol}//${window.location.host}/ws/stats`;
// 确保先关闭现有连接
if (dashboardWsConnection) {
try {
dashboardWsConnection.close();
} catch (e) {
console.error('关闭现有WebSocket连接失败:', e);
}
dashboardWsConnection = null;
}
// 清除现有重连计时器
if (dashboardWsReconnectTimer) {
clearTimeout(dashboardWsReconnectTimer);
dashboardWsReconnectTimer = null;
}
// 创建WebSocket连接
dashboardWsConnection = new WebSocket(wsUrl);
@@ -121,6 +70,12 @@ function connectWebSocket() {
// 连接打开事件
dashboardWsConnection.onopen = function() {
showNotification('数据更新成功', 'success');
// 清除重连计时器
if (dashboardWsReconnectTimer) {
clearTimeout(dashboardWsReconnectTimer);
dashboardWsReconnectTimer = null;
}
};
// 接收消息事件
@@ -453,11 +408,7 @@ function fallbackToIntervalRefresh() {
function cleanupResources() {
// 清除WebSocket连接
if (dashboardWsConnection) {
try {
dashboardWsConnection.close();
} catch (e) {
console.error('关闭WebSocket连接失败:', e);
}
dashboardWsConnection.close();
dashboardWsConnection = null;
}
@@ -474,392 +425,36 @@ function cleanupResources() {
}
// 清除图表实例,释放内存
cleanupChartInstances();
if (ratioChart) {
ratioChart.destroy();
ratioChart = null;
}
if (dnsRequestsChart) {
dnsRequestsChart.destroy();
dnsRequestsChart = null;
}
if (detailedDnsRequestsChart) {
detailedDnsRequestsChart.destroy();
detailedDnsRequestsChart = null;
}
if (queryTypeChart) {
queryTypeChart.destroy();
queryTypeChart = null;
}
// 清除统计卡片图表实例
for (const key in statCardCharts) {
if (statCardCharts[key]) {
statCardCharts[key].destroy();
delete statCardCharts[key];
}
}
// 清除事件监听器
window.removeEventListener('beforeunload', cleanupResources);
}
// 加载仪表盘数据
async function loadDashboardData() {
console.log('开始加载仪表盘数据');
try {
// 并行获取所有数据,提高加载效率
const [stats, queryTypeStatsResult, topBlockedDomainsResult, recentBlockedDomainsResult, topClientsResult] = await Promise.all([
// 获取基本统计数据
api.getStats().catch(error => {
console.error('获取基本统计数据失败:', error);
return null;
}),
// 获取查询类型统计数据
api.getQueryTypeStats().catch(() => null),
// 获取TOP被屏蔽域名
api.getTopBlockedDomains().catch(() => null),
// 获取最近屏蔽域名
api.getRecentBlockedDomains().catch(() => null),
// 获取TOP客户端
api.getTopClients().catch(() => null)
]);
// 检查内存使用情况
if (window.memoryManager && typeof window.memoryManager.checkMemoryUsage === 'function') {
window.memoryManager.checkMemoryUsage();
}
// 确保stats是有效的对象
if (!stats || typeof stats !== 'object') {
console.error('无效的统计数据:', stats);
return;
}
console.log('统计数据:', stats);
// 处理查询类型统计数据
let queryTypeStats = null;
if (queryTypeStatsResult) {
console.log('查询类型统计数据:', queryTypeStatsResult);
queryTypeStats = queryTypeStatsResult;
} else if (stats.dns && stats.dns.QueryTypes) {
queryTypeStats = Object.entries(stats.dns.QueryTypes).map(([type, count]) => ({
type,
count
}));
console.log('从stats中提取的查询类型统计:', queryTypeStats);
}
// 处理TOP被屏蔽域名
let topBlockedDomains = [];
if (topBlockedDomainsResult && Array.isArray(topBlockedDomainsResult)) {
topBlockedDomains = topBlockedDomainsResult;
console.log('TOP被屏蔽域名:', topBlockedDomains);
} else {
topBlockedDomains = [
{ domain: 'example-blocked.com', count: 15, lastSeen: new Date().toISOString() },
{ domain: 'ads.example.org', count: 12, lastSeen: new Date().toISOString() },
{ domain: 'tracking.example.net', count: 8, lastSeen: new Date().toISOString() }
];
}
// 处理最近屏蔽域名
let recentBlockedDomains = [];
if (recentBlockedDomainsResult && Array.isArray(recentBlockedDomainsResult)) {
recentBlockedDomains = recentBlockedDomainsResult;
console.log('最近屏蔽域名:', recentBlockedDomains);
} else {
recentBlockedDomains = [
{ domain: '---.---.---', ip: '---.---.---.---', timestamp: new Date().toISOString() },
{ domain: '---.---.---', ip: '---.---.---.---', timestamp: new Date().toISOString() }
];
}
// 处理TOP客户端
let topClients = [];
if (topClientsResult && Array.isArray(topClientsResult)) {
topClients = topClientsResult;
console.log('TOP客户端:', topClients);
} else {
topClients = [
{ ip: '192.168.1.100', count: 100 },
{ ip: '192.168.1.101', count: 80 },
{ ip: '192.168.1.102', count: 60 }
];
}
// 处理TOP域名
let topDomains = [];
try {
const topDomainsResult = await api.getTopDomains().catch(() => null);
if (topDomainsResult && Array.isArray(topDomainsResult)) {
topDomains = topDomainsResult;
console.log('TOP域名:', topDomains);
} else {
topDomains = [
{ domain: 'example.com', count: 50 },
{ domain: 'google.com', count: 45 },
{ domain: 'facebook.com', count: 40 }
];
}
} catch (error) {
console.error('获取TOP域名失败:', error);
topDomains = [
{ domain: 'example.com', count: 50 },
{ domain: 'google.com', count: 45 },
{ domain: 'facebook.com', count: 40 }
];
}
// 更新主页面的统计卡片数据
updateStatsCards(stats);
// 更新TOP客户端表格
updateTopClientsTable(topClients);
// 更新TOP域名表格
updateTopDomainsTable(topDomains);
// 更新TOP被屏蔽域名表格
if (typeof updateTopBlockedDomainsTable === 'function') {
updateTopBlockedDomainsTable(topBlockedDomains);
}
// 更新最近屏蔽域名表格
if (typeof updateRecentBlockedDomainsTable === 'function') {
updateRecentBlockedDomainsTable(recentBlockedDomains);
}
// 更新图表
updateCharts(stats, queryTypeStats);
// 初始化或更新查询类型统计饼图
if (queryTypeStats) {
drawQueryTypeChart(queryTypeStats);
}
// 更新查询类型统计信息
if (document.getElementById('top-query-type')) {
const topQueryTypeElement = document.getElementById('top-query-type');
const topQueryTypeCountElement = document.getElementById('top-query-type-count');
// 从stats中获取查询类型统计数据
if (stats.dns && stats.dns.QueryTypes) {
const queryTypes = stats.dns.QueryTypes;
// 找出数量最多的查询类型
let maxCount = 0;
let topType = 'A';
for (const [type, count] of Object.entries(queryTypes)) {
const numCount = Number(count) || 0;
if (numCount > maxCount) {
maxCount = numCount;
topType = type;
}
}
// 更新DOM
if (topQueryTypeElement) {
topQueryTypeElement.textContent = topType;
}
if (topQueryTypeCountElement) {
topQueryTypeCountElement.textContent = formatNumber(maxCount);
}
// 保存到历史数据,用于计算趋势
window.dashboardHistoryData.prevTopQueryTypeCount = maxCount;
}
}
// 更新活跃IP信息
if (document.getElementById('active-ips')) {
const activeIPsElement = document.getElementById('active-ips');
// 从stats中获取活跃IP数
let activeIPs = 0;
if (stats.activeIPs !== undefined) {
activeIPs = Number(stats.activeIPs) || 0;
} else if (stats.dns && stats.dns.ActiveIPs !== undefined) {
activeIPs = Number(stats.dns.ActiveIPs) || 0;
} else if (stats.dns && stats.dns.SourceIPs) {
activeIPs = Object.keys(stats.dns.SourceIPs).length;
}
// 更新DOM
if (activeIPsElement) {
activeIPsElement.textContent = formatNumber(activeIPs);
}
// 保存到历史数据,用于计算趋势
window.dashboardHistoryData.prevActiveIPs = activeIPs;
}
// 更新平均响应时间
if (document.getElementById('avg-response-time')) {
// 直接使用API返回的平均响应时间
let responseTime = 0;
if (stats.avgResponseTime !== undefined) {
responseTime = Number(stats.avgResponseTime) || 0;
} else if (stats.dns && stats.dns.AvgResponseTime !== undefined) {
responseTime = Number(stats.dns.AvgResponseTime) || 0;
}
const responseTimeElement = document.getElementById('avg-response-time');
if (responseTimeElement) {
responseTimeElement.textContent = responseTime.toFixed(2) + 'ms';
}
// 保存到历史数据,用于计算趋势
window.dashboardHistoryData.prevResponseTime = responseTime;
}
} catch (error) {
console.error('加载仪表盘数据失败:', error);
// 显示错误通知
if (typeof showNotification === 'function') {
showNotification('加载仪表盘数据失败: ' + error.message, 'error');
}
}
}
// 绘制查询类型统计饼图
function drawQueryTypeChart(queryTypeStats) {
const ctx = document.getElementById('query-type-chart');
if (!ctx) return;
// 销毁现有图表
if (queryTypeChart) {
queryTypeChart.destroy();
queryTypeChart = null;
}
// 处理数据
const labels = queryTypeStats.map(item => item.type);
const data = queryTypeStats.map(item => Number(item.count) || 0);
// 生成颜色
const backgroundColors = [
'#3b82f6', // 蓝色
'#ef4444', // 红色
'#10b981', // 绿色
'#f59e0b', // 橙色
'#8b5cf6', // 紫色
'#ec4899', // 粉色
'#6366f1', // 靛蓝色
'#14b8a6', // 青色
'#f97316', // 橙红色
'#84cc16' // 黄绿
];
// 生成悬停时的深色效果
const hoverBackgroundColor = backgroundColors.map(color => {
const hex = color.replace('#', '');
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
return `rgb(${Math.max(0, r - 20)}, ${Math.max(0, g - 20)}, ${Math.max(0, b - 20)})`;
});
// 检查是否为深色模式
const isDarkMode = document.documentElement.classList.contains('dark');
const legendTextColor = isDarkMode ? '#e2e8f0' : '#4B5563';
// 创建新图表
queryTypeChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: labels,
datasets: [{
data: data,
backgroundColor: backgroundColors,
borderWidth: 0, // 移除边框宽度
hoverOffset: 15, // 增加悬停偏移效果,增强交互体验
hoverBorderWidth: 0, // 移除悬停时的边框宽度
hoverBackgroundColor: hoverBackgroundColor, // 悬停时的深色效果
borderRadius: 10, // 添加圆角效果,增强现代感
borderSkipped: false // 显示所有边框
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
// 简化动画,提高性能
animation: {
duration: 300, // 缩短动画时间
easing: 'easeOutQuart', // 简化缓动函数
animateRotate: true, // 仅保留旋转动画
animateScale: false // 禁用缩放动画
},
plugins: {
legend: {
position: 'bottom',
align: 'center',
labels: {
boxWidth: 12, // 调整图例框的宽度
font: {
size: 11, // 调整字体大小
family: 'Inter, system-ui, sans-serif', // 使用现代字体
weight: 500 // 字体粗细
},
padding: 12, // 调整内边距
lineHeight: 1.5, // 调整行高
usePointStyle: true, // 使用点样式代替方形图例,节省空间
pointStyle: 'circle', // 使用圆形点样式
color: legendTextColor, // 根据主题设置图例文本颜色
// 启用图例点击交互
onClick: function(event, legendItem, legend) {
// 切换对应数据的显示
const index = legendItem.index;
const ci = legend.chart;
ci.toggleDataVisibility(index);
ci.update();
}
}
},
tooltip: {
enabled: true,
backgroundColor: 'rgba(17, 24, 39, 0.9)', // 深背景,增强可读性
padding: 12, // 增加内边距
titleFont: {
size: 13, // 标题字体大小
family: 'Inter, system-ui, sans-serif',
weight: 600
},
bodyFont: {
size: 12, // 正文字体大小
family: 'Inter, system-ui, sans-serif'
},
bodySpacing: 6, // 正文行间距
displayColors: true, // 显示颜色指示器
callbacks: {
label: function(context) {
const label = context.label || '';
const value = context.raw || 0;
const total = context.dataset.data.reduce((acc, val) => acc + val, 0);
const percentage = total > 0 ? Math.round((value / total) * 100) : 0;
return `${label}: ${value} (${percentage}%)`;
}
},
cornerRadius: 8, // 圆角
boxPadding: 6, // 盒子内边距
borderColor: 'rgba(255, 255, 255, 0.2)', // 边框颜色
borderWidth: 1 // 边框宽度
},
title: {
display: false // 不显示标题由HTML标题代替
}
},
cutout: '50%', // 调整中心空白区域比例,使环更粗
// 增强元素配置
elements: {
arc: {
borderAlign: 'center',
tension: 0.1, // 添加轻微的张力,使圆弧更平滑
borderWidth: 3 // 统一边框宽度
}
},
layout: {
padding: {
top: 20, // 增加顶部内边距
right: 20,
bottom: 30, // 增加底部内边距,为图例预留更多空间
left: 20
}
},
// 添加交互配置
interaction: {
mode: 'nearest', // 交互模式
axis: 'x', // 交互轴
intersect: false // 不要求精确相交
},
// 增强悬停效果
hover: {
mode: 'nearest',
intersect: true,
animationDuration: 300 // 悬停动画持续时间
}
}
});
}
// 更新统计卡片
function updateStatsCards(stats) {
@@ -1049,8 +644,8 @@ function updateStatsCards(stats) {
// 标记动画正在进行
animationInProgress[elementId] = true;
// 动画配置 - 优化:使用固定的动画持续时间,减少计算开销
const duration = 300; // 固定300ms动画时间足够流畅且减少计算
// 动画配置
const duration = Math.min(800, Math.abs(targetValue - currentValue) * 2); // 根据数值变化大小调整动画持续时间
const startTime = performance.now();
const startValue = currentValue;
@@ -1073,8 +668,6 @@ function updateStatsCards(stats) {
element.textContent = formatNumber(targetValue);
// 标记动画完成
delete animationInProgress[elementId];
// 清理内存
element = null;
}
}
@@ -1246,7 +839,7 @@ function updateStatsCards(stats) {
blockedQueries: 0,
allowedQueries: 0,
errorQueries: 0,
prevResponseTime: null
avgResponseTime: 0
};
// 计算百分比并更新箭头
@@ -1274,46 +867,19 @@ function updateStatsCards(stats) {
// 更新平均响应时间的百分比和箭头,使用与其他统计卡片相同的逻辑
if (avgResponseTime !== undefined && avgResponseTime !== null) {
// 获取历史响应时间
const prevResponseTime = window.dashboardHistoryData.prevResponseTime;
// 计算变化百分比
let responsePercent = '0.0%';
const prevResponseTime = window.dashboardHistoryData.avgResponseTime || 0;
const currentResponseTime = avgResponseTime;
// 查找箭头元素
const responseTimePercentElem = document.getElementById('response-time-percent');
let parent = null;
let arrowIcon = null;
if (responseTimePercentElem) {
parent = responseTimePercentElem.parentElement;
if (parent) {
arrowIcon = parent.querySelector('.fa-arrow-up, .fa-arrow-down, .fa-circle');
}
if (prevResponseTime > 0) {
const changePercent = ((currentResponseTime - prevResponseTime) / prevResponseTime) * 100;
responsePercent = Math.abs(changePercent).toFixed(1) + '%';
}
// 首次加载时初始化历史数据,不计算趋势
if (prevResponseTime === null) {
window.dashboardHistoryData.prevResponseTime = currentResponseTime;
if (responseTimePercentElem) {
responseTimePercentElem.textContent = '0.0%';
responseTimePercentElem.className = 'text-sm flex items-center text-gray-500';
}
if (arrowIcon) {
arrowIcon.className = 'fa fa-circle mr-1 text-xs';
parent.className = 'text-gray-500 text-sm flex items-center';
}
} else {
// 计算变化百分比
let responsePercent = '0.0%';
if (prevResponseTime > 0) {
const changePercent = ((currentResponseTime - prevResponseTime) / prevResponseTime) * 100;
responsePercent = Math.abs(changePercent).toFixed(1) + '%';
}
// 响应时间趋势特殊处理:响应时间下降(性能提升)显示上升箭头,响应时间上升(性能下降)显示下降箭头
// updatePercentWithArrow函数内部已添加响应时间的特殊处理
updatePercentWithArrow('response-time-percent', responsePercent, prevResponseTime, currentResponseTime);
}
// 响应时间趋势特殊处理:响应时间下降(性能提升)显示上升箭头,响应时间上升(性能下降)显示下降箭头
// updatePercentWithArrow函数内部已添加响应时间的特殊处理
updatePercentWithArrow('response-time-percent', responsePercent, prevResponseTime, currentResponseTime);
} else {
updatePercentage('response-time-percent', '---');
}
@@ -1324,9 +890,9 @@ function updateStatsCards(stats) {
window.dashboardHistoryData.blockedQueries = blockedQueries;
window.dashboardHistoryData.allowedQueries = allowedQueries;
window.dashboardHistoryData.errorQueries = errorQueries;
// 只有当prevResponseTime不为null时才更新避免覆盖首次加载时设置的值
if (window.dashboardHistoryData.prevResponseTime !== null) {
window.dashboardHistoryData.prevResponseTime = avgResponseTime;
// 只在avgResponseTime不为0时更新历史数据保留上一次不为0的状态
if (avgResponseTime > 0) {
window.dashboardHistoryData.avgResponseTime = avgResponseTime;
}
@@ -1714,38 +1280,30 @@ async function updateTopClientsTable(clients) {
];
}
// 使用DocumentFragment批量处理DOM操作
const fragment = document.createDocumentFragment();
let html = '';
for (let i = 0; i < tableData.length; i++) {
const client = tableData[i];
// 获取IP地理信息
const location = await getIpGeolocation(client.ip);
// 创建容器元素
const container = document.createElement('div');
container.className = 'flex items-center justify-between p-3 rounded-md hover:bg-gray-50 transition-colors border-l-4 border-primary';
// 创建内容
container.innerHTML = `
<div class="flex-1 min-w-0">
<div class="flex items-center">
<span class="w-6 h-6 flex items-center justify-center rounded-full bg-primary/10 text-primary text-xs font-medium mr-3">${i + 1}</span>
<div class="flex flex-col">
<span class="font-medium truncate">${client.ip}</span>
<span class="text-xs text-gray-500">${location}</span>
html += `
<div class="flex items-center justify-between p-3 rounded-md hover:bg-gray-50 transition-colors border-l-4 border-primary">
<div class="flex-1 min-w-0">
<div class="flex items-center">
<span class="w-6 h-6 flex items-center justify-center rounded-full bg-primary/10 text-primary text-xs font-medium mr-3">${i + 1}</span>
<div class="flex flex-col">
<span class="font-medium truncate">${client.ip}</span>
<span class="text-xs text-gray-500">${location}</span>
</div>
</div>
</div>
<span class="ml-4 flex-shrink-0 font-semibold text-primary">${formatNumber(client.count)}</span>
</div>
<span class="ml-4 flex-shrink-0 font-semibold text-primary">${formatNumber(client.count)}</span>
`;
fragment.appendChild(container);
}
// 清空表格并添加新内容
tableBody.innerHTML = '';
tableBody.appendChild(fragment);
tableBody.innerHTML = html;
}
// 更新请求域名排行表格
@@ -1790,66 +1348,59 @@ async function updateTopDomainsTable(domains) {
return sum + (typeof domain.count === 'number' ? domain.count : 0);
}, 0);
// 使用DocumentFragment批量处理DOM操作
const fragment = document.createDocumentFragment();
let html = '';
for (let i = 0; i < tableData.length; i++) {
const domain = tableData[i];
// 检查域名是否是跟踪器
const trackerInfo = await isDomainInTrackerDatabase(domain.name);
const isTracker = trackerInfo !== null;
// 构建跟踪器浮窗内容
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-2">已知跟踪器</div>
<div class="mb-1"><strong>名称:</strong> ${trackerInfo.name || '未知'}</div>
<div class="mb-1"><strong>类别:</strong> ${trackerInfo.categoryId && trackersDatabase && trackersDatabase.categories ? trackersDatabase.categories[trackerInfo.categoryId] : '未知'}</div>
${trackerInfo.url ? `<div class="mb-1"><strong>URL:</strong> <a href="${trackerInfo.url}" target="_blank" class="text-blue-500 hover:underline">${trackerInfo.url}</a></div>` : ''}
${trackerInfo.source ? `<div class="mb-1"><strong>源:</strong> ${trackerInfo.source}</div>` : ''}
</div>
` : '';
// 计算百分比
const percentage = totalCount > 0 && typeof domain.count === 'number'
? ((domain.count / totalCount) * 100).toFixed(2)
: '0.00';
// 创建容器元素
const container = document.createElement('div');
container.className = 'p-3 rounded-md hover:bg-gray-50 transition-colors border-l-4 border-success';
// 构建跟踪器浮窗内容
const trackerContent = isTracker ? `
<div class="tracker-icon-container relative ml-2">
<i class="fa fa-eye text-red-500" title="已知跟踪器"></i>
<div class="tracker-tooltip absolute z-50 bg-white shadow-lg rounded-md border p-3 min-w-64 text-sm hidden">
<div class="font-semibold mb-2">已知跟踪器</div>
<div class="mb-1"><strong>名称:</strong> ${trackerInfo.name || '未知'}</div>
<div class="mb-1"><strong>类别:</strong> ${trackerInfo.categoryId && trackersDatabase && trackersDatabase.categories ? trackersDatabase.categories[trackerInfo.categoryId] : '未知'}</div>
${trackerInfo.url ? `<div class="mb-1"><strong>URL:</strong> <a href="${trackerInfo.url}" target="_blank" class="text-blue-500 hover:underline">${trackerInfo.url}</a></div>` : ''}
${trackerInfo.source ? `<div class="mb-1"><strong>源:</strong> ${trackerInfo.source}</div>` : ''}
</div>
</div>
` : '';
// 创建内容
container.innerHTML = `
<div class="flex items-center justify-between mb-2">
<div class="flex-1 min-w-0">
<div class="flex items-center">
<span class="w-6 h-6 flex items-center justify-center rounded-full bg-success/10 text-success text-xs font-medium mr-3">${i + 1}</span>
html += `
<div class="p-3 rounded-md hover:bg-gray-50 transition-colors border-l-4 border-success">
<div class="flex items-center justify-between mb-2">
<div class="flex-1 min-w-0">
<div class="flex items-center">
<span class="font-medium truncate">${domain.name}${domain.dnssec ? ' <i class="fa fa-lock text-green-500"></i>' : ''}</span>
${trackerContent}
<span class="w-6 h-6 flex items-center justify-center rounded-full bg-success/10 text-success text-xs font-medium mr-3">${i + 1}</span>
<div class="flex items-center">
<span class="font-medium truncate">${domain.name}${domain.dnssec ? ' <i class="fa fa-lock text-green-500"></i>' : ''}</span>
${isTracker ? `
<div class="tracker-icon-container relative ml-2">
<i class="fa fa-eye text-red-500" title="已知跟踪器"></i>
${trackerTooltip}
</div>
` : ''}
</div>
</div>
</div>
<div class="ml-4 flex items-center space-x-2">
<span class="flex-shrink-0 font-semibold text-success">${formatNumber(domain.count)}</span>
<span class="text-xs text-gray-500">${percentage}%</span>
</div>
</div>
<div class="ml-4 flex items-center space-x-2">
<span class="flex-shrink-0 font-semibold text-success">${formatNumber(domain.count)}</span>
<span class="text-xs text-gray-500">${percentage}%</span>
<div class="w-full bg-gray-200 rounded-full h-2.5">
<div class="bg-success h-2.5 rounded-full" style="width: ${percentage}%"></div>
</div>
</div>
<div class="w-full bg-gray-200 rounded-full h-2.5">
<div class="bg-success h-2.5 rounded-full" style="width: ${percentage}%"></div>
</div>
`;
fragment.appendChild(container);
}
// 清空表格并添加新内容
tableBody.innerHTML = '';
tableBody.appendChild(fragment);
tableBody.innerHTML = html;
// 添加跟踪器图标悬停事件
const trackerIconContainers = tableBody.querySelectorAll('.tracker-icon-container');
@@ -2034,13 +1585,6 @@ function initCharts() {
console.error('未找到比例图表元素');
return;
}
// 销毁现有图表
if (ratioChart) {
ratioChart.destroy();
ratioChart = null;
}
const ratioCtx = ratioChartElement.getContext('2d');
ratioChart = new Chart(ratioCtx, {
type: 'doughnut',
@@ -2156,12 +1700,6 @@ function initCharts() {
// 初始化解析类型统计饼图
const queryTypeChartElement = document.getElementById('query-type-chart');
if (queryTypeChartElement) {
// 销毁现有图表
if (queryTypeChart) {
queryTypeChart.destroy();
queryTypeChart = null;
}
const queryTypeCtx = queryTypeChartElement.getContext('2d');
// 预定义的颜色数组,用于解析类型
const queryTypeColors = ['#3498db', '#e74c3c', '#2ecc71', '#f39c12', '#9b59b6', '#1abc9c', '#d35400', '#34495e'];
@@ -3490,6 +3028,15 @@ window.addEventListener('DOMContentLoaded', () => {
});// 重写loadDashboardData函数修复语法错误
async function loadDashboardData() {
try {
// 检查是否有有效的缓存数据
const cachedData = window.pageDataCache && window.pageDataCache.getCache('dashboard');
if (cachedData) {
console.log('使用缓存的仪表盘数据');
// 处理缓存数据
processDashboardData(cachedData);
return;
}
// 并行获取所有数据,提高加载效率
const [stats, queryTypeStatsResult, topBlockedDomainsResult, recentBlockedDomainsResult, topClientsResult] = await Promise.all([
// 获取基本统计数据
@@ -3712,12 +3259,143 @@ async function loadDashboardData() {
// 确保TOP域名数据被正确加载
updateTopData();
// 存储数据到缓存
const dashboardData = {
stats,
queryTypeStats,
topClients,
topDomains,
topBlockedDomains,
recentBlockedDomains
};
if (window.pageDataCache) {
window.pageDataCache.setCache('dashboard', dashboardData);
}
} catch (error) {
console.error('加载仪表盘数据失败:', error);
// 静默失败,不显示通知以免打扰用户
}
}
// 处理缓存的仪表盘数据
function processDashboardData(cachedData) {
try {
const { stats, queryTypeStats, topClients, topDomains, topBlockedDomains, recentBlockedDomains } = cachedData;
// 确保stats是有效的对象
if (!stats || typeof stats !== 'object') {
console.error('无效的缓存统计数据:', stats);
return;
}
// 更新主页面的统计卡片数据
updateStatsCards(stats);
// 更新TOP客户端表格
updateTopClientsTable(topClients);
// 更新TOP域名表格
updateTopDomainsTable(topDomains);
// 更新TOP被屏蔽域名表格
updateTopBlockedTable(topBlockedDomains);
// 更新最近屏蔽域名表格
updateRecentBlockedTable(recentBlockedDomains);
// 更新图表
updateCharts(stats, queryTypeStats);
// 初始化或更新查询类型统计饼图
if (queryTypeStats) {
drawQueryTypeChart(queryTypeStats);
}
// 更新查询类型统计信息
if (document.getElementById('top-query-type')) {
const topQueryTypeElement = document.getElementById('top-query-type');
const topQueryTypeCountElement = document.getElementById('top-query-type-count');
// 从stats中获取查询类型统计数据
if (stats.dns && stats.dns.QueryTypes) {
const queryTypes = stats.dns.QueryTypes;
// 找出数量最多的查询类型
let maxCount = 0;
let topType = 'A';
for (const [type, count] of Object.entries(queryTypes)) {
const numCount = Number(count) || 0;
if (numCount > maxCount) {
maxCount = numCount;
topType = type;
}
}
// 更新DOM
if (topQueryTypeElement) {
topQueryTypeElement.textContent = topType;
}
if (topQueryTypeCountElement) {
topQueryTypeCountElement.textContent = formatNumber(maxCount);
}
// 保存到历史数据,用于计算趋势
window.dashboardHistoryData.prevTopQueryTypeCount = maxCount;
}
}
// 更新活跃IP信息
if (document.getElementById('active-ips')) {
const activeIPsElement = document.getElementById('active-ips');
// 从stats中获取活跃IP数
let activeIPs = 0;
if (stats.dns && stats.dns.ActiveIPs) {
activeIPs = stats.dns.ActiveIPs;
} else if (stats.activeIPs !== undefined) {
activeIPs = stats.activeIPs;
}
// 更新DOM
if (activeIPsElement) {
activeIPsElement.textContent = formatNumber(activeIPs);
}
// 保存到历史数据,用于计算趋势
window.dashboardHistoryData.prevActiveIPs = activeIPs;
}
// 更新平均响应时间
if (document.getElementById('avg-response-time')) {
// 直接使用缓存的平均响应时间
let responseTime = 0;
if (stats.dns && stats.dns.AvgResponseTime) {
responseTime = stats.dns.AvgResponseTime;
} else if (stats.avgResponseTime !== undefined) {
responseTime = stats.avgResponseTime;
} else if (stats.responseTime) {
responseTime = stats.responseTime;
}
if (responseTime > 0 && statCardCharts['response-time-chart']) {
// 限制小数位数为两位并更新图表
updateChartData('response-time-chart', parseFloat(responseTime).toFixed(2));
}
}
// 更新运行状态
updateUptime();
// 确保TOP域名数据被正确加载
updateTopData();
} catch (error) {
console.error('处理缓存仪表盘数据失败:', error);
}
}
// 主题切换功能
function initThemeToggle() {
const themeToggleBtn = document.getElementById('theme-toggle');

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,84 @@
// main.js - 主脚本文件
// 页面数据缓存对象
const pageDataCache = {
// 页面初始化状态
initializedPages: {},
// 页面数据缓存
data: {
dashboard: {
timestamp: 0,
data: null,
expiry: 5 * 60 * 1000 // 5分钟过期
},
logs: {
timestamp: 0,
data: null,
expiry: 5 * 60 * 1000 // 5分钟过期
},
shield: {
timestamp: 0,
data: null,
expiry: 10 * 60 * 1000 // 10分钟过期
},
hosts: {
timestamp: 0,
data: null,
expiry: 10 * 60 * 1000 // 10分钟过期
},
gfwlist: {
timestamp: 0,
data: null,
expiry: 10 * 60 * 1000 // 10分钟过期
}
},
// 检查缓存是否有效
isCacheValid: function(page) {
const cache = this.data[page];
if (!cache || !cache.data) return false;
const now = Date.now();
return (now - cache.timestamp) < cache.expiry;
},
// 获取缓存数据
getCache: function(page) {
if (this.isCacheValid(page)) {
return this.data[page].data;
}
return null;
},
// 设置缓存数据
setCache: function(page, data) {
if (this.data[page]) {
this.data[page].data = data;
this.data[page].timestamp = Date.now();
}
},
// 清除缓存
clearCache: function(page) {
if (this.data[page]) {
this.data[page].data = null;
this.data[page].timestamp = 0;
}
},
// 标记页面已初始化
markPageInitialized: function(page) {
this.initializedPages[page] = true;
},
// 检查页面是否已初始化
isPageInitialized: function(page) {
return this.initializedPages[page] || false;
}
};
// 页面可见性状态
let isPageVisible = true;
// 页面初始化函数 - 根据当前hash值初始化对应页面
function initPageByHash() {
const hash = window.location.hash.substring(1);
@@ -46,31 +125,51 @@ function initPageByHash() {
if (hash === 'shield') {
setTimeout(() => {
if (typeof initShieldPage === 'function') {
initShieldPage();
// 检查页面是否已经初始化
if (!pageDataCache.isPageInitialized('shield') || !pageDataCache.isCacheValid('shield')) {
initShieldPage();
pageDataCache.markPageInitialized('shield');
}
}
}, 0);
} else if (hash === 'hosts') {
setTimeout(() => {
if (typeof initHostsPage === 'function') {
initHostsPage();
// 检查页面是否已经初始化
if (!pageDataCache.isPageInitialized('hosts') || !pageDataCache.isCacheValid('hosts')) {
initHostsPage();
pageDataCache.markPageInitialized('hosts');
}
}
}, 0);
} else if (hash === 'gfwlist') {
setTimeout(() => {
if (typeof initGFWListPage === 'function') {
initGFWListPage();
// 检查页面是否已经初始化
if (!pageDataCache.isPageInitialized('gfwlist') || !pageDataCache.isCacheValid('gfwlist')) {
initGFWListPage();
pageDataCache.markPageInitialized('gfwlist');
}
}
}, 0);
} else if (hash === 'logs') {
setTimeout(() => {
if (typeof initLogsPage === 'function') {
initLogsPage();
// 检查页面是否已经初始化
if (!pageDataCache.isPageInitialized('logs') || !pageDataCache.isCacheValid('logs')) {
initLogsPage();
pageDataCache.markPageInitialized('logs');
}
}
}, 0);
} else if (hash === 'dashboard') {
setTimeout(() => {
if (typeof loadDashboardData === 'function') {
loadDashboardData();
// 检查页面是否已经初始化
if (!pageDataCache.isPageInitialized('dashboard') || !pageDataCache.isCacheValid('dashboard')) {
loadDashboardData();
pageDataCache.markPageInitialized('dashboard');
}
}
}, 0);
}
@@ -407,6 +506,39 @@ function updateThemeIcon(toggleElement, isDark) {
}
}
// 处理页面可见性变化
function handleVisibilityChange() {
if (document.visibilityState === 'visible') {
isPageVisible = true;
console.log('页面变为可见');
// 当页面重新可见时,检查当前页面是否需要刷新数据
const hash = window.location.hash.substring(1);
// 只有当缓存过期时才重新加载数据
if (hash && !pageDataCache.isCacheValid(hash)) {
console.log(`缓存已过期,重新加载${hash}页面数据`);
// 根据当前页面类型重新加载数据
if (hash === 'dashboard' && typeof loadDashboardData === 'function') {
loadDashboardData();
} else if (hash === 'logs' && typeof loadLogs === 'function') {
loadLogs();
} else if (hash === 'shield' && typeof initShieldPage === 'function') {
initShieldPage();
} else if (hash === 'hosts' && typeof initHostsPage === 'function') {
initHostsPage();
}
}
// 更新系统状态
updateSystemStatus();
} else {
isPageVisible = false;
console.log('页面变为隐藏');
}
}
// 初始化函数
function init() {
// 设置导航
@@ -424,6 +556,9 @@ function init() {
// 添加hashchange事件监听处理浏览器前进/后退按钮
window.addEventListener('hashchange', initPageByHash);
// 添加页面可见性变化监听
document.addEventListener('visibilitychange', handleVisibilityChange);
// 定期更新系统状态
setInterval(updateSystemStatus, 5000);
}

View File

@@ -183,6 +183,14 @@ function animateCounter(element, target, duration = 1000) {
// 加载屏蔽规则统计信息
async function loadShieldStats() {
// 检查是否有有效的缓存数据
const cachedStats = window.pageDataCache && window.pageDataCache.getCache('shield_stats');
if (cachedStats) {
console.log('使用缓存的屏蔽规则统计信息');
updateShieldStatsUI(cachedStats);
return;
}
try {
// 获取屏蔽规则统计信息
const shieldResponse = await fetch('/api/shield');
@@ -203,27 +211,18 @@ async function loadShieldStats() {
const blacklists = await blacklistsResponse.json();
const disabledBlacklistCount = blacklists.filter(blacklist => !blacklist.enabled).length;
// 组合统计数据
const shieldStats = {
...stats,
disabledBlacklistCount
};
// 更新统计信息
const elements = [
{ id: 'domain-rules-count', value: stats.domainRulesCount },
{ id: 'domain-exceptions-count', value: stats.domainExceptionsCount },
{ id: 'regex-rules-count', value: stats.regexRulesCount },
{ id: 'regex-exceptions-count', value: stats.regexExceptionsCount },
{ id: 'hosts-rules-count', value: stats.hostsRulesCount },
{ id: 'blacklist-count', value: stats.blacklistCount }
];
updateShieldStatsUI(shieldStats);
elements.forEach(item => {
const element = document.getElementById(item.id);
if (element) {
animateCounter(element, item.value || 0);
}
});
// 更新禁用黑名单数量
const disabledBlacklistElement = document.getElementById('blacklist-disabled-count');
if (disabledBlacklistElement) {
animateCounter(disabledBlacklistElement, disabledBlacklistCount);
// 存储数据到缓存
if (window.pageDataCache) {
window.pageDataCache.setCache('shield_stats', shieldStats);
}
} catch (error) {
console.error('加载屏蔽规则统计信息失败:', error);
@@ -231,8 +230,44 @@ async function loadShieldStats() {
}
}
// 更新屏蔽规则统计信息UI
function updateShieldStatsUI(stats) {
if (!stats) return;
// 更新统计信息
const elements = [
{ id: 'domain-rules-count', value: stats.domainRulesCount },
{ id: 'domain-exceptions-count', value: stats.domainExceptionsCount },
{ id: 'regex-rules-count', value: stats.regexRulesCount },
{ id: 'regex-exceptions-count', value: stats.regexExceptionsCount },
{ id: 'hosts-rules-count', value: stats.hostsRulesCount },
{ id: 'blacklist-count', value: stats.blacklistCount }
];
elements.forEach(item => {
const element = document.getElementById(item.id);
if (element) {
animateCounter(element, item.value || 0);
}
});
// 更新禁用黑名单数量
const disabledBlacklistElement = document.getElementById('blacklist-disabled-count');
if (disabledBlacklistElement) {
animateCounter(disabledBlacklistElement, stats.disabledBlacklistCount || 0);
}
}
// 加载自定义规则
async function loadLocalRules() {
// 检查是否有有效的缓存数据
const cachedRules = window.pageDataCache && window.pageDataCache.getCache('local_rules');
if (cachedRules) {
console.log('使用缓存的自定义规则');
updateLocalRulesUI(cachedRules);
return;
}
try {
const response = await fetch('/api/shield/localrules');
@@ -243,41 +278,62 @@ async function loadLocalRules() {
const data = await response.json();
// 更新自定义规则数量显示
if (document.getElementById('local-rules-count')) {
document.getElementById('local-rules-count').textContent = data.localRulesCount || 0;
}
updateLocalRulesUI(data);
// 设置当前规则类型
currentRulesType = 'local';
// 合并所有自定义规则
let rules = [];
// 添加域名规则
if (Array.isArray(data.domainRules)) {
rules = rules.concat(data.domainRules);
// 存储数据到缓存
if (window.pageDataCache) {
window.pageDataCache.setCache('local_rules', data);
}
// 添加域名排除规则
if (Array.isArray(data.domainExceptions)) {
rules = rules.concat(data.domainExceptions);
}
// 添加正则规则
if (Array.isArray(data.regexRules)) {
rules = rules.concat(data.regexRules);
}
// 添加正则排除规则
if (Array.isArray(data.regexExceptions)) {
rules = rules.concat(data.regexExceptions);
}
updateRulesTable(rules);
} catch (error) {
console.error('加载自定义规则失败:', error);
showNotification('加载自定义规则失败', 'error');
}
}
// 更新自定义规则UI
function updateLocalRulesUI(data) {
if (!data) return;
// 更新自定义规则数量显示
if (document.getElementById('local-rules-count')) {
document.getElementById('local-rules-count').textContent = data.localRulesCount || 0;
}
// 设置当前规则类型
currentRulesType = 'local';
// 合并所有自定义规则
let rules = [];
// 添加域名规则
if (Array.isArray(data.domainRules)) {
rules = rules.concat(data.domainRules);
}
// 添加域名排除规则
if (Array.isArray(data.domainExceptions)) {
rules = rules.concat(data.domainExceptions);
}
// 添加正则规则
if (Array.isArray(data.regexRules)) {
rules = rules.concat(data.regexRules);
}
// 添加正则排除规则
if (Array.isArray(data.regexExceptions)) {
rules = rules.concat(data.regexExceptions);
}
updateRulesTable(rules);
}
// 加载远程规则
async function loadRemoteRules() {
// 检查是否有有效的缓存数据
const cachedRules = window.pageDataCache && window.pageDataCache.getCache('remote_rules');
if (cachedRules) {
console.log('使用缓存的远程规则');
updateRemoteRulesUI(cachedRules);
return;
}
try {
// 设置当前规则类型
currentRulesType = 'remote';
@@ -290,36 +346,52 @@ async function loadRemoteRules() {
const data = await response.json();
// 更新远程规则数量显示
if (document.getElementById('remote-rules-count')) {
document.getElementById('remote-rules-count').textContent = data.remoteRulesCount || 0;
}
updateRemoteRulesUI(data);
// 合并所有远程规则
let rules = [];
// 添加域名规则
if (Array.isArray(data.domainRules)) {
rules = rules.concat(data.domainRules);
// 存储数据到缓存
if (window.pageDataCache) {
window.pageDataCache.setCache('remote_rules', data);
}
// 添加域名排除规则
if (Array.isArray(data.domainExceptions)) {
rules = rules.concat(data.domainExceptions);
}
// 添加正则规则
if (Array.isArray(data.regexRules)) {
rules = rules.concat(data.regexRules);
}
// 添加正则排除规则
if (Array.isArray(data.regexExceptions)) {
rules = rules.concat(data.regexExceptions);
}
updateRulesTable(rules);
} catch (error) {
console.error('加载远程规则失败:', error);
showNotification('加载远程规则失败', 'error');
}
}
// 更新远程规则UI
function updateRemoteRulesUI(data) {
if (!data) return;
// 设置当前规则类型
currentRulesType = 'remote';
// 更新远程规则数量显示
if (document.getElementById('remote-rules-count')) {
document.getElementById('remote-rules-count').textContent = data.remoteRulesCount || 0;
}
// 合并所有远程规则
let rules = [];
// 添加域名规则
if (Array.isArray(data.domainRules)) {
rules = rules.concat(data.domainRules);
}
// 添加域名排除规则
if (Array.isArray(data.domainExceptions)) {
rules = rules.concat(data.domainExceptions);
}
// 添加正则规则
if (Array.isArray(data.regexRules)) {
rules = rules.concat(data.regexRules);
}
// 添加正则排除规则
if (Array.isArray(data.regexExceptions)) {
rules = rules.concat(data.regexExceptions);
}
updateRulesTable(rules);
}
// 更新规则表格
function updateRulesTable(rules) {
const tbody = document.getElementById('rules-table-body');
@@ -600,6 +672,14 @@ async function handleAddRule() {
// 加载远程黑名单
async function loadRemoteBlacklists() {
// 检查是否有有效的缓存数据
const cachedBlacklists = window.pageDataCache && window.pageDataCache.getCache('remote_blacklists');
if (cachedBlacklists) {
console.log('使用缓存的远程黑名单');
updateBlacklistsTable(cachedBlacklists);
return;
}
try {
const response = await fetch('/api/shield/blacklists');
@@ -612,6 +692,11 @@ async function loadRemoteBlacklists() {
// 确保blacklists是数组
const blacklistArray = Array.isArray(blacklists) ? blacklists : [];
updateBlacklistsTable(blacklistArray);
// 存储数据到缓存
if (window.pageDataCache) {
window.pageDataCache.setCache('remote_blacklists', blacklistArray);
}
} catch (error) {
console.error('加载远程黑名单失败:', error);
showNotification('加载远程黑名单失败', 'error');

52
temp_config.json Normal file
View File

@@ -0,0 +1,52 @@
{
"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