增加web功能
This commit is contained in:
@@ -8,8 +8,6 @@ function initShieldPage() {
|
||||
loadLocalRules();
|
||||
// 加载远程黑名单
|
||||
loadRemoteBlacklists();
|
||||
// 加载hosts条目
|
||||
loadHostsEntries();
|
||||
// 设置事件监听器
|
||||
setupShieldEventListeners();
|
||||
}
|
||||
@@ -19,21 +17,33 @@ async function loadShieldStats() {
|
||||
showLoading('加载屏蔽规则统计信息...');
|
||||
try {
|
||||
const response = await fetch('/api/shield');
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load shield stats');
|
||||
throw new Error(`加载失败: ${response.status}`);
|
||||
}
|
||||
|
||||
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;
|
||||
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 loading shield stats:', error);
|
||||
console.error('加载屏蔽规则统计信息失败:', error);
|
||||
showErrorMessage('加载屏蔽规则统计信息失败');
|
||||
hideLoading();
|
||||
}
|
||||
@@ -43,17 +53,37 @@ async function loadShieldStats() {
|
||||
async function loadLocalRules() {
|
||||
showLoading('加载本地规则...');
|
||||
try {
|
||||
const response = await fetch('/api/shield');
|
||||
const response = await fetch('/api/shield?all=true');
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load local rules');
|
||||
throw new Error(`加载失败: ${response.status}`);
|
||||
}
|
||||
// 注意:当前API不返回完整规则列表,这里只是示例
|
||||
// 实际实现需要后端提供获取本地规则的API
|
||||
const rules = [];
|
||||
|
||||
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 loading local rules:', error);
|
||||
console.error('加载本地规则失败:', error);
|
||||
showErrorMessage('加载本地规则失败');
|
||||
hideLoading();
|
||||
}
|
||||
@@ -63,26 +93,55 @@ async function loadLocalRules() {
|
||||
function updateRulesTable(rules) {
|
||||
const tbody = document.getElementById('rules-table-body');
|
||||
|
||||
// 清空表格
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (rules.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="2" class="py-4 text-center text-gray-500">暂无规则</td></tr>';
|
||||
const emptyRow = document.createElement('tr');
|
||||
emptyRow.innerHTML = '<td colspan="2" class="py-4 text-center text-gray-500">暂无规则</td>';
|
||||
tbody.appendChild(emptyRow);
|
||||
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('');
|
||||
// 对于大量规则,限制显示数量
|
||||
const maxRulesToShow = 1000; // 限制最大显示数量
|
||||
const rulesToShow = rules.length > maxRulesToShow ? rules.slice(0, maxRulesToShow) : rules;
|
||||
|
||||
// 重新绑定删除事件
|
||||
document.querySelectorAll('.delete-rule-btn').forEach(btn => {
|
||||
btn.addEventListener('click', handleDeleteRule);
|
||||
// 使用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);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理删除规则
|
||||
@@ -140,8 +199,6 @@ async function handleAddRule() {
|
||||
showSuccessMessage('规则添加成功');
|
||||
// 清空输入框
|
||||
document.getElementById('new-rule').value = '';
|
||||
// 隐藏表单
|
||||
document.getElementById('add-rule-form').classList.add('hidden');
|
||||
// 重新加载规则
|
||||
loadLocalRules();
|
||||
// 重新加载统计信息
|
||||
@@ -159,14 +216,19 @@ async function loadRemoteBlacklists() {
|
||||
showLoading('加载远程黑名单...');
|
||||
try {
|
||||
const response = await fetch('/api/shield/blacklists');
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load remote blacklists');
|
||||
throw new Error(`加载失败: ${response.status}`);
|
||||
}
|
||||
|
||||
const blacklists = await response.json();
|
||||
updateBlacklistsTable(blacklists);
|
||||
|
||||
// 确保blacklists是数组
|
||||
const blacklistArray = Array.isArray(blacklists) ? blacklists : [];
|
||||
updateBlacklistsTable(blacklistArray);
|
||||
hideLoading();
|
||||
} catch (error) {
|
||||
console.error('Error loading remote blacklists:', error);
|
||||
console.error('加载远程黑名单失败:', error);
|
||||
showErrorMessage('加载远程黑名单失败');
|
||||
hideLoading();
|
||||
}
|
||||
@@ -176,37 +238,81 @@ async function loadRemoteBlacklists() {
|
||||
function updateBlacklistsTable(blacklists) {
|
||||
const tbody = document.getElementById('blacklists-table-body');
|
||||
|
||||
// 清空表格
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (blacklists.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="4" class="py-4 text-center text-gray-500">暂无黑名单</td></tr>';
|
||||
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;
|
||||
}
|
||||
|
||||
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('');
|
||||
// 对于大量黑名单,限制显示数量
|
||||
const maxBlacklistsToShow = 100; // 限制最大显示数量
|
||||
const blacklistsToShow = blacklists.length > maxBlacklistsToShow ? blacklists.slice(0, maxBlacklistsToShow) : blacklists;
|
||||
|
||||
// 重新绑定事件
|
||||
document.querySelectorAll('.update-blacklist-btn').forEach(btn => {
|
||||
btn.addEventListener('click', handleUpdateBlacklist);
|
||||
// 使用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);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.delete-blacklist-btn').forEach(btn => {
|
||||
btn.addEventListener('click', handleDeleteBlacklist);
|
||||
});
|
||||
// 一次性添加所有行到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);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理更新单个黑名单
|
||||
@@ -289,8 +395,6 @@ async function handleAddBlacklist() {
|
||||
// 清空输入框
|
||||
document.getElementById('blacklist-name').value = '';
|
||||
document.getElementById('blacklist-url').value = '';
|
||||
// 隐藏表单
|
||||
document.getElementById('add-blacklist-form').classList.add('hidden');
|
||||
// 重新加载黑名单
|
||||
loadRemoteBlacklists();
|
||||
// 重新加载统计信息
|
||||
@@ -303,162 +407,15 @@ async function handleAddBlacklist() {
|
||||
}
|
||||
}
|
||||
|
||||
// 加载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 = '';
|
||||
});
|
||||
}
|
||||
|
||||
// 显示成功消息
|
||||
|
||||
Reference in New Issue
Block a user