修复本地规则管理不工作的问题

This commit is contained in:
Alex Yang
2025-11-28 18:41:55 +08:00
parent ca4a32422c
commit ee148fe6c3
21 changed files with 273 additions and 819217 deletions

View File

@@ -0,0 +1,90 @@
## 问题分析
通过代码分析我发现web屏蔽管理页面点击后一直显示处理中灰色遮罩的原因有两个
1. **JavaScript错误导致`hideLoading()`不被调用**
- `loadLocalRules()`函数尝试访问`document.getElementById('local-rules-count')`但该元素在HTML中不存在
- `loadRemoteRules()`函数尝试访问`document.getElementById('remote-rules-count')`但该元素在HTML中不存在
- `setupShieldEventListeners()`函数尝试访问`document.getElementById('view-local-rules-btn')``document.getElementById('view-remote-rules-btn')`但这些元素在HTML中不存在
- 这些JavaScript错误会导致函数执行中断从而使`hideLoading()`不被调用,加载遮罩一直显示
2. **竞态条件问题**
- `initShieldPage()`函数并行调用三个异步函数:`loadShieldStats()``loadLocalRules()``loadRemoteBlacklists()`
- 每个异步函数都会调用`showLoading()`,而`showLoading()`会先调用`hideLoading()`来移除现有加载状态,然后创建一个新的加载状态
- 这种并行调用可能导致竞态条件,使最后一个加载遮罩无法被正确销毁
## 修复计划
1. **修改`loadLocalRules()`函数**添加元素存在性检查避免JavaScript错误
2. **修改`loadRemoteRules()`函数**添加元素存在性检查避免JavaScript错误
3. **修改`setupShieldEventListeners()`函数**添加元素存在性检查避免JavaScript错误
4. **修改`initShieldPage()`函数**:确保只创建一个加载遮罩,并在所有异步函数完成后销毁它
## 修复步骤
### 步骤1修改`loadLocalRules()`函数
`shield.js`文件中,修改`loadLocalRules()`函数,添加元素存在性检查:
```javascript
// 更新本地规则数量显示
if (document.getElementById('local-rules-count')) {
document.getElementById('local-rules-count').textContent = data.localRulesCount || 0;
}
```
### 步骤2修改`loadRemoteRules()`函数
`shield.js`文件中,修改`loadRemoteRules()`函数,添加元素存在性检查:
```javascript
// 更新远程规则数量显示
if (document.getElementById('remote-rules-count')) {
document.getElementById('remote-rules-count').textContent = data.remoteRulesCount || 0;
}
```
### 步骤3修改`setupShieldEventListeners()`函数
`shield.js`文件中,修改`setupShieldEventListeners()`函数,添加元素存在性检查:
```javascript
// 添加切换查看本地规则和远程规则的事件监听
if (document.getElementById('view-local-rules-btn')) {
document.getElementById('view-local-rules-btn').addEventListener('click', loadLocalRules);
}
if (document.getElementById('view-remote-rules-btn')) {
document.getElementById('view-remote-rules-btn').addEventListener('click', loadRemoteRules);
}
```
### 步骤4修改`initShieldPage()`函数
`shield.js`文件中,修改`initShieldPage()`函数,确保只创建一个加载遮罩,并在所有异步函数完成后销毁它:
```javascript
// 初始化屏蔽管理页面
async function initShieldPage() {
showLoading('加载屏蔽管理数据...');
try {
// 并行加载所有数据
await Promise.all([
loadShieldStats(),
loadLocalRules(),
loadRemoteBlacklists()
]);
// 设置事件监听器
setupShieldEventListeners();
} finally {
hideLoading();
}
}
```
### 步骤5修改异步函数移除内部的`showLoading()`和`hideLoading()`调用
`shield.js`文件中,修改`loadShieldStats()``loadLocalRules()``loadRemoteBlacklists()`函数,移除内部的`showLoading()``hideLoading()`调用,只保留数据加载逻辑。
## 预期效果
修复后web屏蔽管理页面点击后
1. 只会显示一个加载遮罩
2. 不会出现JavaScript错误
3. 所有数据加载完成后,加载遮罩会被正确隐藏
4. 页面会正常显示屏蔽管理设置内容

View File

@@ -0,0 +1,41 @@
# 问题分析
1. **问题现象**用户点击删除按钮删除本地规则时通过API发送数据到服务器但Web界面没有自动刷新本地规则列表。
2. **问题根源**
- 后端`RemoveRule`方法存在缺陷,当删除规则时没有正确更新所有相关映射
- 前端代码虽然调用了重新加载规则列表的函数,但由于后端数据不一致,导致刷新后列表没有变化
3. **具体问题**
-`shield/manager.go``RemoveRule`方法中,删除域名规则时只从`domainRules``domainExceptions`映射中删除了规则,但没有更新`domainRulesIsLocal``domainExceptionsIsLocal``domainRulesSource``domainExceptionsSource`映射
- 处理正则表达式规则时,使用了错误的比较方式(`re.pattern.String() != pattern`),而应该使用原始规则字符串进行比较
# 修复方案
1. **修复`RemoveRule`方法**
- 当删除域名规则时,同时更新所有相关映射
- 当删除排除规则时,同时更新所有相关映射
- 修复正则表达式规则的比较方式,使用原始规则字符串进行比较
2. **验证前端代码**
- 确认前端代码在删除规则后正确调用了重新加载规则列表的函数
- 验证前端代码处理API响应的逻辑是否正确
# 实现步骤
1. 修改`shield/manager.go`文件中的`RemoveRule`方法:
- 在删除域名规则时,添加删除`domainRulesIsLocal``domainRulesSource`映射的代码
- 在删除排除规则时,添加删除`domainExceptionsIsLocal``domainExceptionsSource`映射的代码
- 修复正则表达式规则的比较方式
2. 测试修复后的功能:
- 启动DNS服务器
- 访问Web界面添加几条本地规则
- 点击删除按钮删除其中一条规则
- 验证规则列表是否自动刷新,且删除的规则不再显示
# 预期结果
- 用户点击删除按钮后,规则被成功删除
- Web界面自动刷新删除的规则从列表中消失
- 本地规则文件被正确更新,删除的规则不再存在于文件中

View File

@@ -0,0 +1,51 @@
## 问题分析
通过代码分析,我发现本地规则管理删除规则功能失效的原因有两个:
1. **规则格式不匹配**
- 前端显示的规则带有修饰符,例如:`||example.com^`
- 服务器端实际存储的是裸域名,例如:`example.com`
- `RemoveRule` 函数在处理规则时,虽然尝试了多种格式变体,但没有正确处理前端发送的带有修饰符的规则
2. **本地规则标记未更新**
- `RemoveRule` 函数没有考虑 `m.domainRulesIsLocal``m.domainExceptionsIsLocal` 映射,这些映射用于标记哪些规则是本地规则
- 删除规则后,没有更新这些映射,导致规则删除不彻底
## 修复计划
1. **修改 `RemoveRule` 函数**
- 改进规则处理逻辑,确保能正确处理带有修饰符的规则
- 更新 `domainRulesIsLocal``domainExceptionsIsLocal` 映射,确保本地规则被正确删除
2. **修改 `GetLocalRules` 函数**
- 确保返回的规则格式与 `RemoveRule` 函数期望的格式一致
3. **添加调试日志**
- 在关键位置添加日志,便于调试和监控规则删除过程
## 修复步骤
### 步骤1修改 `RemoveRule` 函数
`shield/manager.go` 文件中,修改 `RemoveRule` 函数,改进规则处理逻辑:
1. 确保正确处理带有修饰符的规则
2. 更新 `domainRulesIsLocal``domainExceptionsIsLocal` 映射
3. 添加调试日志
### 步骤2测试修复效果
- 启动服务器
- 访问本地规则管理页面
- 添加一条本地规则
- 删除该规则
- 验证规则是否被正确删除,页面内容是否减少
## 预期效果
修复后,本地规则管理删除规则功能将正常工作:
- 点击删除按钮后,规则会被正确发送到服务器
- 服务器会正确处理带有修饰符的规则
- 本地规则标记会被正确更新
- 规则会被持久化保存
- 页面内容会立即减少
这样就能确保本地规则管理删除规则功能正常工作,提供良好的用户体验。

View File

@@ -1,81 +0,0 @@
# DNS服务器项目介绍
## 项目概述
这是一个基于Go语言开发的高性能DNS服务器具备域名屏蔽、Hosts管理、统计分析和远程规则管理等功能。服务器支持通过Web界面进行管理配置同时能够自动更新和缓存远程规则列表。
## 技术架构
### 核心组件
1. DNS服务模块 ( `server.go` )
- 基于 github.com/miekg/dns 库实现高性能DNS查询处理
- 支持配置上游DNS服务器进行递归查询
- 实现域名屏蔽、统计数据收集等核心功能
2. 屏蔽管理系统 ( `manager.go` )
- 管理本地和远程屏蔽规则
- 支持规则缓存、自动更新和统计
- 实现域名和正则表达式规则的解析和匹配
3. HTTP控制台 ( `server.go` )
- 提供Web管理界面
- 实现REST API用于配置管理和数据查询
4. 配置管理 ( `config.go` )
- 定义配置结构和加载功能
- 支持JSON格式配置文件
## 主要功能特性
### 1. 域名屏蔽系统
- 支持本地规则文件和远程规则URL
- 多种屏蔽方式NXDOMAIN、refused、emptyIP、customIP
- 支持域名精确匹配和正则表达式匹配
- 远程规则自动缓存和更新机制
### 2. Hosts管理
- 支持自定义Hosts映射
- 提供Web界面管理Hosts条目
- 自动保存Hosts配置
### 3. 统计分析功能
- 记录屏蔽域名统计信息
- 记录解析域名统计信息
- 提供按小时统计的屏蔽数据
- 支持查询最常屏蔽和解析的域名
### 4. 远程规则管理
- 支持添加多个远程规则URL
- 自动定期更新远程规则
- 本地缓存机制确保规则可用性
- Web界面可视化管理
### 5. 管理界面
- 提供直观的Web控制台
- 支持查看服务器状态和统计信息
- 规则管理和配置修改
- DNS查询测试工具
## 项目结构
```
/root/dns/
├── config/          # 配置管理
├── data/            # 数据目录(包含缓存和统计)
   └── remote_rules/ # 远程规则缓存
├── dns/             # DNS服务器核心
├── http/            # HTTP控制台
├── logger/          # 日志系统
├── shield/          # 屏蔽规则管理
├── static/          # 静态Web文件
├── main.go          # 程序入口
└── config.json      # 配置文件
```
## 配置项说明
主要配置文件 `config.json` 包含以下部分:
- DNS配置 端口、上游DNS服务器、超时设置等
- HTTP配置 :控制台端口、主机绑定等
- 屏蔽配置 规则文件路径、远程规则URL、更新间隔等
- 日志配置 :日志文件路径、级别设置等
## 使用场景
1. 网络内容过滤(广告、恶意网站屏蔽)
2. 本地DNS缓存加速
3. 企业/家庭网络DNS管理
4. 开发测试环境DNS重定向
## 技术栈
- 语言 Go
- DNS库 github.com/miekg/dns
- 日志库 github.com/sirupsen/logrus
- Web前端 HTML/CSS/JavaScript
该DNS服务器具有高性能、功能全面、易于配置等特点适用于需要精确控制DNS查询结果的各种网络环境。

View File

@@ -1,7 +0,0 @@
# DNS Server Hosts File
# Generated by DNS Server
127.0.0.1 localhost
::1 localhost
127.0.0.1 example.com

View File

@@ -1,3 +0,0 @@
/example.com/
/adjust.com/
/adjust.c/

View File

@@ -1,5 +0,0 @@
{
"blockedDomainsCount": {},
"resolvedDomainsCount": {},
"lastSaved": "2025-11-28T18:01:32.204427496+08:00"
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -804,6 +804,8 @@ func (m *ShieldManager) RemoveRule(rule string) error {
domain := strings.TrimPrefix(format, "@@||") domain := strings.TrimPrefix(format, "@@||")
if _, exists := m.domainExceptions[domain]; exists { if _, exists := m.domainExceptions[domain]; exists {
delete(m.domainExceptions, domain) delete(m.domainExceptions, domain)
delete(m.domainExceptionsIsLocal, domain)
delete(m.domainExceptionsSource, domain)
removed = true removed = true
break break
} }
@@ -812,6 +814,8 @@ func (m *ShieldManager) RemoveRule(rule string) error {
domain := strings.TrimPrefix(format, "||") domain := strings.TrimPrefix(format, "||")
if _, exists := m.domainRules[domain]; exists { if _, exists := m.domainRules[domain]; exists {
delete(m.domainRules, domain) delete(m.domainRules, domain)
delete(m.domainRulesIsLocal, domain)
delete(m.domainRulesSource, domain)
removed = true removed = true
break break
} }
@@ -819,11 +823,15 @@ func (m *ShieldManager) RemoveRule(rule string) error {
// 尝试直接作为域名删除 // 尝试直接作为域名删除
if _, exists := m.domainRules[format]; exists { if _, exists := m.domainRules[format]; exists {
delete(m.domainRules, format) delete(m.domainRules, format)
delete(m.domainRulesIsLocal, format)
delete(m.domainRulesSource, format)
removed = true removed = true
break break
} }
if _, exists := m.domainExceptions[format]; exists { if _, exists := m.domainExceptions[format]; exists {
delete(m.domainExceptions, format) delete(m.domainExceptions, format)
delete(m.domainExceptionsIsLocal, format)
delete(m.domainExceptionsSource, format)
removed = true removed = true
break break
} }
@@ -832,12 +840,10 @@ func (m *ShieldManager) RemoveRule(rule string) error {
// 处理正则表达式规则 // 处理正则表达式规则
if !removed && strings.HasPrefix(cleanRule, "/") && strings.HasSuffix(cleanRule, "/") { if !removed && strings.HasPrefix(cleanRule, "/") && strings.HasSuffix(cleanRule, "/") {
pattern := strings.TrimPrefix(strings.TrimSuffix(cleanRule, "/"), "/")
// 检查是否在正则表达式规则中 // 检查是否在正则表达式规则中
newRegexRules := []regexRule{} newRegexRules := []regexRule{}
for _, re := range m.regexRules { for _, re := range m.regexRules {
if re.pattern.String() != pattern { if re.original != rule && re.original != cleanRule {
newRegexRules = append(newRegexRules, re) newRegexRules = append(newRegexRules, re)
} else { } else {
removed = true removed = true
@@ -849,7 +855,7 @@ func (m *ShieldManager) RemoveRule(rule string) error {
if !removed { if !removed {
newRegexExceptions := []regexRule{} newRegexExceptions := []regexRule{}
for _, re := range m.regexExceptions { for _, re := range m.regexExceptions {
if re.pattern.String() != pattern { if re.original != rule && re.original != cleanRule {
newRegexExceptions = append(newRegexExceptions, re) newRegexExceptions = append(newRegexExceptions, re)
} else { } else {
removed = true removed = true

View File

@@ -36,7 +36,6 @@ function initConfigPage() {
// 加载系统配置 // 加载系统配置
async function loadConfig() { async function loadConfig() {
try { try {
showLoading(true);
const result = await api.getConfig(); const result = await api.getConfig();
// 检查API返回的错误 // 检查API返回的错误
@@ -49,8 +48,6 @@ async function loadConfig() {
} catch (error) { } catch (error) {
// 捕获可能的异常虽然apiRequest不应该再抛出异常 // 捕获可能的异常虽然apiRequest不应该再抛出异常
showErrorMessage('加载配置失败: ' + (error.message || '未知错误')); showErrorMessage('加载配置失败: ' + (error.message || '未知错误'));
} finally {
showLoading(false);
} }
} }
@@ -106,7 +103,6 @@ async function handleSaveConfig() {
if (!formData) return; if (!formData) return;
try { try {
showLoading(true);
const result = await api.saveConfig(formData); const result = await api.saveConfig(formData);
// 检查API返回的错误 // 检查API返回的错误
@@ -119,8 +115,6 @@ async function handleSaveConfig() {
} catch (error) { } catch (error) {
// 捕获可能的异常虽然apiRequest不应该再抛出异常 // 捕获可能的异常虽然apiRequest不应该再抛出异常
showErrorMessage('保存配置失败: ' + (error.message || '未知错误')); showErrorMessage('保存配置失败: ' + (error.message || '未知错误'));
} finally {
showLoading(false);
} }
} }
@@ -129,7 +123,6 @@ async function handleRestartService() {
if (!confirm('确定要重启DNS服务吗重启期间服务可能会短暂不可用。')) return; if (!confirm('确定要重启DNS服务吗重启期间服务可能会短暂不可用。')) return;
try { try {
showLoading(true);
const result = await api.restartService(); const result = await api.restartService();
// 检查API返回的错误 // 检查API返回的错误
@@ -142,8 +135,6 @@ async function handleRestartService() {
} catch (error) { } catch (error) {
// 捕获可能的异常虽然apiRequest不应该再抛出异常 // 捕获可能的异常虽然apiRequest不应该再抛出异常
showErrorMessage('重启服务失败: ' + (error.message || '未知错误')); showErrorMessage('重启服务失败: ' + (error.message || '未知错误'));
} finally {
showLoading(false);
} }
} }
@@ -220,34 +211,7 @@ function setupConfigEventListeners() {
getElement('restart-service-btn')?.addEventListener('click', handleRestartService); getElement('restart-service-btn')?.addEventListener('click', handleRestartService);
} }
// 显示加载状态
function showLoading(show) {
const loadingElement = document.getElementById('loading-overlay');
if (show) {
if (!loadingElement) {
const overlay = document.createElement('div');
overlay.id = 'loading-overlay';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
color: white;
font-size: 18px;
`;
overlay.innerHTML = '<div>处理中...</div>';
document.body.appendChild(overlay);
}
} else {
loadingElement?.remove();
}
}
// 显示成功消息 // 显示成功消息
function showSuccessMessage(message) { function showSuccessMessage(message) {

View File

@@ -446,18 +446,7 @@ async function loadDashboardData() {
]; ];
} }
// 实现数据加载状态管理
function showLoading(elementId) {
const loadingElement = document.getElementById(elementId + '-loading');
const errorElement = document.getElementById(elementId + '-error');
if (loadingElement) loadingElement.classList.remove('hidden');
if (errorElement) errorElement.classList.add('hidden');
}
function hideLoading(elementId) {
const loadingElement = document.getElementById(elementId + '-loading');
if (loadingElement) loadingElement.classList.add('hidden');
}
function showError(elementId) { function showError(elementId) {
const loadingElement = document.getElementById(elementId + '-loading'); const loadingElement = document.getElementById(elementId + '-loading');
@@ -468,7 +457,6 @@ async function loadDashboardData() {
// 尝试获取TOP客户端优先使用真实数据失败时使用模拟数据 // 尝试获取TOP客户端优先使用真实数据失败时使用模拟数据
let topClients = []; let topClients = [];
showLoading('top-clients');
try { try {
const clientsData = await api.getTopClients(); const clientsData = await api.getTopClients();
console.log('TOP客户端:', clientsData); console.log('TOP客户端:', clientsData);
@@ -513,13 +501,10 @@ async function loadDashboardData() {
{ ip: '192.168.1.104', count: 50 } { ip: '192.168.1.104', count: 50 }
]; ];
showError('top-clients'); showError('top-clients');
} finally {
hideLoading('top-clients');
} }
// 尝试获取TOP域名优先使用真实数据失败时使用模拟数据 // 尝试获取TOP域名优先使用真实数据失败时使用模拟数据
let topDomains = []; let topDomains = [];
showLoading('top-domains');
try { try {
const domainsData = await api.getTopDomains(); const domainsData = await api.getTopDomains();
console.log('TOP域名:', domainsData); console.log('TOP域名:', domainsData);
@@ -564,8 +549,6 @@ async function loadDashboardData() {
{ domain: 'youtube.com', count: 30 } { domain: 'youtube.com', count: 30 }
]; ];
showError('top-domains'); showError('top-domains');
} finally {
hideLoading('top-domains');
} }
// 更新统计卡片 // 更新统计卡片

View File

@@ -23,8 +23,6 @@ async function handleDNSQuery() {
return; return;
} }
showLoading('查询中...');
try { try {
const response = await fetch(`/api/query?domain=${encodeURIComponent(domain)}`); const response = await fetch(`/api/query?domain=${encodeURIComponent(domain)}`);
if (!response.ok) { if (!response.ok) {
@@ -35,11 +33,9 @@ async function handleDNSQuery() {
displayQueryResult(result, domain); displayQueryResult(result, domain);
saveQueryHistory(domain, result); saveQueryHistory(domain, result);
loadQueryHistory(); loadQueryHistory();
hideLoading();
} catch (error) { } catch (error) {
console.error('DNS查询出错:', error); console.error('DNS查询出错:', error);
showErrorMessage('查询失败,请稍后重试'); showErrorMessage('查询失败,请稍后重试');
hideLoading();
} }
} }
@@ -229,31 +225,7 @@ function setupQueryEventListeners() {
} }
} }
// 显示加载状态
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 showSuccessMessage(message) { function showSuccessMessage(message) {

View File

@@ -1,20 +1,19 @@
// 屏蔽管理页面功能实现 // 屏蔽管理页面功能实现
// 初始化屏蔽管理页面 // 初始化屏蔽管理页面
function initShieldPage() { async function initShieldPage() {
// 加载屏蔽规则统计信息 // 并行加载所有数据
loadShieldStats(); await Promise.all([
// 加载本地规则 loadShieldStats(),
loadLocalRules(); loadLocalRules(),
// 加载远程黑名单 loadRemoteBlacklists()
loadRemoteBlacklists(); ]);
// 设置事件监听器 // 设置事件监听器
setupShieldEventListeners(); setupShieldEventListeners();
} }
// 加载屏蔽规则统计信息 // 加载屏蔽规则统计信息
async function loadShieldStats() { async function loadShieldStats() {
showLoading('加载屏蔽规则统计信息...');
try { try {
const response = await fetch('/api/shield'); const response = await fetch('/api/shield');
@@ -40,18 +39,14 @@ async function loadShieldStats() {
element.textContent = item.value || 0; element.textContent = item.value || 0;
} }
}); });
hideLoading();
} catch (error) { } catch (error) {
console.error('加载屏蔽规则统计信息失败:', error); console.error('加载屏蔽规则统计信息失败:', error);
showErrorMessage('加载屏蔽规则统计信息失败'); showErrorMessage('加载屏蔽规则统计信息失败');
hideLoading();
} }
} }
// 加载本地规则 // 加载本地规则
async function loadLocalRules() { async function loadLocalRules() {
showLoading('加载本地规则...');
try { try {
const response = await fetch('/api/shield/localrules'); const response = await fetch('/api/shield/localrules');
@@ -89,17 +84,14 @@ async function loadLocalRules() {
} }
updateRulesTable(rules); updateRulesTable(rules);
hideLoading();
} catch (error) { } catch (error) {
console.error('加载本地规则失败:', error); console.error('加载本地规则失败:', error);
showErrorMessage('加载本地规则失败'); showErrorMessage('加载本地规则失败');
hideLoading();
} }
} }
// 加载远程规则 // 加载远程规则
async function loadRemoteRules() { async function loadRemoteRules() {
showLoading('加载远程规则...');
try { try {
// 设置当前规则类型 // 设置当前规则类型
currentRulesType = 'remote'; currentRulesType = 'remote';
@@ -136,11 +128,9 @@ async function loadRemoteRules() {
} }
updateRulesTable(rules); updateRulesTable(rules);
hideLoading();
} catch (error) { } catch (error) {
console.error('加载远程规则失败:', error); console.error('加载远程规则失败:', error);
showErrorMessage('加载远程规则失败'); showErrorMessage('加载远程规则失败');
hideLoading();
} }
} }
@@ -179,8 +169,19 @@ function updateRulesTable(rules) {
const deleteBtn = document.createElement('button'); 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.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.dataset.rule = rule;
deleteBtn.innerHTML = '<i class="fa fa-trash"></i>';
deleteBtn.addEventListener('click', handleDeleteRule); // 创建删除图标
const deleteIcon = document.createElement('i');
deleteIcon.className = 'fa fa-trash';
deleteIcon.style.pointerEvents = 'none'; // 防止图标拦截点击事件
deleteBtn.appendChild(deleteIcon);
// 使用普通函数确保this指向按钮元素
deleteBtn.onclick = function(e) {
e.stopPropagation(); // 阻止事件冒泡
handleDeleteRule(e);
};
tdAction.appendChild(deleteBtn); tdAction.appendChild(deleteBtn);
tr.appendChild(tdRule); tr.appendChild(tdRule);
@@ -201,9 +202,36 @@ function updateRulesTable(rules) {
// 处理删除规则 // 处理删除规则
async function handleDeleteRule(e) { async function handleDeleteRule(e) {
const rule = e.target.closest('.delete-rule-btn').dataset.rule; console.log('Delete button clicked');
showLoading('删除规则中...'); let deleteBtn;
// 尝试从事件目标获取按钮元素
deleteBtn = e.target.closest('.delete-rule-btn');
console.log('Delete button from event target:', deleteBtn);
// 尝试从this获取按钮元素备用方案
if (!deleteBtn && this && typeof this.classList === 'object' && this.classList) {
if (this.classList.contains('delete-rule-btn')) {
deleteBtn = this;
console.log('Delete button from this:', deleteBtn);
}
}
if (!deleteBtn) {
console.error('Delete button not found');
return;
}
const rule = deleteBtn.dataset.rule;
console.log('Rule to delete:', rule);
if (!rule) {
console.error('Rule not found in data-rule attribute');
return;
}
try { try {
console.log('Sending DELETE request to /api/shield');
const response = await fetch('/api/shield', { const response = await fetch('/api/shield', {
method: 'DELETE', method: 'DELETE',
headers: { headers: {
@@ -212,24 +240,32 @@ async function handleDeleteRule(e) {
body: JSON.stringify({ rule }) body: JSON.stringify({ rule })
}); });
console.log('Response status:', response.status);
console.log('Response ok:', response.ok);
if (!response.ok) { if (!response.ok) {
throw new Error('Failed to delete rule'); const errorText = await response.text();
throw new Error(`Failed to delete rule: ${response.status} ${errorText}`);
} }
const responseData = await response.json();
console.log('Response data:', responseData);
showSuccessMessage('规则删除成功'); showSuccessMessage('规则删除成功');
console.log('Current rules type:', currentRulesType);
// 根据当前显示的规则类型重新加载对应的规则列表 // 根据当前显示的规则类型重新加载对应的规则列表
if (currentRulesType === 'local') { if (currentRulesType === 'local') {
console.log('Reloading local rules');
loadLocalRules(); loadLocalRules();
} else { } else {
console.log('Reloading remote rules');
loadRemoteRules(); loadRemoteRules();
} }
// 重新加载统计信息 // 重新加载统计信息
loadShieldStats(); loadShieldStats();
hideLoading();
} catch (error) { } catch (error) {
console.error('Error deleting rule:', error); console.error('Error deleting rule:', error);
showErrorMessage('删除规则失败'); showErrorMessage('删除规则失败: ' + error.message);
hideLoading();
} }
} }
@@ -241,7 +277,6 @@ async function handleAddRule() {
return; return;
} }
showLoading('添加规则中...');
try { try {
const response = await fetch('/api/shield', { const response = await fetch('/api/shield', {
method: 'POST', method: 'POST',
@@ -262,17 +297,14 @@ async function handleAddRule() {
loadLocalRules(); loadLocalRules();
// 重新加载统计信息 // 重新加载统计信息
loadShieldStats(); loadShieldStats();
hideLoading();
} catch (error) { } catch (error) {
console.error('Error adding rule:', error); console.error('Error adding rule:', error);
showErrorMessage('添加规则失败'); showErrorMessage('添加规则失败');
hideLoading();
} }
} }
// 加载远程黑名单 // 加载远程黑名单
async function loadRemoteBlacklists() { async function loadRemoteBlacklists() {
showLoading('加载远程黑名单...');
try { try {
const response = await fetch('/api/shield/blacklists'); const response = await fetch('/api/shield/blacklists');
@@ -285,11 +317,9 @@ async function loadRemoteBlacklists() {
// 确保blacklists是数组 // 确保blacklists是数组
const blacklistArray = Array.isArray(blacklists) ? blacklists : []; const blacklistArray = Array.isArray(blacklists) ? blacklists : [];
updateBlacklistsTable(blacklistArray); updateBlacklistsTable(blacklistArray);
hideLoading();
} catch (error) { } catch (error) {
console.error('加载远程黑名单失败:', error); console.error('加载远程黑名单失败:', error);
showErrorMessage('加载远程黑名单失败'); showErrorMessage('加载远程黑名单失败');
hideLoading();
} }
} }
@@ -426,7 +456,6 @@ async function handleUpdateBlacklist(e) {
return; return;
} }
showLoading('更新黑名单中...');
try { try {
const response = await fetch(`/api/shield/blacklists/${encodeURIComponent(url)}/update`, { const response = await fetch(`/api/shield/blacklists/${encodeURIComponent(url)}/update`, {
method: 'POST', method: 'POST',
@@ -448,8 +477,6 @@ async function handleUpdateBlacklist(e) {
} catch (error) { } catch (error) {
console.error('更新黑名单失败:', error); console.error('更新黑名单失败:', error);
showToast('更新黑名单失败: ' + error.message, 'error'); showToast('更新黑名单失败: ' + error.message, 'error');
} finally {
hideLoading();
} }
} }
@@ -467,7 +494,6 @@ async function handleDeleteBlacklist(e) {
return; return;
} }
showLoading('删除黑名单中...');
try { try {
const response = await fetch(`/api/shield/blacklists/${encodeURIComponent(url)}`, { const response = await fetch(`/api/shield/blacklists/${encodeURIComponent(url)}`, {
method: 'DELETE', method: 'DELETE',
@@ -489,8 +515,6 @@ async function handleDeleteBlacklist(e) {
} catch (error) { } catch (error) {
console.error('删除黑名单失败:', error); console.error('删除黑名单失败:', error);
showToast('删除黑名单失败: ' + error.message, 'error'); showToast('删除黑名单失败: ' + error.message, 'error');
} finally {
hideLoading();
} }
} }
@@ -521,7 +545,6 @@ async function handleAddBlacklist(event) {
return; return;
} }
showLoading('添加黑名单中...');
try { try {
const response = await fetch('/api/shield/blacklists', { const response = await fetch('/api/shield/blacklists', {
method: 'POST', method: 'POST',
@@ -553,11 +576,9 @@ async function handleAddBlacklist(event) {
loadRemoteBlacklists(); loadRemoteBlacklists();
// 重新加载统计信息 // 重新加载统计信息
loadShieldStats(); loadShieldStats();
hideLoading();
} catch (error) { } catch (error) {
console.error('Error adding blacklist:', error); console.error('Error adding blacklist:', error);
showErrorMessage(error.message || '添加黑名单失败'); showErrorMessage(error.message || '添加黑名单失败');
hideLoading();
} }
} }
@@ -569,18 +590,26 @@ let currentRulesType = 'local';
// 设置事件监听器 // 设置事件监听器
function setupShieldEventListeners() { function setupShieldEventListeners() {
// 本地规则管理事件 // 本地规则管理事件
document.getElementById('save-rule-btn').addEventListener('click', handleAddRule); const saveRuleBtn = document.getElementById('save-rule-btn');
if (saveRuleBtn) {
// 远程黑名单管理事件 saveRuleBtn.addEventListener('click', handleAddRule);
document.getElementById('save-blacklist-btn').addEventListener('click', handleAddBlacklist);
// 添加切换查看本地规则和远程规则的事件监听
if (document.getElementById('view-local-rules-btn')) {
document.getElementById('view-local-rules-btn').addEventListener('click', loadLocalRules);
} }
if (document.getElementById('view-remote-rules-btn')) { // 远程黑名单管理事件
document.getElementById('view-remote-rules-btn').addEventListener('click', loadRemoteRules); const saveBlacklistBtn = document.getElementById('save-blacklist-btn');
if (saveBlacklistBtn) {
saveBlacklistBtn.addEventListener('click', handleAddBlacklist);
}
// 添加切换查看本地规则和远程规则的事件监听
const viewLocalRulesBtn = document.getElementById('view-local-rules-btn');
if (viewLocalRulesBtn) {
viewLocalRulesBtn.addEventListener('click', loadLocalRules);
}
const viewRemoteRulesBtn = document.getElementById('view-remote-rules-btn');
if (viewRemoteRulesBtn) {
viewRemoteRulesBtn.addEventListener('click', loadRemoteRules);
} }
} }
@@ -594,31 +623,7 @@ function showErrorMessage(message) {
showNotification(message, 'error'); 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') { function showNotification(message, type = 'info') {