Files
dns-server/static/js/shield.js
2025-11-27 19:58:16 +08:00

561 lines
17 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.
// 屏蔽管理页面功能实现
// 初始化屏蔽管理页面
function initShieldPage() {
// 加载屏蔽规则统计信息
loadShieldStats();
// 加载本地规则
loadLocalRules();
// 加载远程黑名单
loadRemoteBlacklists();
// 加载hosts条目
loadHostsEntries();
// 设置事件监听器
setupShieldEventListeners();
}
// 加载屏蔽规则统计信息
async function loadShieldStats() {
showLoading('加载屏蔽规则统计信息...');
try {
const response = await fetch('/api/shield');
if (!response.ok) {
throw new Error('Failed to load shield stats');
}
const stats = await response.json();
// 更新统计信息
document.getElementById('domain-rules-count').textContent = stats.domainRulesCount || 0;
document.getElementById('domain-exceptions-count').textContent = stats.domainExceptionsCount || 0;
document.getElementById('regex-rules-count').textContent = stats.regexRulesCount || 0;
document.getElementById('regex-exceptions-count').textContent = stats.regexExceptionsCount || 0;
document.getElementById('hosts-rules-count').textContent = stats.hostsRulesCount || 0;
document.getElementById('blacklist-count').textContent = stats.blacklistCount || 0;
hideLoading();
} catch (error) {
console.error('Error loading shield stats:', error);
showErrorMessage('加载屏蔽规则统计信息失败');
hideLoading();
}
}
// 加载本地规则
async function loadLocalRules() {
showLoading('加载本地规则...');
try {
const response = await fetch('/api/shield');
if (!response.ok) {
throw new Error('Failed to load local rules');
}
// 注意当前API不返回完整规则列表这里只是示例
// 实际实现需要后端提供获取本地规则的API
const rules = [];
updateRulesTable(rules);
hideLoading();
} catch (error) {
console.error('Error loading local rules:', error);
showErrorMessage('加载本地规则失败');
hideLoading();
}
}
// 更新规则表格
function updateRulesTable(rules) {
const tbody = document.getElementById('rules-table-body');
if (rules.length === 0) {
tbody.innerHTML = '<tr><td colspan="2" class="py-4 text-center text-gray-500">暂无规则</td></tr>';
return;
}
tbody.innerHTML = rules.map(rule => `
<tr class="border-b border-gray-200">
<td class="py-3 px-4">${rule}</td>
<td class="py-3 px-4 text-right">
<button class="delete-rule-btn px-3 py-1 bg-danger text-white rounded-md hover:bg-danger/90 transition-colors text-sm" data-rule="${rule}">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
`).join('');
// 重新绑定删除事件
document.querySelectorAll('.delete-rule-btn').forEach(btn => {
btn.addEventListener('click', handleDeleteRule);
});
}
// 处理删除规则
async function handleDeleteRule(e) {
const rule = e.target.closest('.delete-rule-btn').dataset.rule;
showLoading('删除规则中...');
try {
const response = await fetch('/api/shield', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ rule })
});
if (!response.ok) {
throw new Error('Failed to delete rule');
}
showSuccessMessage('规则删除成功');
// 重新加载规则
loadLocalRules();
// 重新加载统计信息
loadShieldStats();
hideLoading();
} catch (error) {
console.error('Error deleting rule:', error);
showErrorMessage('删除规则失败');
hideLoading();
}
}
// 添加新规则
async function handleAddRule() {
const rule = document.getElementById('new-rule').value.trim();
if (!rule) {
showErrorMessage('规则不能为空');
return;
}
showLoading('添加规则中...');
try {
const response = await fetch('/api/shield', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ rule })
});
if (!response.ok) {
throw new Error('Failed to add rule');
}
showSuccessMessage('规则添加成功');
// 清空输入框
document.getElementById('new-rule').value = '';
// 隐藏表单
document.getElementById('add-rule-form').classList.add('hidden');
// 重新加载规则
loadLocalRules();
// 重新加载统计信息
loadShieldStats();
hideLoading();
} catch (error) {
console.error('Error adding rule:', error);
showErrorMessage('添加规则失败');
hideLoading();
}
}
// 加载远程黑名单
async function loadRemoteBlacklists() {
showLoading('加载远程黑名单...');
try {
const response = await fetch('/api/shield/blacklists');
if (!response.ok) {
throw new Error('Failed to load remote blacklists');
}
const blacklists = await response.json();
updateBlacklistsTable(blacklists);
hideLoading();
} catch (error) {
console.error('Error loading remote blacklists:', error);
showErrorMessage('加载远程黑名单失败');
hideLoading();
}
}
// 更新黑名单表格
function updateBlacklistsTable(blacklists) {
const tbody = document.getElementById('blacklists-table-body');
if (blacklists.length === 0) {
tbody.innerHTML = '<tr><td colspan="4" class="py-4 text-center text-gray-500">暂无黑名单</td></tr>';
return;
}
tbody.innerHTML = blacklists.map(blacklist => `
<tr class="border-b border-gray-200">
<td class="py-3 px-4">${blacklist.Name}</td>
<td class="py-3 px-4 truncate max-w-xs">${blacklist.URL}</td>
<td class="py-3 px-4 text-center">
<span class="inline-block w-3 h-3 rounded-full ${blacklist.Enabled ? 'bg-success' : 'bg-gray-300'}"></span>
</td>
<td class="py-3 px-4 text-right space-x-2">
<button class="update-blacklist-btn px-3 py-1 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors text-sm" data-url="${blacklist.URL}">
<i class="fa fa-refresh"></i>
</button>
<button class="delete-blacklist-btn px-3 py-1 bg-danger text-white rounded-md hover:bg-danger/90 transition-colors text-sm" data-url="${blacklist.URL}">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
`).join('');
// 重新绑定事件
document.querySelectorAll('.update-blacklist-btn').forEach(btn => {
btn.addEventListener('click', handleUpdateBlacklist);
});
document.querySelectorAll('.delete-blacklist-btn').forEach(btn => {
btn.addEventListener('click', handleDeleteBlacklist);
});
}
// 处理更新单个黑名单
async function handleUpdateBlacklist(e) {
const url = e.target.closest('.update-blacklist-btn').dataset.url;
showLoading('更新黑名单中...');
try {
const response = await fetch(`/api/shield/blacklists/${encodeURIComponent(url)}/update`, {
method: 'POST'
});
if (!response.ok) {
throw new Error('Failed to update blacklist');
}
showSuccessMessage('黑名单更新成功');
// 重新加载黑名单
loadRemoteBlacklists();
// 重新加载统计信息
loadShieldStats();
hideLoading();
} catch (error) {
console.error('Error updating blacklist:', error);
showErrorMessage('更新黑名单失败');
hideLoading();
}
}
// 处理删除黑名单
async function handleDeleteBlacklist(e) {
const url = e.target.closest('.delete-blacklist-btn').dataset.url;
showLoading('删除黑名单中...');
try {
const response = await fetch(`/api/shield/blacklists/${encodeURIComponent(url)}`, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error('Failed to delete blacklist');
}
showSuccessMessage('黑名单删除成功');
// 重新加载黑名单
loadRemoteBlacklists();
// 重新加载统计信息
loadShieldStats();
hideLoading();
} catch (error) {
console.error('Error deleting blacklist:', error);
showErrorMessage('删除黑名单失败');
hideLoading();
}
}
// 处理添加黑名单
async function handleAddBlacklist() {
const name = document.getElementById('blacklist-name').value.trim();
const url = document.getElementById('blacklist-url').value.trim();
if (!name || !url) {
showErrorMessage('名称和URL不能为空');
return;
}
showLoading('添加黑名单中...');
try {
const response = await fetch('/api/shield/blacklists', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name, url })
});
if (!response.ok) {
throw new Error('Failed to add blacklist');
}
showSuccessMessage('黑名单添加成功');
// 清空输入框
document.getElementById('blacklist-name').value = '';
document.getElementById('blacklist-url').value = '';
// 隐藏表单
document.getElementById('add-blacklist-form').classList.add('hidden');
// 重新加载黑名单
loadRemoteBlacklists();
// 重新加载统计信息
loadShieldStats();
hideLoading();
} catch (error) {
console.error('Error adding blacklist:', error);
showErrorMessage('添加黑名单失败');
hideLoading();
}
}
// 加载hosts条目
async function loadHostsEntries() {
showLoading('加载Hosts条目...');
try {
const response = await fetch('/api/shield/hosts');
if (!response.ok) {
throw new Error('Failed to load hosts entries');
}
const data = await response.json();
updateHostsTable(data.hosts || []);
hideLoading();
} catch (error) {
console.error('Error loading hosts entries:', error);
showErrorMessage('加载Hosts条目失败');
hideLoading();
}
}
// 更新hosts表格
function updateHostsTable(hosts) {
const tbody = document.getElementById('hosts-table-body');
if (hosts.length === 0) {
tbody.innerHTML = '<tr><td colspan="3" class="py-4 text-center text-gray-500">暂无Hosts条目</td></tr>';
return;
}
tbody.innerHTML = hosts.map(entry => `
<tr class="border-b border-gray-200">
<td class="py-3 px-4">${entry.ip}</td>
<td class="py-3 px-4">${entry.domain}</td>
<td class="py-3 px-4 text-right">
<button class="delete-hosts-btn px-3 py-1 bg-danger text-white rounded-md hover:bg-danger/90 transition-colors text-sm" data-domain="${entry.domain}">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
`).join('');
// 重新绑定删除事件
document.querySelectorAll('.delete-hosts-btn').forEach(btn => {
btn.addEventListener('click', handleDeleteHostsEntry);
});
}
// 处理删除hosts条目
async function handleDeleteHostsEntry(e) {
const domain = e.target.closest('.delete-hosts-btn').dataset.domain;
showLoading('删除Hosts条目...');
try {
const response = await fetch('/api/shield/hosts', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ domain })
});
if (!response.ok) {
throw new Error('Failed to delete hosts entry');
}
showSuccessMessage('Hosts条目删除成功');
// 重新加载hosts条目
loadHostsEntries();
// 重新加载统计信息
loadShieldStats();
hideLoading();
} catch (error) {
console.error('Error deleting hosts entry:', error);
showErrorMessage('删除Hosts条目失败');
hideLoading();
}
}
// 处理添加hosts条目
async function handleAddHostsEntry() {
const ip = document.getElementById('hosts-ip').value.trim();
const domain = document.getElementById('hosts-domain').value.trim();
if (!ip || !domain) {
showErrorMessage('IP地址和域名不能为空');
return;
}
showLoading('添加Hosts条目...');
try {
const response = await fetch('/api/shield/hosts', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ ip, domain })
});
if (!response.ok) {
throw new Error('Failed to add hosts entry');
}
showSuccessMessage('Hosts条目添加成功');
// 清空输入框
document.getElementById('hosts-ip').value = '';
document.getElementById('hosts-domain').value = '';
// 隐藏表单
document.getElementById('add-hosts-form').classList.add('hidden');
// 重新加载hosts条目
loadHostsEntries();
// 重新加载统计信息
loadShieldStats();
hideLoading();
} catch (error) {
console.error('Error adding hosts entry:', error);
showErrorMessage('添加Hosts条目失败');
hideLoading();
}
}
// 设置事件监听器
function setupShieldEventListeners() {
// 本地规则管理事件
document.getElementById('add-rule-btn').addEventListener('click', () => {
document.getElementById('add-rule-form').classList.toggle('hidden');
});
document.getElementById('save-rule-btn').addEventListener('click', handleAddRule);
document.getElementById('cancel-rule-btn').addEventListener('click', () => {
document.getElementById('add-rule-form').classList.add('hidden');
document.getElementById('new-rule').value = '';
});
// 远程黑名单管理事件
document.getElementById('add-blacklist-btn').addEventListener('click', () => {
document.getElementById('add-blacklist-form').classList.toggle('hidden');
});
document.getElementById('save-blacklist-btn').addEventListener('click', handleAddBlacklist);
document.getElementById('cancel-blacklist-btn').addEventListener('click', () => {
document.getElementById('add-blacklist-form').classList.add('hidden');
document.getElementById('blacklist-name').value = '';
document.getElementById('blacklist-url').value = '';
});
// Hosts条目管理事件
document.getElementById('add-hosts-btn').addEventListener('click', () => {
document.getElementById('add-hosts-form').classList.toggle('hidden');
});
document.getElementById('save-hosts-btn').addEventListener('click', handleAddHostsEntry);
document.getElementById('cancel-hosts-btn').addEventListener('click', () => {
document.getElementById('add-hosts-form').classList.add('hidden');
document.getElementById('hosts-ip').value = '';
document.getElementById('hosts-domain').value = '';
});
}
// 显示成功消息
function showSuccessMessage(message) {
showNotification(message, 'success');
}
// 显示错误消息
function showErrorMessage(message) {
showNotification(message, 'error');
}
// 显示加载状态
function showLoading(message = '加载中...') {
// 移除现有加载状态
hideLoading();
// 创建加载状态元素
const loading = document.createElement('div');
loading.className = 'loading-overlay fixed inset-0 bg-black/50 flex items-center justify-center z-50';
loading.innerHTML = `
<div class="bg-white rounded-lg p-6 shadow-lg flex items-center space-x-4">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
<span class="text-gray-700 font-medium">${message}</span>
</div>
`;
document.body.appendChild(loading);
}
// 隐藏加载状态
function hideLoading() {
const loading = document.querySelector('.loading-overlay');
if (loading) {
loading.remove();
}
}
// 显示通知
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-all 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.innerHTML = `
<div class="flex items-center space-x-2">
<i class="fa ${type === 'success' ? 'fa-check-circle' : type === 'error' ? 'fa-exclamation-circle' : 'fa-info-circle'}"></i>
<span>${message}</span>
</div>
`;
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();
}
// 当切换到屏蔽管理页面时重新加载数据
document.addEventListener('DOMContentLoaded', () => {
// 监听hash变化当切换到屏蔽管理页面时重新加载数据
window.addEventListener('hashchange', () => {
if (window.location.hash === '#shield') {
initShieldPage();
}
});
});