优化修复

This commit is contained in:
Alex Yang
2026-01-17 01:18:03 +08:00
parent cdac4fcf43
commit ac96c7c10b
57 changed files with 895 additions and 1252132 deletions

View File

@@ -1246,6 +1246,25 @@
"url": "toutiaoapi.com",
"icon": "https://www.toutiao.com/favicon.ico"
},
"抖音HLS直播拉流CDN节点(x0区域)": {
"name": "抖音HLS直播拉流CDN节点(x0区域)/HLS格式直播视频流分发服务",
"categoryId": 2,
"url": {
"1": "pull-x0-f5-hls.douyincdn.com",
"2": "pull-hs-f5-hls.douyincdn.com",
"3": "pull-spe-f5-hls.douyincdn.com",
"4": "pull-l-f5-hls.douyincdn.com",
"5": "pull-f3-f5-hls.douyincdn.com",
"6": "pull-f11-f5-hls.douyincdn.com"
},
"icon": "https://www.douyin.com/favicon.ico"
},
"抖音官方CDN主域名": {
"name": "抖音官方CDN主域名",
"categoryId": 2,
"url": "douyincdn.com",
"icon": "https://www.douyin.com/favicon.ico"
},
"snssdk": {
"日志 / 数据上报核心服务": {
"name": "日志 / 数据上报核心服务",

View File

@@ -17,6 +17,7 @@
"13": "邮件",
"14": "consent",
"15": "遥测",
"16": "",
"101": "移动分析"
},
@@ -2619,6 +2620,12 @@
"url": "https://ww.deluxe.com/",
"companyId": "deluxe"
},
"browserevent": {
"name": "Browser Event",
"categoryId": 15,
"url": "https://browser.events.data.msn.cn",
"companyId": "Microsoft"
},
"appcenter": {
"name": "Microsoft App Center",
"categoryId": 5,

BIN
static/images/user.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -48,6 +48,12 @@
<span>Hosts管理</span>
</a>
</li>
<li>
<a href="#gfwlist" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all">
<i class="fa fa-globe mr-3 text-lg"></i>
<span>GFWList管理</span>
</a>
</li>
<li>
<a href="#query" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all">
@@ -112,6 +118,12 @@
<span>Hosts管理</span>
</a>
</li>
<li>
<a href="#gfwlist" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all">
<i class="fa fa-globe mr-3 text-lg"></i>
<span>GFWList管理</span>
</a>
</li>
<li>
<a href="#query" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all">
@@ -163,7 +175,7 @@
<!-- 账户下拉菜单 -->
<div class="relative group" id="account-dropdown">
<button class="flex items-center p-2 rounded-full hover:bg-gray-100 transition-colors focus:outline-none">
<img src="https://picsum.photos/id/1005/40/40" alt="用户头像" class="w-8 h-8 rounded-full">
<img src="images/user.jpg" alt="用户头像" class="w-8 h-8 rounded-full">
<span class="ml-2 hidden md:block">管理员</span>
<i class="fa fa-caret-down ml-1 text-xs hidden md:block"></i>
</button>
@@ -1036,9 +1048,98 @@
</div>
</div>
<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管理</h3>
<form id="gfwlist-form">
<div class="mb-8">
<h4 class="text-md font-medium mb-4">基本设置</h4>
<div class="grid grid-cols-1 gap-6">
<div>
<div class="flex items-center justify-between">
<div>
<label for="gfwlist-enabled" class="block text-sm font-medium text-gray-700">启用GFWList</label>
<p class="mt-1 text-xs text-gray-500">启用后GFWList中的域名将被解析到指定的目标IP</p>
</div>
<button id="gfwlist-enabled" type="button" class="toggle-btn bg-gray-300 hover:bg-gray-400 text-white font-medium py-1 px-3 rounded-md transition-colors duration-200">
<i class="fa fa-toggle-off"></i>
</button>
</div>
</div>
</div>
</div>
<div class="mb-8">
<h4 class="text-md font-medium mb-4">GFWList解析目标IP</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label for="gfwlist-target-ip" class="block text-sm font-medium text-gray-700 mb-1">目标IP地址</label>
<input type="text" id="gfwlist-target-ip" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="127.0.0.1">
<p class="mt-1 text-xs text-gray-500">GFWList中的域名将被解析到这个IP</p>
</div>
</div>
</div>
<div class="mb-8">
<h4 class="text-md font-medium mb-4">通行网站</h4>
<p class="text-sm text-gray-500 mb-4">开启以下开关后对应域名将使用GFWList目标IP进行解析</p>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4">
<div class="bg-gray-50 p-4 rounded-lg">
<div class="flex items-center justify-between">
<div>
<span class="text-sm font-medium text-gray-700">谷歌 (Google)</span>
</div>
<button id="gfwlist-google" type="button" class="toggle-btn bg-gray-300 hover:bg-gray-400 text-white font-medium py-1 px-3 rounded-md transition-colors duration-200">
<i class="fa fa-toggle-off"></i>
</button>
</div>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<div class="flex items-center justify-between">
<div>
<span class="text-sm font-medium text-gray-700">YouTube</span>
</div>
<button id="gfwlist-youtube" type="button" class="toggle-btn bg-gray-300 hover:bg-gray-400 text-white font-medium py-1 px-3 rounded-md transition-colors duration-200">
<i class="fa fa-toggle-off"></i>
</button>
</div>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<div class="flex items-center justify-between">
<div>
<span class="text-sm font-medium text-gray-700">Facebook</span>
</div>
<button id="gfwlist-facebook" type="button" class="toggle-btn bg-gray-300 hover:bg-gray-400 text-white font-medium py-1 px-3 rounded-md transition-colors duration-200">
<i class="fa fa-toggle-off"></i>
</button>
</div>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<div class="flex items-center justify-between">
<div>
<span class="text-sm font-medium text-gray-700">X (Twitter)</span>
</div>
<button id="gfwlist-twitter" type="button" class="toggle-btn bg-gray-300 hover:bg-gray-400 text-white font-medium py-1 px-3 rounded-md transition-colors duration-200">
<i class="fa fa-toggle-off"></i>
</button>
</div>
</div>
</div>
</div>
<div class="flex justify-end">
<button type="button" id="gfwlist-save-btn" class="px-6 py-2 bg-primary text-white rounded-md hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
保存配置
</button>
</div>
</form>
</div>
</div>
<div id="config-content" class="hidden">
<!-- 系统设置页面内容 -->
<div class="bg-white rounded-lg p-6 card-shadow">
<div class="bg-white rounded-lg p-6 card-shadow max-w-3xl">
<h3 class="text-lg font-semibold mb-6">系统设置</h3>
<!-- 配置表单 -->
@@ -1052,23 +1153,42 @@
<input type="number" id="dns-port" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="53">
</div>
<div class="md:col-span-2">
<label for="dns-upstream-servers" class="block text-sm font-medium text-gray-700 mb-1">上游DNS服务器 (逗号分隔)</label>
<input type="text" id="dns-upstream-servers" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="8.8.8.8, 1.1.1.1">
<label for="dns-upstream-servers" class="block text-sm font-medium text-gray-700 mb-1">上游DNS服务器 (每行一个)</label>
<textarea id="dns-upstream-servers" rows="4" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="8.8.8.8&#10;1.1.1.1"></textarea>
</div>
<div>
<label for="dns-dnssec-upstream-servers" class="block text-sm font-medium text-gray-700 mb-1">DNSSEC上游DNS服务器 (逗号分隔)</label>
<input type="text" id="dns-dnssec-upstream-servers" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="8.8.8.8, 1.1.1.1">
<label for="dns-dnssec-upstream-servers" class="block text-sm font-medium text-gray-700 mb-1">DNSSEC上游DNS服务器 (每行一个)</label>
<textarea id="dns-dnssec-upstream-servers" rows="4" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="8.8.8.8&#10;1.1.1.1"></textarea>
</div>
<div>
<label for="dns-save-interval" class="block text-sm font-medium text-gray-700 mb-1">保存间隔 (秒)</label>
<input type="number" id="dns-save-interval" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="300">
</div>
<div class="md:col-span-2">
<label class="flex items-center space-x-2">
<input type="checkbox" id="dns-enable-ipv6" class="rounded text-primary focus:ring-primary">
<span class="text-sm font-medium text-gray-700">启用IPv6解析AAAA记录</span>
</label>
</div>
<div>
<label for="dns-cache-mode" class="block text-sm font-medium text-gray-700 mb-1">缓存模式</label>
<select id="dns-cache-mode" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
<option value="memory">内存缓存</option>
<option value="file">文件缓存</option>
</select>
</div>
<div>
<label for="dns-cache-size" class="block text-sm font-medium text-gray-700 mb-1">缓存大小 (MB)</label>
<input type="number" id="dns-cache-size" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="100">
</div>
<div>
<label for="dns-max-cache-ttl" class="block text-sm font-medium text-gray-700 mb-1">最大缓存TTL (分钟)</label>
<input type="number" id="dns-max-cache-ttl" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="120">
</div>
<div>
<label for="dns-min-cache-ttl" class="block text-sm font-medium text-gray-700 mb-1">最小缓存TTL (分钟)</label>
<input type="number" id="dns-min-cache-ttl" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="5">
</div>
<div class="md:col-span-2">
<label class="flex items-center space-x-2">
<input type="checkbox" id="dns-enable-ipv6" class="rounded text-primary focus:ring-primary">
<span class="text-sm font-medium text-gray-700">启用IPv6解析AAAA记录</span>
</label>
</div>
</div>
</div>
@@ -1093,30 +1213,18 @@
<div class="mt-4">
<label for="shield-block-method" class="block text-sm font-medium text-gray-700 mb-1">屏蔽方法</label>
<select id="shield-block-method" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
<option value="0.0.0.0">返回0.0.0.0</option>
<option value="NXDOMAIN">返回NXDOMAIN</option>
<option value="refused">返回refused</option>
<option value="emptyIP">返回空IP</option>
<option value="emptyIP">返回0.0.0.0</option>
<option value="customIP">返回自定义IP</option>
</select>
</div>
<div class="mt-4">
<div class="mt-4" id="custom-block-ip-container">
<label for="shield-custom-block-ip" class="block text-sm font-medium text-gray-700 mb-1">自定义屏蔽IP</label>
<input type="text" id="shield-custom-block-ip" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="127.0.0.1">
</div>
</div>
<!-- GFWList配置 -->
<div class="mb-8">
<h4 class="text-md font-medium mb-4">GFWList配置</h4>
<div>
<label for="shield-gfwlist-ip" class="block text-sm font-medium text-gray-700 mb-1">GFWList解析目标IP</label>
<input type="text" id="shield-gfwlist-ip" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="127.0.0.1">
</div>
<div class="mt-4">
<label for="shield-gfwlist-content" class="block text-sm font-medium text-gray-700 mb-1">GFWList内容</label>
<textarea id="shield-gfwlist-content" rows="10" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="输入GFWList规则内容..."></textarea>
</div>
</div>
<!-- 操作按钮 -->

View File

@@ -60,11 +60,15 @@ function populateConfigForm(config) {
// DNS配置 - 使用函数安全设置值,避免 || 操作符可能的错误处理
setElementValue('dns-port', getSafeValue(dnsServerConfig.Port, 53));
setElementValue('dns-upstream-servers', getSafeArray(dnsServerConfig.UpstreamServers).join(', '));
setElementValue('dns-dnssec-upstream-servers', getSafeArray(dnsServerConfig.DNSSECUpstreamServers).join(', '));
setElementValue('dns-upstream-servers', getSafeArray(dnsServerConfig.UpstreamServers).join('\n'));
setElementValue('dns-dnssec-upstream-servers', getSafeArray(dnsServerConfig.DNSSECUpstreamServers).join('\n'));
//setElementValue('dns-stats-file', getSafeValue(dnsServerConfig.StatsFile, 'data/stats.json'));
setElementValue('dns-save-interval', getSafeValue(dnsServerConfig.SaveInterval, 30));
//setElementValue('dns-cache-ttl', getSafeValue(dnsServerConfig.CacheTTL, 10));
setElementValue('dns-save-interval', getSafeValue(dnsServerConfig.saveInterval, 30));
// 缓存配置
setElementValue('dns-cache-mode', getSafeValue(dnsServerConfig.CacheMode, 'memory'));
setElementValue('dns-cache-size', getSafeValue(dnsServerConfig.CacheSize, 100));
setElementValue('dns-max-cache-ttl', getSafeValue(dnsServerConfig.MaxCacheTTL, 120));
setElementValue('dns-min-cache-ttl', getSafeValue(dnsServerConfig.MinCacheTTL, 5));
setElementValue('dns-enable-ipv6', getSafeValue(dnsServerConfig.EnableIPv6, false));
// HTTP配置
setElementValue('http-port', getSafeValue(httpServerConfig.Port, 8080));
@@ -75,21 +79,38 @@ function populateConfigForm(config) {
// 使用服务器端接受的屏蔽方法值默认使用NXDOMAIN, 可选值: NXDOMAIN, NULL, REFUSED
setElementValue('shield-block-method', getSafeValue(shieldConfig.BlockMethod, 'NXDOMAIN'));
setElementValue('shield-custom-block-ip', getSafeValue(shieldConfig.CustomBlockIP, ''));
// GFWList配置
setElementValue('shield-gfwlist-ip', getSafeValue(shieldConfig.GFWListIP, ''));
setElementValue('shield-gfwlist-content', getSafeValue(shieldConfig.GFWListContent, ''));
// 初始加载时更新自定义屏蔽IP输入框的可见性
updateCustomBlockIpVisibility();
}
// 工具函数:安全设置元素值
function setElementValue(elementId, value) {
const element = document.getElementById(elementId);
if (element && element.tagName === 'INPUT') {
if (element.type === 'checkbox') {
element.checked = value;
} else {
if (element) {
if (element.tagName === 'INPUT') {
if (element.type === 'checkbox') {
element.checked = value;
} else {
element.value = value;
}
} else if (element.tagName === 'TEXTAREA') {
element.value = value;
} else if (element.tagName === 'BUTTON' && element.classList.contains('toggle-btn')) {
const icon = element.querySelector('i');
if (icon) {
if (value) {
element.classList.remove('bg-gray-300', 'hover:bg-gray-400');
element.classList.add('bg-success', 'hover:bg-success/90');
icon.className = 'fa fa-toggle-on';
} else {
element.classList.remove('bg-success', 'hover:bg-success/90');
element.classList.add('bg-gray-300', 'hover:bg-gray-400');
icon.className = 'fa fa-toggle-off';
}
}
}
} else if (!element) {
} else {
console.warn(`Element with id "${elementId}" not found for setting value: ${value}`);
}
}
@@ -168,13 +189,12 @@ function collectFormData() {
// 安全获取上游服务器列表
const upstreamServersText = getElementValue('dns-upstream-servers');
const upstreamServers = upstreamServersText ?
upstreamServersText.split(',').map(function(s) { return s.trim(); }).filter(function(s) { return s !== ''; }) :
upstreamServersText.split('\n').map(function(s) { return s.trim(); }).filter(function(s) { return s !== ''; }) :
[];
// 安全获取DNSSEC上游服务器列表
const dnssecUpstreamServersText = getElementValue('dns-dnssec-upstream-servers');
const dnssecUpstreamServers = dnssecUpstreamServersText ?
dnssecUpstreamServersText.split(',').map(function(s) { return s.trim(); }).filter(function(s) { return s !== ''; }) :
dnssecUpstreamServersText.split('\n').map(function(s) { return s.trim(); }).filter(function(s) { return s !== ''; }) :
[];
// 安全获取并转换整数值
@@ -187,6 +207,16 @@ function collectFormData() {
const updateIntervalValue = getElementValue('shield-update-interval');
const updateInterval = updateIntervalValue ? parseInt(updateIntervalValue, 10) : 3600;
// 安全获取并转换缓存配置值
const cacheSizeValue = getElementValue('dns-cache-size');
const cacheSize = cacheSizeValue ? parseInt(cacheSizeValue, 10) : 100;
const maxCacheTTLValue = getElementValue('dns-max-cache-ttl');
const maxCacheTTL = maxCacheTTLValue ? parseInt(maxCacheTTLValue, 10) : 120;
const minCacheTTLValue = getElementValue('dns-min-cache-ttl');
const minCacheTTL = minCacheTTLValue ? parseInt(minCacheTTLValue, 10) : 5;
return {
dnsserver: {
port: dnsPort,
@@ -194,7 +224,11 @@ function collectFormData() {
dnssecUpstreamServers: dnssecUpstreamServers,
timeout: timeout,
saveInterval: saveInterval,
enableIPv6: getElementValue('dns-enable-ipv6')
enableIPv6: getElementValue('dns-enable-ipv6'),
cacheMode: getElementValue('dns-cache-mode') || 'memory',
cacheSize: cacheSize,
maxCacheTTL: maxCacheTTL,
minCacheTTL: minCacheTTL
},
httpserver: {
port: httpPort
@@ -202,9 +236,7 @@ function collectFormData() {
shield: {
updateInterval: updateInterval,
blockMethod: getElementValue('shield-block-method') || 'NXDOMAIN',
customBlockIP: getElementValue('shield-custom-block-ip'),
gfwListIP: getElementValue('shield-gfwlist-ip'),
gfwListContent: getElementValue('shield-gfwlist-content')
customBlockIP: getElementValue('shield-custom-block-ip')
}
};
}
@@ -220,19 +252,43 @@ function getElementValue(elementId) {
return element.value;
} else if (element.tagName === 'TEXTAREA') {
return element.value;
} else if (element.tagName === 'BUTTON' && element.classList.contains('toggle-btn')) {
// 处理按钮式开关
return element.classList.contains('bg-success');
}
return element.value;
}
return ''; // 默认返回空字符串
}
// 更新自定义屏蔽IP输入框的可见性
function updateCustomBlockIpVisibility() {
const blockMethod = getElementValue('shield-block-method');
const customBlockIpContainer = document.getElementById('custom-block-ip-container');
if (blockMethod === 'customIP') {
customBlockIpContainer.style.display = 'block';
} else {
customBlockIpContainer.style.display = 'none';
}
}
// 设置事件监听器
function setupConfigEventListeners() {
// 保存配置按钮
getElement('save-config-btn')?.addEventListener('click', handleSaveConfig);
const saveConfigBtn = getElement('save-config-btn');
if (saveConfigBtn) {
saveConfigBtn.addEventListener('click', handleSaveConfig);
}
// 重启服务按钮
getElement('restart-service-btn')?.addEventListener('click', handleRestartService);
const restartServiceBtn = getElement('restart-service-btn');
if (restartServiceBtn) {
restartServiceBtn.addEventListener('click', handleRestartService);
}
// 监听屏蔽方法选择变化
const blockMethodSelect = document.getElementById('shield-block-method');
if (blockMethodSelect) {
blockMethodSelect.addEventListener('change', updateCustomBlockIpVisibility);
}
}
@@ -300,6 +356,114 @@ function showNotification(message, type = 'info') {
}, 3000);
}
// GFWList管理页面功能实现
// 初始化GFWList管理页面
function initGFWListPage() {
loadGFWListConfig();
setupGFWListEventListeners();
}
// 加载GFWList配置
async function loadGFWListConfig() {
try {
const result = await api.getConfig();
if (result && result.error) {
showErrorMessage('加载配置失败: ' + result.error);
return;
}
populateGFWListForm(result);
} catch (error) {
showErrorMessage('加载配置失败: ' + (error.message || '未知错误'));
}
}
// 填充GFWList配置表单
function populateGFWListForm(config) {
const gfwListConfig = config.gfwList || {};
setElementValue('gfwlist-enabled', getSafeValue(gfwListConfig.enabled, false));
setElementValue('gfwlist-target-ip', getSafeValue(gfwListConfig.ip, ''));
setElementValue('gfwlist-google', getSafeValue(config.allowGoogle, false));
setElementValue('gfwlist-youtube', getSafeValue(config.allowYouTube, false));
setElementValue('gfwlist-facebook', getSafeValue(config.allowFacebook, false));
setElementValue('gfwlist-twitter', getSafeValue(config.allowTwitter, false));
}
// 保存GFWList配置
async function handleSaveGFWListConfig() {
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表单数据
function collectGFWListFormData() {
const targetIP = getElementValue('gfwlist-target-ip');
return {
gfwList: {
ip: targetIP,
enabled: getElementValue('gfwlist-enabled'),
content: '/root/dns/data/gfwlist.txt' // 保持默认路径
},
allowGoogle: getElementValue('gfwlist-google'),
allowYouTube: getElementValue('gfwlist-youtube'),
allowFacebook: getElementValue('gfwlist-facebook'),
allowTwitter: getElementValue('gfwlist-twitter')
};
}
// 重启GFWList服务
async function handleRestartGFWListService() {
if (!confirm('确定要重启DNS服务吗重启期间服务可能会短暂不可用。')) return;
try {
const result = await api.restartService();
if (result && result.error) {
showErrorMessage('服务重启失败: ' + result.error);
return;
}
showSuccessMessage('服务重启成功');
} catch (error) {
showErrorMessage('重启服务失败: ' + (error.message || '未知错误'));
}
}
// 设置GFWList事件监听器
function setupGFWListEventListeners() {
const saveBtn = getElement('gfwlist-save-btn');
if (saveBtn) {
saveBtn.addEventListener('click', handleSaveGFWListConfig);
}
// 为所有按钮式开关添加点击事件监听器
const toggleBtns = document.querySelectorAll('.toggle-btn');
toggleBtns.forEach(btn => {
btn.addEventListener('click', function() {
// 切换按钮状态
const currentState = this.classList.contains('bg-success');
setElementValue(this.id, !currentState);
});
});
}
// 页面加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initConfigPage);

View File

@@ -157,7 +157,15 @@ function processRealTimeData(stats) {
// 更新新卡片数据
if (document.getElementById('avg-response-time')) {
const responseTime = stats.avgResponseTime ? stats.avgResponseTime.toFixed(2) + 'ms' : '---';
// 首先尝试从stats.dns.AvgResponseTime获取然后尝试stats.avgResponseTime
let avgResponseTime = null;
if (stats.dns && stats.dns.AvgResponseTime !== undefined) {
avgResponseTime = stats.dns.AvgResponseTime;
} else if (stats.avgResponseTime !== undefined) {
avgResponseTime = stats.avgResponseTime;
}
const responseTime = avgResponseTime ? avgResponseTime.toFixed(2) + 'ms' : '---';
// 计算响应时间趋势
let responsePercent = '---';
@@ -176,10 +184,10 @@ function processRealTimeData(stats) {
}
}
if (stats.avgResponseTime !== undefined && stats.avgResponseTime !== null) {
if (avgResponseTime !== undefined && avgResponseTime !== null) {
// 首次加载时初始化历史数据,不计算趋势
if (window.dashboardHistoryData.prevResponseTime === undefined || window.dashboardHistoryData.prevResponseTime === null) {
window.dashboardHistoryData.prevResponseTime = stats.avgResponseTime;
window.dashboardHistoryData.prevResponseTime = avgResponseTime;
responsePercent = '0.0%';
trendIcon = '•';
trendClass = 'text-gray-500';
@@ -191,7 +199,7 @@ function processRealTimeData(stats) {
const prevResponseTime = window.dashboardHistoryData.prevResponseTime;
// 计算变化百分比
const changePercent = ((stats.avgResponseTime - prevResponseTime) / prevResponseTime) * 100;
const changePercent = ((avgResponseTime - prevResponseTime) / prevResponseTime) * 100;
responsePercent = Math.abs(changePercent).toFixed(1) + '%';
// 处理-0.0%的情况
@@ -227,7 +235,7 @@ function processRealTimeData(stats) {
}
// 更新历史数据
window.dashboardHistoryData.prevResponseTime = stats.avgResponseTime;
window.dashboardHistoryData.prevResponseTime = avgResponseTime;
}
}
@@ -690,8 +698,16 @@ async function loadDashboardData() {
// 更新新卡片数据 - 使用API返回的真实数据
if (document.getElementById('avg-response-time')) {
// 首先尝试从stats.dns.AvgResponseTime获取然后尝试stats.avgResponseTime
let avgResponseTime = null;
if (stats.dns && stats.dns.AvgResponseTime !== undefined) {
avgResponseTime = stats.dns.AvgResponseTime;
} else if (stats.avgResponseTime !== undefined) {
avgResponseTime = stats.avgResponseTime;
}
// 保留两位小数并添加单位
const responseTime = stats.avgResponseTime ? stats.avgResponseTime.toFixed(2) + 'ms' : '---';
const responseTime = avgResponseTime ? avgResponseTime.toFixed(2) + 'ms' : '---';
// 计算响应时间趋势
let responsePercent = '---';
@@ -710,10 +726,10 @@ async function loadDashboardData() {
}
}
if (stats.avgResponseTime !== undefined && stats.avgResponseTime !== null) {
if (avgResponseTime !== undefined && avgResponseTime !== null) {
// 首次加载时初始化历史数据,不计算趋势
if (window.dashboardHistoryData.prevResponseTime === undefined || window.dashboardHistoryData.prevResponseTime === null) {
window.dashboardHistoryData.prevResponseTime = stats.avgResponseTime;
window.dashboardHistoryData.prevResponseTime = avgResponseTime;
responsePercent = '0.0%';
trendIcon = '•';
trendClass = 'text-gray-500';
@@ -726,7 +742,7 @@ async function loadDashboardData() {
// 计算变化百分比
if (prevResponseTime > 0) {
const changePercent = ((stats.avgResponseTime - prevResponseTime) / prevResponseTime) * 100;
const changePercent = ((avgResponseTime - prevResponseTime) / prevResponseTime) * 100;
responsePercent = Math.abs(changePercent).toFixed(1) + '%';
// 处理-0.0%的情况
@@ -765,7 +781,7 @@ async function loadDashboardData() {
}
// 更新历史数据
window.dashboardHistoryData.prevResponseTime = stats.avgResponseTime;
window.dashboardHistoryData.prevResponseTime = avgResponseTime;
}
}
@@ -934,6 +950,10 @@ function updateStatsCards(stats) {
if (stats.queryTypePercentage !== undefined) queryTypePercentage = stats.queryTypePercentage;
if (stats.activeIPs !== undefined) activeIPs = stats.activeIPs;
if (stats.activeIPsPercentage !== undefined) activeIPsPercentage = stats.activeIPsPercentage;
if (stats.avgResponseTime !== undefined) {
// 存储平均响应时间,用于后续更新卡片
window.avgResponseTime = stats.avgResponseTime;
}
// 如果dns对象存在优先使用其中的数据
@@ -953,6 +973,12 @@ function updateStatsCards(stats) {
if (activeIPs > 0 && stats.dns.SourceIPs) {
activeIPsPercentage = activeIPs / Object.keys(stats.dns.SourceIPs).length * 100;
}
// 检查并更新平均响应时间
if (stats.dns.AvgResponseTime !== undefined) {
// 存储平均响应时间,用于后续更新卡片
window.avgResponseTime = stats.dns.AvgResponseTime;
}
}
} else if (Array.isArray(stats) && stats.length > 0) {
// 可能的数据结构3: 数组形式
@@ -1330,12 +1356,18 @@ function updateStatsCards(stats) {
updatePercentage('error-percent', '---');
}
// 更新平均响应时间卡片
if (document.getElementById('avg-response-time')) {
const responseTime = window.avgResponseTime ? window.avgResponseTime.toFixed(2) + 'ms' : '---';
document.getElementById('avg-response-time').textContent = responseTime;
}
// 更新历史数据
window.dashboardHistoryData.totalQueries = totalQueries;
window.dashboardHistoryData.blockedQueries = blockedQueries;
window.dashboardHistoryData.allowedQueries = allowedQueries;
window.dashboardHistoryData.errorQueries = errorQueries;
}
@@ -1370,6 +1402,11 @@ async function updateTopBlockedTable(domains) {
console.log('使用示例数据填充Top屏蔽域名表格');
}
// 计算总拦截次数
const totalCount = tableData.reduce((sum, domain) => {
return sum + (typeof domain.count === 'number' ? domain.count : 0);
}, 0);
let html = '';
for (let i = 0; i < tableData.length; i++) {
const domain = tableData[i];
@@ -1388,23 +1425,36 @@ async function updateTopBlockedTable(domains) {
</div>
` : '';
// 计算百分比
const percentage = totalCount > 0 && typeof domain.count === 'number'
? ((domain.count / totalCount) * 100).toFixed(2)
: '0.00';
html += `
<div class="flex items-center justify-between p-3 rounded-md hover:bg-gray-50 transition-colors border-l-4 border-danger">
<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-danger/10 text-danger text-xs font-medium mr-3">${i + 1}</span>
<div class="p-3 rounded-md hover:bg-gray-50 transition-colors border-l-4 border-danger">
<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}</span>
${isTracker ? `
<div class="tracker-icon-container relative ml-2">
<i class="fa fa-eye text-red-500" title="已知跟踪器"></i>
${trackerTooltip}
</div>
` : ''}
<span class="w-6 h-6 flex items-center justify-center rounded-full bg-danger/10 text-danger text-xs font-medium mr-3">${i + 1}</span>
<div class="flex items-center">
<span class="font-medium truncate">${domain.name}</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-danger">${formatNumber(domain.count)}</span>
<span class="text-xs text-gray-500">${percentage}%</span>
</div>
</div>
<div class="w-full bg-gray-200 rounded-full h-2.5">
<div class="bg-danger h-2.5 rounded-full" style="width: ${percentage}%"></div>
</div>
<span class="ml-4 flex-shrink-0 font-semibold text-danger">${formatNumber(domain.count)}</span>
</div>
`;
}
@@ -1774,7 +1824,10 @@ async function updateTopDomainsTable(domains) {
console.log('使用示例数据填充请求域名排行表格');
}
// 计算总请求次数
const totalCount = tableData.reduce((sum, domain) => {
return sum + (typeof domain.count === 'number' ? domain.count : 0);
}, 0);
let html = '';
for (let i = 0; i < tableData.length; i++) {
@@ -1794,23 +1847,36 @@ async function updateTopDomainsTable(domains) {
</div>
` : '';
// 计算百分比
const percentage = totalCount > 0 && typeof domain.count === 'number'
? ((domain.count / totalCount) * 100).toFixed(2)
: '0.00';
html += `
<div class="flex items-center justify-between p-3 rounded-md hover:bg-gray-50 transition-colors border-l-4 border-success">
<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>
<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>
${isTracker ? `
<div class="tracker-icon-container relative ml-2">
<i class="fa fa-eye text-red-500" title="已知跟踪器"></i>
${trackerTooltip}
</div>
` : ''}
<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="w-full bg-gray-200 rounded-full h-2.5">
<div class="bg-success h-2.5 rounded-full" style="width: ${percentage}%"></div>
</div>
<span class="ml-4 flex-shrink-0 font-semibold text-success">${formatNumber(domain.count)}</span>
</div>
`;
}
@@ -2931,16 +2997,18 @@ function updateStatCardCharts(stats) {
// 更新平均响应时间显示
if (document.getElementById('avg-response-time')) {
let avgResponseTime = 0;
let avgResponseTime = null;
// 尝试从不同的数据结构获取平均响应时间
if (stats.dns && stats.dns.AvgResponseTime) {
if (stats.dns && stats.dns.AvgResponseTime !== undefined) {
avgResponseTime = stats.dns.AvgResponseTime;
} else if (stats.avgResponseTime !== undefined) {
avgResponseTime = stats.avgResponseTime;
} else if (stats.responseTime) {
avgResponseTime = stats.responseTime;
}
document.getElementById('avg-response-time').textContent = formatNumber(avgResponseTime);
// 保留两位小数并添加单位
const responseTime = avgResponseTime ? avgResponseTime.toFixed(2) + 'ms' : '---';
document.getElementById('avg-response-time').textContent = responseTime;
}
// 更新规则数图表

View File

@@ -8,6 +8,7 @@ function setupNavigation() {
document.getElementById('dashboard-content'),
document.getElementById('shield-content'),
document.getElementById('hosts-content'),
document.getElementById('gfwlist-content'),
document.getElementById('query-content'),
document.getElementById('logs-content'),
document.getElementById('config-content')
@@ -118,6 +119,7 @@ function initPageByHash() {
document.getElementById('dashboard-content'),
document.getElementById('shield-content'),
document.getElementById('hosts-content'),
document.getElementById('gfwlist-content'),
document.getElementById('query-content'),
document.getElementById('logs-content'),
document.getElementById('config-content')
@@ -142,6 +144,7 @@ function initPageByHash() {
'dashboard': '仪表盘',
'shield': '屏蔽管理',
'hosts': 'Hosts管理',
'gfwlist': 'GFWList管理',
'query': 'DNS屏蔽查询',
'logs': '查询日志',
'config': '系统设置'
@@ -162,6 +165,12 @@ function initPageByHash() {
initHostsPage();
}
}, 0);
} else if (hash === 'gfwlist') {
setTimeout(() => {
if (typeof initGFWListPage === 'function') {
initGFWListPage();
}
}, 0);
} else if (hash === 'logs') {
setTimeout(() => {
if (typeof initLogsPage === 'function') {