优化修复
This commit is contained in:
@@ -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": "日志 / 数据上报核心服务",
|
||||
|
||||
@@ -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
BIN
static/images/user.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -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 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 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>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
// 更新规则数图表
|
||||
|
||||
@@ -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') {
|
||||
|
||||
Reference in New Issue
Block a user