更新beta2

This commit is contained in:
Alex Yang
2025-11-25 01:34:20 +08:00
parent 9f92dcb86d
commit d9a8462cf6
8 changed files with 1395 additions and 719 deletions

100
static/js/api.js Normal file
View File

@@ -0,0 +1,100 @@
// API模块 - 统一管理所有API调用
// API路径定义
const API_BASE_URL = '/api';
// API请求封装
async function apiRequest(endpoint, method = 'GET', data = null) {
const url = `${API_BASE_URL}${endpoint}`;
const options = {
method,
headers: {
'Content-Type': 'application/json',
},
};
if (data) {
options.body = JSON.stringify(data);
}
try {
const response = await fetch(url, options);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || `请求失败: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('API请求错误:', error);
throw error;
}
}
// API方法集合
// API方法集合
const api = {
// 获取统计信息
getStats: () => apiRequest('/stats'),
// 获取系统状态
getStatus: () => apiRequest('/status'),
// 获取Top屏蔽域名
getTopBlockedDomains: () => apiRequest('/top-blocked'),
// 获取Top解析域名
getTopResolvedDomains: () => apiRequest('/top-resolved'),
// 获取最近屏蔽域名
getRecentBlockedDomains: () => apiRequest('/recent-blocked'),
// 获取小时统计
getHourlyStats: () => apiRequest('/hourly-stats'),
// 获取屏蔽规则
getShieldRules: () => apiRequest('/shield'),
// 添加屏蔽规则
addShieldRule: (rule) => apiRequest('/shield', 'POST', { rule }),
// 删除屏蔽规则
deleteShieldRule: (rule) => apiRequest('/shield', 'DELETE', { rule }),
// 更新远程规则
updateRemoteRules: () => apiRequest('/shield', 'PUT', { action: 'update' }),
// 获取黑名单列表
getBlacklists: () => apiRequest('/shield/blacklists'),
// 添加黑名单
addBlacklist: (url) => apiRequest('/shield/blacklists', 'POST', { url }),
// 删除黑名单
deleteBlacklist: (url) => apiRequest('/shield/blacklists', 'DELETE', { url }),
// 获取Hosts内容
getHosts: () => apiRequest('/shield/hosts'),
// 保存Hosts内容
saveHosts: (content) => apiRequest('/shield/hosts', 'POST', { content }),
// 刷新Hosts
refreshHosts: () => apiRequest('/shield/hosts', 'PUT', { action: 'refresh' }),
// 执行DNS查询
queryDNS: (domain, recordType = 'A') => apiRequest(`/query?domain=${encodeURIComponent(domain)}&type=${recordType}`),
// 获取系统配置
getConfig: () => apiRequest('/config'),
// 保存系统配置
saveConfig: (config) => apiRequest('/config', 'POST', config),
// 重启服务
restartService: () => apiRequest('/config/restart', 'POST'),
};
// 导出API工具
window.api = api;

154
static/js/config.js Normal file
View File

@@ -0,0 +1,154 @@
// 配置管理页面功能实现
// 初始化配置管理页面
function initConfigPage() {
loadConfig();
setupConfigEventListeners();
}
// 加载系统配置
async function loadConfig() {
try {
const config = await api.getConfig();
populateConfigForm(config);
} catch (error) {
showErrorMessage('加载配置失败: ' + error.message);
}
}
// 填充配置表单
function populateConfigForm(config) {
// DNS配置
document.getElementById('dns-port')?.value = config.DNSServer.Port || 53;
document.getElementById('dns-upstream-servers')?.value = (config.DNSServer.UpstreamServers || []).join(', ');
document.getElementById('dns-timeout')?.value = config.DNSServer.Timeout || 5;
document.getElementById('dns-stats-file')?.value = config.DNSServer.StatsFile || './stats.json';
document.getElementById('dns-save-interval')?.value = config.DNSServer.SaveInterval || 300;
// HTTP配置
document.getElementById('http-port')?.value = config.HTTPServer.Port || 8080;
document.getElementById('http-host')?.value = config.HTTPServer.Host || '0.0.0.0';
document.getElementById('http-api-enabled')?.checked = config.HTTPServer.APIEnabled !== false;
// 屏蔽配置
document.getElementById('shield-local-rules-file')?.value = config.Shield.LocalRulesFile || './rules.txt';
document.getElementById('shield-remote-rules-urls')?.value = (config.Shield.RemoteRulesURLs || []).join('\n');
document.getElementById('shield-update-interval')?.value = config.Shield.UpdateInterval || 3600;
document.getElementById('shield-hosts-file')?.value = config.Shield.HostsFile || '/etc/hosts';
document.getElementById('shield-block-method')?.value = config.Shield.BlockMethod || '0.0.0.0';
}
// 保存配置
async function handleSaveConfig() {
const formData = collectFormData();
try {
await api.saveConfig(formData);
showSuccessMessage('配置保存成功');
} catch (error) {
showErrorMessage('保存配置失败: ' + error.message);
}
}
// 重启服务
async function handleRestartService() {
if (confirm('确定要重启DNS服务吗重启期间服务可能会短暂不可用。')) {
try {
await api.restartService();
showSuccessMessage('服务重启成功');
} catch (error) {
showErrorMessage('重启服务失败: ' + error.message);
}
}
}
// 收集表单数据
function collectFormData() {
return {
DNSServer: {
Port: parseInt(document.getElementById('dns-port')?.value) || 53,
UpstreamServers: document.getElementById('dns-upstream-servers')?.value.split(',').map(s => s.trim()).filter(Boolean) || [],
Timeout: parseInt(document.getElementById('dns-timeout')?.value) || 5,
StatsFile: document.getElementById('dns-stats-file')?.value || './stats.json',
SaveInterval: parseInt(document.getElementById('dns-save-interval')?.value) || 300
},
HTTPServer: {
Port: parseInt(document.getElementById('http-port')?.value) || 8080,
Host: document.getElementById('http-host')?.value || '0.0.0.0',
APIEnabled: document.getElementById('http-api-enabled')?.checked !== false
},
Shield: {
LocalRulesFile: document.getElementById('shield-local-rules-file')?.value || './rules.txt',
RemoteRulesURLs: document.getElementById('shield-remote-rules-urls')?.value.split('\n').map(s => s.trim()).filter(Boolean) || [],
UpdateInterval: parseInt(document.getElementById('shield-update-interval')?.value) || 3600,
HostsFile: document.getElementById('shield-hosts-file')?.value || '/etc/hosts',
BlockMethod: document.getElementById('shield-block-method')?.value || '0.0.0.0'
}
};
}
// 设置事件监听器
function setupConfigEventListeners() {
// 保存配置按钮
document.getElementById('save-config-btn')?.addEventListener('click', handleSaveConfig);
// 重启服务按钮
document.getElementById('restart-service-btn')?.addEventListener('click', handleRestartService);
}
// 显示成功消息
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 bottom-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`;
// 设置通知样式
if (type === 'success') {
notification.classList.add('bg-green-500', 'text-white');
} else if (type === 'error') {
notification.classList.add('bg-red-500', 'text-white');
} else {
notification.classList.add('bg-blue-500', 'text-white');
}
notification.textContent = message;
document.body.appendChild(notification);
// 显示通知
setTimeout(() => {
notification.classList.remove('opacity-0');
notification.classList.add('opacity-100');
}, 10);
// 3秒后隐藏通知
setTimeout(() => {
notification.classList.remove('opacity-100');
notification.classList.add('opacity-0');
setTimeout(() => {
notification.remove();
}, 300);
}, 3000);
}
// 页面加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initConfigPage);
} else {
initConfigPage();
}

270
static/js/dashboard.js Normal file
View File

@@ -0,0 +1,270 @@
// dashboard.js - 仪表盘页面功能
// 加载仪表盘数据
async function loadDashboardData() {
try {
// 并行加载所有需要的数据
const [statsData, topBlockedData, recentBlockedData, hourlyStatsData] = await Promise.all([
api.getStats(),
api.getTopBlockedDomains(),
api.getRecentBlockedDomains(),
api.getHourlyStats()
]);
// 更新统计卡片
updateStatsCards(statsData);
// 更新表格数据
updateTopBlockedTable(topBlockedData);
updateRecentBlockedTable(recentBlockedData);
// 更新图表
updateQueryTrendChart(hourlyStatsData);
updateRatioChart(statsData);
} catch (error) {
console.error('加载仪表盘数据失败:', error);
showErrorMessage('数据加载失败,请刷新页面重试');
}
}
// 更新统计卡片
function updateStatsCards(data) {
const dnsStats = data.dns;
// 更新总查询数
document.getElementById('total-queries').textContent = formatNumber(dnsStats.Queries);
// 更新屏蔽数量
document.getElementById('blocked-queries').textContent = formatNumber(dnsStats.Blocked);
// 更新正常解析数量
document.getElementById('allowed-queries').textContent = formatNumber(dnsStats.Allowed);
// 更新错误数量
document.getElementById('error-queries').textContent = formatNumber(dnsStats.Errors);
// 计算百分比(简化计算,实际可能需要与历史数据比较)
if (dnsStats.Queries > 0) {
const blockedPercent = Math.round((dnsStats.Blocked / dnsStats.Queries) * 100);
const allowedPercent = Math.round((dnsStats.Allowed / dnsStats.Queries) * 100);
const errorPercent = Math.round((dnsStats.Errors / dnsStats.Queries) * 100);
document.getElementById('blocked-percent').textContent = `${blockedPercent}%`;
document.getElementById('allowed-percent').textContent = `${allowedPercent}%`;
document.getElementById('error-percent').textContent = `${errorPercent}%`;
document.getElementById('queries-percent').textContent = '100%';
}
}
// 更新最常屏蔽域名表格
function updateTopBlockedTable(data) {
const tableBody = document.getElementById('top-blocked-table');
tableBody.innerHTML = '';
if (data.length === 0) {
const row = document.createElement('tr');
row.innerHTML = `<td colspan="2" class="py-4 text-center text-gray-500">暂无数据</td>`;
tableBody.appendChild(row);
return;
}
data.forEach(item => {
const row = document.createElement('tr');
row.className = 'border-b border-gray-100 hover:bg-gray-50';
row.innerHTML = `
<td class="py-3 px-4 text-sm text-gray-800">${item.domain}</td>
<td class="py-3 px-4 text-sm text-gray-800 text-right">${formatNumber(item.count)}</td>
`;
tableBody.appendChild(row);
});
}
// 更新最近屏蔽域名表格
function updateRecentBlockedTable(data) {
const tableBody = document.getElementById('recent-blocked-table');
tableBody.innerHTML = '';
if (data.length === 0) {
const row = document.createElement('tr');
row.innerHTML = `<td colspan="2" class="py-4 text-center text-gray-500">暂无数据</td>`;
tableBody.appendChild(row);
return;
}
data.forEach(item => {
const row = document.createElement('tr');
row.className = 'border-b border-gray-100 hover:bg-gray-50';
row.innerHTML = `
<td class="py-3 px-4 text-sm text-gray-800">${item.domain}</td>
<td class="py-3 px-4 text-sm text-gray-500 text-right">${item.time}</td>
`;
tableBody.appendChild(row);
});
}
// 更新查询趋势图表
function updateQueryTrendChart(data) {
const ctx = document.getElementById('query-trend-chart').getContext('2d');
// 创建图表
new Chart(ctx, {
type: 'line',
data: {
labels: data.labels,
datasets: [{
label: '查询数量',
data: data.data,
borderColor: '#165DFF',
backgroundColor: 'rgba(22, 93, 255, 0.1)',
borderWidth: 2,
tension: 0.3,
fill: true,
pointBackgroundColor: '#FFFFFF',
pointBorderColor: '#165DFF',
pointBorderWidth: 2,
pointRadius: 4,
pointHoverRadius: 6
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.7)',
padding: 12,
cornerRadius: 8,
titleFont: {
size: 14,
weight: 'bold'
},
bodyFont: {
size: 13
}
}
},
scales: {
x: {
grid: {
display: false
},
ticks: {
font: {
size: 12
}
}
},
y: {
beginAtZero: true,
grid: {
color: 'rgba(0, 0, 0, 0.05)'
},
ticks: {
font: {
size: 12
},
callback: function(value) {
return formatNumber(value);
}
}
}
},
interaction: {
intersect: false,
mode: 'index'
}
}
});
}
// 更新比例图表
function updateRatioChart(data) {
const dnsStats = data.dns;
const ctx = document.getElementById('ratio-chart').getContext('2d');
// 准备数据
const chartData = [dnsStats.Allowed, dnsStats.Blocked, dnsStats.Errors];
const chartColors = ['#00B42A', '#F53F3F', '#FF7D00'];
const chartLabels = ['正常解析', '屏蔽', '错误'];
// 创建图表
new Chart(ctx, {
type: 'doughnut',
data: {
labels: chartLabels,
datasets: [{
data: chartData,
backgroundColor: chartColors,
borderWidth: 0,
hoverOffset: 10
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',',\,
labels: {
padding: 20,
font: {
size: 13
}
}
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.7)',
padding: 12,
cornerRadius: 8,
callbacks: {
label: function(context) {
const value = context.parsed;
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = total > 0 ? Math.round((value / total) * 100) : 0;
return `${context.label}: ${formatNumber(value)} (${percentage}%)`;
}
}
}
},
cutout: '70%'
}
});
}
// 格式化数字
function formatNumber(num) {
if (num >= 1000000) {
return (num / 1000000).toFixed(1) + 'M';
} else if (num >= 1000) {
return (num / 1000).toFixed(1) + 'K';
}
return num.toString();
}
// 显示错误消息
function showErrorMessage(message) {
// 创建错误消息元素
const errorElement = document.createElement('div');
errorElement.className = 'fixed bottom-4 right-4 bg-danger text-white px-6 py-3 rounded-lg shadow-lg z-50 flex items-center';
errorElement.innerHTML = `
<i class="fa fa-exclamation-circle mr-2"></i>
<span>${message}</span>
`;
document.body.appendChild(errorElement);
// 3秒后自动移除
setTimeout(() => {
errorElement.classList.add('opacity-0', 'transition-opacity', 'duration-300');
setTimeout(() => {
document.body.removeChild(errorElement);
}, 300);
}, 3000);
}
// 定期刷新数据
setInterval(loadDashboardData, 30000); // 每30秒刷新一次

150
static/js/hosts.js Normal file
View File

@@ -0,0 +1,150 @@
// Hosts管理页面功能实现
// 初始化Hosts管理页面
function initHostsPage() {
loadHostsContent();
setupHostsEventListeners();
}
// 加载Hosts内容
async function loadHostsContent() {
try {
const hostsContent = await api.getHosts();
document.getElementById('hosts-content').value = hostsContent;
} catch (error) {
showErrorMessage('加载Hosts文件失败: ' + error.message);
}
}
// 保存Hosts内容
async function handleSaveHosts() {
const hostsContent = document.getElementById('hosts-content').value;
try {
await api.saveHosts(hostsContent);
showSuccessMessage('Hosts文件保存成功');
} catch (error) {
showErrorMessage('保存Hosts文件失败: ' + error.message);
}
}
// 刷新Hosts
async function handleRefreshHosts() {
try {
await api.refreshHosts();
showSuccessMessage('Hosts刷新成功');
loadHostsContent();
} catch (error) {
showErrorMessage('刷新Hosts失败: ' + error.message);
}
}
// 添加新的Hosts条目
function handleAddHostsEntry() {
const ipInput = document.getElementById('hosts-ip');
const domainInput = document.getElementById('hosts-domain');
const ip = ipInput.value.trim();
const domain = domainInput.value.trim();
if (!ip || !domain) {
showErrorMessage('IP和域名不能为空');
return;
}
// 简单的IP验证
const ipRegex = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/;
if (!ipRegex.test(ip)) {
showErrorMessage('请输入有效的IP地址');
return;
}
const hostsTextarea = document.getElementById('hosts-content');
const newEntry = `\n${ip} ${domain}`;
hostsTextarea.value += newEntry;
// 清空输入框
ipInput.value = '';
domainInput.value = '';
// 滚动到文本区域底部
hostsTextarea.scrollTop = hostsTextarea.scrollHeight;
showSuccessMessage('Hosts条目已添加到编辑器');
}
// 设置事件监听器
function setupHostsEventListeners() {
// 保存按钮
document.getElementById('save-hosts-btn')?.addEventListener('click', handleSaveHosts);
// 刷新按钮
document.getElementById('refresh-hosts-btn')?.addEventListener('click', handleRefreshHosts);
// 添加Hosts条目按钮
document.getElementById('add-hosts-entry-btn')?.addEventListener('click', handleAddHostsEntry);
// 按回车键添加条目
document.getElementById('hosts-domain')?.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
handleAddHostsEntry();
}
});
}
// 显示成功消息
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 bottom-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`;
// 设置通知样式
if (type === 'success') {
notification.classList.add('bg-green-500', 'text-white');
} else if (type === 'error') {
notification.classList.add('bg-red-500', 'text-white');
} else {
notification.classList.add('bg-blue-500', 'text-white');
}
notification.textContent = message;
document.body.appendChild(notification);
// 显示通知
setTimeout(() => {
notification.classList.remove('opacity-0');
notification.classList.add('opacity-100');
}, 10);
// 3秒后隐藏通知
setTimeout(() => {
notification.classList.remove('opacity-100');
notification.classList.add('opacity-0');
setTimeout(() => {
notification.remove();
}, 300);
}, 3000);
}
// 页面加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initHostsPage);
} else {
initHostsPage();
}

109
static/js/main.js Normal file
View File

@@ -0,0 +1,109 @@
// main.js - 主脚本文件
// 页面导航功能
function setupNavigation() {
// 侧边栏菜单项
const menuItems = document.querySelectorAll('nav a');
const contentSections = [
document.getElementById('dashboard-content'),
document.getElementById('shield-content'),
document.getElementById('hosts-content'),
document.getElementById('blacklists-content'),
document.getElementById('query-content'),
document.getElementById('config-content')
];
const pageTitle = document.getElementById('page-title');
menuItems.forEach((item, index) => {
item.addEventListener('click', (e) => {
e.preventDefault();
// 更新活跃状态
menuItems.forEach(menuItem => {
menuItem.classList.remove('sidebar-item-active');
});
item.classList.add('sidebar-item-active');
// 隐藏所有内容部分
contentSections.forEach(section => {
section.classList.add('hidden');
});
// 显示对应内容部分
const target = item.getAttribute('href').substring(1);
const activeContent = document.getElementById(`${target}-content`);
if (activeContent) {
activeContent.classList.remove('hidden');
}
// 更新页面标题
pageTitle.textContent = item.querySelector('span').textContent;
});
});
// 移动端侧边栏切换
const toggleSidebar = document.getElementById('toggle-sidebar');
const sidebar = document.getElementById('sidebar');
if (toggleSidebar && sidebar) {
toggleSidebar.addEventListener('click', () => {
sidebar.classList.toggle('-translate-x-full');
});
}
}
// 初始化函数
function init() {
// 设置导航
setupNavigation();
// 加载仪表盘数据
if (typeof loadDashboardData === 'function') {
loadDashboardData();
}
// 定期更新系统状态
setInterval(updateSystemStatus, 5000);
}
// 更新系统状态
function updateSystemStatus() {
fetch('/api/status')
.then(response => response.json())
.then(data => {
const uptimeElement = document.getElementById('uptime');
if (uptimeElement) {
uptimeElement.textContent = `正常运行中 | ${formatUptime(data.uptime)}`;
}
})
.catch(error => {
console.error('更新系统状态失败:', error);
const uptimeElement = document.getElementById('uptime');
if (uptimeElement) {
uptimeElement.textContent = '连接异常';
uptimeElement.classList.add('text-danger');
}
});
}
// 格式化运行时间
function formatUptime(milliseconds) {
// 简化版的格式化实际使用时需要根据API返回的数据格式调整
const seconds = Math.floor(milliseconds / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (days > 0) {
return `${days}${hours % 24}小时`;
} else if (hours > 0) {
return `${hours}小时${minutes % 60}分钟`;
} else if (minutes > 0) {
return `${minutes}分钟${seconds % 60}`;
} else {
return `${seconds}`;
}
}
// 页面加载完成后执行初始化
window.addEventListener('DOMContentLoaded', init);

157
static/js/query.js Normal file
View File

@@ -0,0 +1,157 @@
// DNS查询工具页面功能实现
// 初始化查询工具页面
function initQueryPage() {
setupQueryEventListeners();
}
// 执行DNS查询
async function handleDNSQuery() {
const domainInput = document.getElementById('query-domain');
const recordTypeSelect = document.getElementById('query-record-type');
const resultDiv = document.getElementById('query-result');
const domain = domainInput.value.trim();
const recordType = recordTypeSelect.value;
if (!domain) {
showErrorMessage('请输入域名');
return;
}
// 清空之前的结果
resultDiv.innerHTML = '<div class="text-center py-4"><i class="fa fa-spinner fa-spin text-primary"></i> 查询中...</div>';
try {
const result = await api.queryDNS(domain, recordType);
displayQueryResult(result, domain, recordType);
} catch (error) {
resultDiv.innerHTML = `<div class="text-red-500 text-center py-4">查询失败: ${error.message}</div>`;
}
}
// 显示查询结果
function displayQueryResult(result, domain, recordType) {
const resultDiv = document.getElementById('query-result');
if (!result || result.length === 0) {
resultDiv.innerHTML = `<div class="text-gray-500 text-center py-4">未找到 ${domain}${recordType} 记录</div>`;
return;
}
// 创建结果表格
let html = `
<div class="mb-4">
<h3 class="text-lg font-medium text-gray-800 mb-2">查询结果: ${domain} (${recordType})</h3>
<div class="overflow-x-auto">
<table class="min-w-full bg-white rounded-lg overflow-hidden">
<thead class="bg-gray-100">
<tr>
<th class="py-2 px-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">类型</th>
<th class="py-2 px-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">值</th>
<th class="py-2 px-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">TTL</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
`;
// 添加查询结果
result.forEach(record => {
const type = record.Type || recordType;
let value = record.Value;
// 格式化不同类型的记录值
if (type === 'MX' && record.Preference) {
value = `${record.Preference} ${value}`;
} else if (type === 'SRV') {
value = `${record.Priority} ${record.Weight} ${record.Port} ${value}`;
}
html += `
<tr>
<td class="py-2 px-4 text-sm text-gray-900">${type}</td>
<td class="py-2 px-4 text-sm text-gray-900 font-mono break-all">${value}</td>
<td class="py-2 px-4 text-sm text-gray-500">${record.TTL || '-'}</td>
</tr>
`;
});
html += `
</tbody>
</table>
</div>
</div>
`;
resultDiv.innerHTML = html;
}
// 设置事件监听器
function setupQueryEventListeners() {
// 查询按钮
document.getElementById('query-btn')?.addEventListener('click', handleDNSQuery);
// 按回车键查询
document.getElementById('query-domain')?.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
handleDNSQuery();
}
});
}
// 显示成功消息
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 bottom-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`;
// 设置通知样式
if (type === 'success') {
notification.classList.add('bg-green-500', 'text-white');
} else if (type === 'error') {
notification.classList.add('bg-red-500', 'text-white');
} else {
notification.classList.add('bg-blue-500', 'text-white');
}
notification.textContent = message;
document.body.appendChild(notification);
// 显示通知
setTimeout(() => {
notification.classList.remove('opacity-0');
notification.classList.add('opacity-100');
}, 10);
// 3秒后隐藏通知
setTimeout(() => {
notification.classList.remove('opacity-100');
notification.classList.add('opacity-0');
setTimeout(() => {
notification.remove();
}, 300);
}, 3000);
}
// 页面加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initQueryPage);
} else {
initQueryPage();
}

167
static/js/shield.js Normal file
View File

@@ -0,0 +1,167 @@
// 屏蔽管理页面功能实现
// 初始化屏蔽管理页面
function initShieldPage() {
loadShieldRules();
setupShieldEventListeners();
}
// 加载屏蔽规则
async function loadShieldRules() {
try {
const rules = await api.getShieldRules();
updateShieldRulesTable(rules);
} catch (error) {
showErrorMessage('加载屏蔽规则失败: ' + error.message);
}
}
// 更新屏蔽规则表格
function updateShieldRulesTable(rules) {
const tbody = document.getElementById('shield-rules-tbody');
tbody.innerHTML = '';
if (!rules || rules.length === 0) {
tbody.innerHTML = '<tr><td colspan="4" class="text-center py-4 text-gray-500">暂无屏蔽规则</td></tr>';
return;
}
rules.forEach((rule, index) => {
const tr = document.createElement('tr');
tr.className = 'border-b border-gray-200 hover:bg-gray-50';
tr.innerHTML = `
<td class="py-3 px-4">${index + 1}</td>
<td class="py-3 px-4">${rule}</td>
<td class="py-3 px-4">
<button data-rule="${rule}" class="delete-rule-btn text-red-500 hover:text-red-700">
<i class="fa fa-trash-o"></i>
</button>
</td>
`;
tbody.appendChild(tr);
});
// 添加删除按钮事件监听器
document.querySelectorAll('.delete-rule-btn').forEach(btn => {
btn.addEventListener('click', handleDeleteRule);
});
}
// 处理删除规则
async function handleDeleteRule(e) {
const rule = e.currentTarget.getAttribute('data-rule');
if (confirm(`确定要删除规则: ${rule} 吗?`)) {
try {
await api.deleteShieldRule(rule);
showSuccessMessage('规则删除成功');
loadShieldRules();
} catch (error) {
showErrorMessage('删除规则失败: ' + error.message);
}
}
}
// 添加新规则
async function handleAddRule() {
const ruleInput = document.getElementById('new-rule-input');
const rule = ruleInput.value.trim();
if (!rule) {
showErrorMessage('规则不能为空');
return;
}
try {
await api.addShieldRule(rule);
showSuccessMessage('规则添加成功');
loadShieldRules();
ruleInput.value = '';
} catch (error) {
showErrorMessage('添加规则失败: ' + error.message);
}
}
// 更新远程规则
async function handleUpdateRemoteRules() {
try {
await api.updateRemoteRules();
showSuccessMessage('远程规则更新成功');
loadShieldRules();
} catch (error) {
showErrorMessage('远程规则更新失败: ' + error.message);
}
}
// 设置事件监听器
function setupShieldEventListeners() {
// 添加规则按钮
document.getElementById('add-rule-btn')?.addEventListener('click', handleAddRule);
// 按回车键添加规则
document.getElementById('new-rule-input')?.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
handleAddRule();
}
});
// 更新远程规则按钮
document.getElementById('update-remote-rules-btn')?.addEventListener('click', handleUpdateRemoteRules);
}
// 显示成功消息
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 bottom-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`;
// 设置通知样式
if (type === 'success') {
notification.classList.add('bg-green-500', 'text-white');
} else if (type === 'error') {
notification.classList.add('bg-red-500', 'text-white');
} else {
notification.classList.add('bg-blue-500', 'text-white');
}
notification.textContent = message;
document.body.appendChild(notification);
// 显示通知
setTimeout(() => {
notification.classList.remove('opacity-0');
notification.classList.add('opacity-100');
}, 10);
// 3秒后隐藏通知
setTimeout(() => {
notification.classList.remove('opacity-100');
notification.classList.add('opacity-0');
setTimeout(() => {
notification.remove();
}, 300);
}, 3000);
}
// 页面加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initShieldPage);
} else {
initShieldPage();
}