更新beta2
This commit is contained in:
File diff suppressed because it is too large
Load Diff
100
static/js/api.js
Normal file
100
static/js/api.js
Normal 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
154
static/js/config.js
Normal 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
270
static/js/dashboard.js
Normal 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
150
static/js/hosts.js
Normal 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
109
static/js/main.js
Normal 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
157
static/js/query.js
Normal 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
167
static/js/shield.js
Normal 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();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user