541 lines
18 KiB
JavaScript
541 lines
18 KiB
JavaScript
// 配置管理页面功能实现
|
||
|
||
// 工具函数:安全获取DOM元素
|
||
function getElement(id) {
|
||
const element = document.getElementById(id);
|
||
if (!element) {
|
||
console.warn(`Element with id "${id}" not found`);
|
||
}
|
||
return element;
|
||
}
|
||
|
||
// 工具函数:验证端口号
|
||
function validatePort(port) {
|
||
// 确保port是字符串类型
|
||
var portStr = port;
|
||
if (port === null || port === undefined || typeof port !== 'string') {
|
||
return null;
|
||
}
|
||
|
||
// 去除前后空白并验证是否为纯数字
|
||
portStr = port.trim();
|
||
if (!/^\d+$/.test(portStr)) {
|
||
return null;
|
||
}
|
||
|
||
const num = parseInt(portStr, 10);
|
||
return num >= 1 && num <= 65535 ? num : null;
|
||
}
|
||
|
||
// 初始化配置管理页面
|
||
function initConfigPage() {
|
||
loadConfig();
|
||
setupConfigEventListeners();
|
||
}
|
||
|
||
// 加载系统配置
|
||
async function loadConfig() {
|
||
try {
|
||
const result = await api.getConfig();
|
||
|
||
// 检查API返回的错误
|
||
if (result && result.error) {
|
||
showErrorMessage('加载配置失败: ' + result.error);
|
||
return;
|
||
}
|
||
|
||
populateConfigForm(result);
|
||
} catch (error) {
|
||
// 捕获可能的异常(虽然apiRequest不应该再抛出异常)
|
||
showErrorMessage('加载配置失败: ' + (error.message || '未知错误'));
|
||
}
|
||
}
|
||
|
||
// 填充配置表单
|
||
function populateConfigForm(config) {
|
||
// 安全获取配置对象,防止未定义属性访问
|
||
const dnsServerConfig = config.DNSServer || {};
|
||
const httpServerConfig = config.HTTPServer || {};
|
||
const shieldConfig = config.Shield || {};
|
||
|
||
// DNS配置 - 使用函数安全设置值,避免 || 操作符可能的错误处理
|
||
setElementValue('dns-port', getSafeValue(dnsServerConfig.Port, 53));
|
||
setElementValue('dns-run-mode', getSafeValue(dnsServerConfig.QueryMode, 'parallel'));
|
||
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-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));
|
||
// 屏蔽配置
|
||
//setElementValue('shield-local-rules-file', getSafeValue(shieldConfig.LocalRulesFile, 'data/rules.txt'));
|
||
setElementValue('shield-update-interval', getSafeValue(shieldConfig.UpdateInterval, 3600));
|
||
//setElementValue('shield-hosts-file', getSafeValue(shieldConfig.HostsFile, 'data/hosts.txt'));
|
||
// 使用服务器端接受的屏蔽方法值,默认使用NXDOMAIN, 可选值: NXDOMAIN, NULL, REFUSED
|
||
setElementValue('shield-block-method', getSafeValue(shieldConfig.BlockMethod, 'NXDOMAIN'));
|
||
setElementValue('shield-custom-block-ip', getSafeValue(shieldConfig.CustomBlockIP, ''));
|
||
|
||
// 初始加载时更新自定义屏蔽IP输入框的可见性
|
||
updateCustomBlockIpVisibility();
|
||
}
|
||
|
||
// 工具函数:安全设置元素值
|
||
function setElementValue(elementId, value) {
|
||
const element = document.getElementById(elementId);
|
||
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 === 'SELECT') {
|
||
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 {
|
||
console.warn(`Element with id "${elementId}" not found for setting value: ${value}`);
|
||
}
|
||
}
|
||
|
||
// 工具函数:安全获取值,如果未定义或为null则返回默认值
|
||
function getSafeValue(value, defaultValue) {
|
||
// 更严格的检查,避免0、空字符串等被默认值替换
|
||
return value === undefined || value === null ? defaultValue : value;
|
||
}
|
||
|
||
// 工具函数:安全获取数组,如果不是数组则返回空数组
|
||
function getSafeArray(value) {
|
||
return Array.isArray(value) ? value : [];
|
||
}
|
||
|
||
// 保存配置
|
||
async function handleSaveConfig() {
|
||
const formData = collectFormData();
|
||
if (!formData) return;
|
||
|
||
try {
|
||
const result = await api.saveConfig(formData);
|
||
|
||
// 检查API返回的错误
|
||
if (result && result.error) {
|
||
showErrorMessage('保存配置失败: ' + result.error);
|
||
return;
|
||
}
|
||
|
||
showSuccessMessage('配置保存成功');
|
||
} catch (error) {
|
||
// 捕获可能的异常(虽然apiRequest不应该再抛出异常)
|
||
showErrorMessage('保存配置失败: ' + (error.message || '未知错误'));
|
||
}
|
||
}
|
||
|
||
// 重启服务
|
||
async function handleRestartService() {
|
||
if (!confirm('确定要重启DNS服务吗?重启期间服务可能会短暂不可用。')) return;
|
||
|
||
try {
|
||
const result = await api.restartService();
|
||
|
||
// 检查API返回的错误
|
||
if (result && result.error) {
|
||
showErrorMessage('服务重启失败: ' + result.error);
|
||
return;
|
||
}
|
||
|
||
showSuccessMessage('服务重启成功');
|
||
} catch (error) {
|
||
// 捕获可能的异常(虽然apiRequest不应该再抛出异常)
|
||
showErrorMessage('重启服务失败: ' + (error.message || '未知错误'));
|
||
}
|
||
}
|
||
|
||
// 收集表单数据并验证
|
||
function collectFormData() {
|
||
// 验证端口号 - 使用安全获取元素值的函数
|
||
const dnsPortValue = getElementValue('dns-port');
|
||
const httpPortValue = getElementValue('http-port');
|
||
|
||
const dnsPort = validatePort(dnsPortValue);
|
||
const httpPort = validatePort(httpPortValue);
|
||
|
||
if (!dnsPort) {
|
||
showErrorMessage('DNS端口号无效(必须是1-65535之间的整数)');
|
||
return null;
|
||
}
|
||
|
||
if (!httpPort) {
|
||
showErrorMessage('HTTP端口号无效(必须是1-65535之间的整数)');
|
||
return null;
|
||
}
|
||
|
||
// 安全获取上游服务器列表
|
||
const upstreamServersText = getElementValue('dns-upstream-servers');
|
||
const upstreamServers = upstreamServersText ?
|
||
upstreamServersText.split('\n').map(function(s) { return s.trim(); }).filter(function(s) { return s !== ''; }) :
|
||
[];
|
||
|
||
const dnssecUpstreamServersText = getElementValue('dns-dnssec-upstream-servers');
|
||
const dnssecUpstreamServers = dnssecUpstreamServersText ?
|
||
dnssecUpstreamServersText.split('\n').map(function(s) { return s.trim(); }).filter(function(s) { return s !== ''; }) :
|
||
[];
|
||
|
||
// 安全获取并转换整数值
|
||
const timeoutValue = getElementValue('dns-timeout');
|
||
const timeout = timeoutValue ? parseInt(timeoutValue, 10) : 5;
|
||
|
||
const saveIntervalValue = getElementValue('dns-save-interval');
|
||
const saveInterval = saveIntervalValue ? parseInt(saveIntervalValue, 10) : 300;
|
||
|
||
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,
|
||
queryMode: getElementValue('dns-run-mode') || 'parallel',
|
||
upstreamServers: upstreamServers,
|
||
dnssecUpstreamServers: dnssecUpstreamServers,
|
||
timeout: timeout,
|
||
saveInterval: saveInterval,
|
||
enableIPv6: getElementValue('dns-enable-ipv6'),
|
||
cacheMode: getElementValue('dns-cache-mode') || 'memory',
|
||
cacheSize: cacheSize,
|
||
maxCacheTTL: maxCacheTTL,
|
||
minCacheTTL: minCacheTTL
|
||
},
|
||
httpserver: {
|
||
port: httpPort
|
||
},
|
||
shield: {
|
||
updateInterval: updateInterval,
|
||
blockMethod: getElementValue('shield-block-method') || 'NXDOMAIN',
|
||
customBlockIP: getElementValue('shield-custom-block-ip')
|
||
}
|
||
};
|
||
}
|
||
|
||
// 工具函数:安全获取元素值
|
||
function getElementValue(elementId) {
|
||
const element = document.getElementById(elementId);
|
||
if (element) {
|
||
if (element.tagName === 'INPUT') {
|
||
if (element.type === 'checkbox') {
|
||
return element.checked;
|
||
}
|
||
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() {
|
||
const saveConfigBtn = getElement('save-config-btn');
|
||
if (saveConfigBtn) {
|
||
saveConfigBtn.addEventListener('click', handleSaveConfig);
|
||
}
|
||
|
||
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);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
// 显示成功消息
|
||
function showSuccessMessage(message) {
|
||
showNotification(message, 'success');
|
||
}
|
||
|
||
// 显示错误消息
|
||
function showErrorMessage(message) {
|
||
showNotification(message, 'error');
|
||
}
|
||
|
||
// 显示通知
|
||
function showNotification(message, type = 'info') {
|
||
// 移除现有通知
|
||
const existingNotification = document.querySelector('.notification');
|
||
if (existingNotification) {
|
||
existingNotification.remove();
|
||
}
|
||
|
||
// 创建新通知
|
||
const notification = document.createElement('div');
|
||
notification.className = `notification fixed top-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 transform transition-transform duration-300 ease-in-out translate-y-0 opacity-0`;
|
||
|
||
// 设置通知样式(兼容Tailwind和原生CSS)
|
||
notification.style.cssText += `
|
||
position: fixed;
|
||
top: 16px;
|
||
right: 16px;
|
||
padding: 16px 24px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||
z-index: 1000;
|
||
transition: all 0.3s ease;
|
||
opacity: 0;
|
||
`;
|
||
|
||
if (type === 'success') {
|
||
notification.style.backgroundColor = '#10b981';
|
||
notification.style.color = 'white';
|
||
notification.innerHTML = `<i class="fa fa-check-circle mr-2"></i>${message}`;
|
||
} else if (type === 'error') {
|
||
notification.style.backgroundColor = '#ef4444';
|
||
notification.style.color = 'white';
|
||
notification.innerHTML = `<i class="fa fa-exclamation-circle mr-2"></i>${message}`;
|
||
} else {
|
||
notification.style.backgroundColor = '#3b82f6';
|
||
notification.style.color = 'white';
|
||
notification.innerHTML = `<i class="fa fa-info-circle mr-2"></i>${message}`;
|
||
}
|
||
|
||
document.body.appendChild(notification);
|
||
|
||
// 显示通知
|
||
setTimeout(() => {
|
||
notification.style.opacity = '1';
|
||
}, 10);
|
||
|
||
// 3秒后隐藏通知
|
||
setTimeout(() => {
|
||
notification.style.opacity = '0';
|
||
setTimeout(() => {
|
||
notification.remove();
|
||
}, 300);
|
||
}, 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 || {};
|
||
const enabled = getSafeValue(gfwListConfig.enabled, false);
|
||
|
||
setElementValue('gfwlist-enabled', enabled);
|
||
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));
|
||
setElementValue('gfwlist-amazon', getSafeValue(config.allowAmazon, false));
|
||
setElementValue('gfwlist-bbc', getSafeValue(config.allowBBC, false));
|
||
setElementValue('gfwlist-discord', getSafeValue(config.allowDiscord, false));
|
||
setElementValue('gfwlist-dropbox', getSafeValue(config.allowDropbox, false));
|
||
setElementValue('gfwlist-microsoft', getSafeValue(config.allowMicrosoft, false));
|
||
setElementValue('gfwlist-steam', getSafeValue(config.allowSteam, false));
|
||
setElementValue('gfwlist-telegram', getSafeValue(config.allowTelegram, false));
|
||
setElementValue('gfwlist-tiktok', getSafeValue(config.allowTikTok, false));
|
||
setElementValue('gfwlist-v2ex', getSafeValue(config.allowV2EX, false));
|
||
setElementValue('gfwlist-wikimedia', getSafeValue(config.allowWikimedia, false));
|
||
setElementValue('gfwlist-yahoo', getSafeValue(config.allowYahoo, false));
|
||
|
||
// 更新通行网站部分的显示效果
|
||
updateAllowedSitesSection(enabled);
|
||
}
|
||
|
||
// 保存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'),
|
||
allowAmazon: getElementValue('gfwlist-amazon'),
|
||
allowBBC: getElementValue('gfwlist-bbc'),
|
||
allowDiscord: getElementValue('gfwlist-discord'),
|
||
allowDropbox: getElementValue('gfwlist-dropbox'),
|
||
allowMicrosoft: getElementValue('gfwlist-microsoft'),
|
||
allowSteam: getElementValue('gfwlist-steam'),
|
||
allowTelegram: getElementValue('gfwlist-telegram'),
|
||
allowTikTok: getElementValue('gfwlist-tiktok'),
|
||
allowV2EX: getElementValue('gfwlist-v2ex'),
|
||
allowWikimedia: getElementValue('gfwlist-wikimedia'),
|
||
allowYahoo: getElementValue('gfwlist-yahoo')
|
||
};
|
||
}
|
||
|
||
// 更新通行网站部分的显示效果
|
||
function updateAllowedSitesSection(enabled) {
|
||
const section = document.getElementById('allowed-sites-section');
|
||
if (!section) return;
|
||
|
||
const siteCards = section.querySelectorAll('.bg-gray-50');
|
||
const siteLabels = section.querySelectorAll('.text-gray-700');
|
||
const siteToggles = section.querySelectorAll('.toggle-btn');
|
||
|
||
if (enabled) {
|
||
// GFWList已启用,显示彩色且可点击
|
||
section.classList.remove('opacity-50');
|
||
siteCards.forEach(card => {
|
||
card.style.filter = 'grayscale(0%)';
|
||
});
|
||
siteToggles.forEach(toggle => {
|
||
toggle.disabled = false;
|
||
toggle.classList.remove('cursor-not-allowed');
|
||
});
|
||
} else {
|
||
// GFWList已禁用,显示灰色且不可点击
|
||
section.classList.add('opacity-50');
|
||
siteCards.forEach(card => {
|
||
card.style.filter = 'grayscale(100%)';
|
||
});
|
||
siteToggles.forEach(toggle => {
|
||
toggle.disabled = true;
|
||
toggle.classList.add('cursor-not-allowed');
|
||
});
|
||
}
|
||
}
|
||
|
||
// 重启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);
|
||
|
||
// 如果是GFWList启用开关,更新通行网站部分的显示效果
|
||
if (this.id === 'gfwlist-enabled') {
|
||
updateAllowedSitesSection(!currentState);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// 页面加载完成后初始化
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', initConfigPage);
|
||
} else {
|
||
initConfigPage();
|
||
} |