561 lines
17 KiB
JavaScript
561 lines
17 KiB
JavaScript
// 屏蔽管理页面功能实现
|
||
|
||
// 初始化屏蔽管理页面
|
||
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();
|
||
}
|
||
});
|
||
}); |