Files
dns-server/static/js/config.js
2026-01-25 16:13:52 +08:00

541 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 配置管理页面功能实现
// 工具函数安全获取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();
}