Files
dns-server/static/js/shield.js
2025-11-28 00:17:56 +08:00

518 lines
15 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();
// 设置事件监听器
setupShieldEventListeners();
}
// 加载屏蔽规则统计信息
async function loadShieldStats() {
showLoading('加载屏蔽规则统计信息...');
try {
const response = await fetch('/api/shield');
if (!response.ok) {
throw new Error(`加载失败: ${response.status}`);
}
const stats = await response.json();
// 更新统计信息
const elements = [
{ id: 'domain-rules-count', value: stats.domainRulesCount },
{ id: 'domain-exceptions-count', value: stats.domainExceptionsCount },
{ id: 'regex-rules-count', value: stats.regexRulesCount },
{ id: 'regex-exceptions-count', value: stats.regexExceptionsCount },
{ id: 'hosts-rules-count', value: stats.hostsRulesCount },
{ id: 'blacklist-count', value: stats.blacklistCount }
];
elements.forEach(item => {
const element = document.getElementById(item.id);
if (element) {
element.textContent = item.value || 0;
}
});
hideLoading();
} catch (error) {
console.error('加载屏蔽规则统计信息失败:', error);
showErrorMessage('加载屏蔽规则统计信息失败');
hideLoading();
}
}
// 加载本地规则
async function loadLocalRules() {
showLoading('加载本地规则...');
try {
const response = await fetch('/api/shield?all=true');
if (!response.ok) {
throw new Error(`加载失败: ${response.status}`);
}
const data = await response.json();
// 合并所有本地规则
let rules = [];
// 添加域名规则
if (Array.isArray(data.domainRules)) {
rules = rules.concat(data.domainRules);
}
// 添加域名排除规则
if (Array.isArray(data.domainExceptions)) {
rules = rules.concat(data.domainExceptions);
}
// 添加正则规则
if (Array.isArray(data.regexRules)) {
rules = rules.concat(data.regexRules);
}
// 添加正则排除规则
if (Array.isArray(data.regexExceptions)) {
rules = rules.concat(data.regexExceptions);
}
updateRulesTable(rules);
hideLoading();
} catch (error) {
console.error('加载本地规则失败:', error);
showErrorMessage('加载本地规则失败');
hideLoading();
}
}
// 更新规则表格
function updateRulesTable(rules) {
const tbody = document.getElementById('rules-table-body');
// 清空表格
tbody.innerHTML = '';
if (rules.length === 0) {
const emptyRow = document.createElement('tr');
emptyRow.innerHTML = '<td colspan="2" class="py-4 text-center text-gray-500">暂无规则</td>';
tbody.appendChild(emptyRow);
return;
}
// 对于大量规则,限制显示数量
const maxRulesToShow = 1000; // 限制最大显示数量
const rulesToShow = rules.length > maxRulesToShow ? rules.slice(0, maxRulesToShow) : rules;
// 使用DocumentFragment提高性能
const fragment = document.createDocumentFragment();
rulesToShow.forEach(rule => {
const tr = document.createElement('tr');
tr.className = 'border-b border-gray-200';
const tdRule = document.createElement('td');
tdRule.className = 'py-3 px-4';
tdRule.textContent = rule;
const tdAction = document.createElement('td');
tdAction.className = 'py-3 px-4 text-right';
const deleteBtn = document.createElement('button');
deleteBtn.className = 'delete-rule-btn px-3 py-1 bg-danger text-white rounded-md hover:bg-danger/90 transition-colors text-sm';
deleteBtn.dataset.rule = rule;
deleteBtn.innerHTML = '<i class="fa fa-trash"></i>';
deleteBtn.addEventListener('click', handleDeleteRule);
tdAction.appendChild(deleteBtn);
tr.appendChild(tdRule);
tr.appendChild(tdAction);
fragment.appendChild(tr);
});
// 一次性添加所有行到DOM
tbody.appendChild(fragment);
// 如果有更多规则,添加提示
if (rules.length > maxRulesToShow) {
const infoRow = document.createElement('tr');
infoRow.innerHTML = `<td colspan="2" class="py-4 text-center text-gray-500">显示前 ${maxRulesToShow} 条规则,共 ${rules.length} 条</td>`;
tbody.appendChild(infoRow);
}
}
// 处理删除规则
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 = '';
// 重新加载规则
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(`加载失败: ${response.status}`);
}
const blacklists = await response.json();
// 确保blacklists是数组
const blacklistArray = Array.isArray(blacklists) ? blacklists : [];
updateBlacklistsTable(blacklistArray);
hideLoading();
} catch (error) {
console.error('加载远程黑名单失败:', error);
showErrorMessage('加载远程黑名单失败');
hideLoading();
}
}
// 更新黑名单表格
function updateBlacklistsTable(blacklists) {
const tbody = document.getElementById('blacklists-table-body');
// 清空表格
tbody.innerHTML = '';
if (blacklists.length === 0) {
const emptyRow = document.createElement('tr');
emptyRow.innerHTML = '<tr><td colspan="4" class="py-4 text-center text-gray-500">暂无黑名单</td></tr>';
tbody.appendChild(emptyRow);
return;
}
// 对于大量黑名单,限制显示数量
const maxBlacklistsToShow = 100; // 限制最大显示数量
const blacklistsToShow = blacklists.length > maxBlacklistsToShow ? blacklists.slice(0, maxBlacklistsToShow) : blacklists;
// 使用DocumentFragment提高性能
const fragment = document.createDocumentFragment();
blacklistsToShow.forEach(blacklist => {
const tr = document.createElement('tr');
tr.className = 'border-b border-gray-200';
// 名称单元格
const tdName = document.createElement('td');
tdName.className = 'py-3 px-4';
tdName.textContent = blacklist.Name;
// URL单元格
const tdUrl = document.createElement('td');
tdUrl.className = 'py-3 px-4 truncate max-w-xs';
tdUrl.textContent = blacklist.URL;
// 状态单元格
const tdStatus = document.createElement('td');
tdStatus.className = 'py-3 px-4 text-center';
const statusDot = document.createElement('span');
statusDot.className = `inline-block w-3 h-3 rounded-full ${blacklist.Enabled ? 'bg-success' : 'bg-gray-300'}`;
tdStatus.appendChild(statusDot);
// 操作单元格
const tdActions = document.createElement('td');
tdActions.className = 'py-3 px-4 text-right space-x-2';
// 更新按钮
const updateBtn = document.createElement('button');
updateBtn.className = 'update-blacklist-btn px-3 py-1 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors text-sm';
updateBtn.dataset.url = blacklist.URL;
updateBtn.innerHTML = '<i class="fa fa-refresh"></i>';
updateBtn.addEventListener('click', handleUpdateBlacklist);
// 删除按钮
const deleteBtn = document.createElement('button');
deleteBtn.className = 'delete-blacklist-btn px-3 py-1 bg-danger text-white rounded-md hover:bg-danger/90 transition-colors text-sm';
deleteBtn.dataset.url = blacklist.URL;
deleteBtn.innerHTML = '<i class="fa fa-trash"></i>';
deleteBtn.addEventListener('click', handleDeleteBlacklist);
tdActions.appendChild(updateBtn);
tdActions.appendChild(deleteBtn);
tr.appendChild(tdName);
tr.appendChild(tdUrl);
tr.appendChild(tdStatus);
tr.appendChild(tdActions);
fragment.appendChild(tr);
});
// 一次性添加所有行到DOM
tbody.appendChild(fragment);
// 如果有更多黑名单,添加提示
if (blacklists.length > maxBlacklistsToShow) {
const infoRow = document.createElement('tr');
infoRow.innerHTML = `<td colspan="4" class="py-4 text-center text-gray-500">显示前 ${maxBlacklistsToShow} 个黑名单,共 ${blacklists.length} 个</td>`;
tbody.appendChild(infoRow);
}
}
// 处理更新单个黑名单
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 = '';
// 重新加载黑名单
loadRemoteBlacklists();
// 重新加载统计信息
loadShieldStats();
hideLoading();
} catch (error) {
console.error('Error adding blacklist:', error);
showErrorMessage('添加黑名单失败');
hideLoading();
}
}
// 设置事件监听器
function setupShieldEventListeners() {
// 本地规则管理事件
document.getElementById('save-rule-btn').addEventListener('click', handleAddRule);
// 远程黑名单管理事件
document.getElementById('save-blacklist-btn').addEventListener('click', handleAddBlacklist);
}
// 显示成功消息
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();
}
});
});