屏蔽规则页面丰富显示
This commit is contained in:
@@ -0,0 +1,96 @@
|
|||||||
|
## 问题分析
|
||||||
|
|
||||||
|
当前系统已经实现了黑名单管理的后端API,但前端界面缺少完整的黑名单管理功能。具体来说:
|
||||||
|
|
||||||
|
1. 后端API已经实现了黑名单管理的所有功能:
|
||||||
|
- GET /api/shield/blacklists - 获取所有黑名单
|
||||||
|
- POST /api/shield/blacklists - 添加黑名单
|
||||||
|
- PUT /api/shield/blacklists - 更新黑名单
|
||||||
|
- DELETE /api/shield/blacklists/{name} - 删除黑名单
|
||||||
|
|
||||||
|
2. 前端已经有了一些基础功能:
|
||||||
|
- loadRemoteBlacklists函数用于加载远程黑名单
|
||||||
|
- setupShieldEventListeners函数用于设置事件监听器
|
||||||
|
- 页面上有blacklist-count元素用于显示黑名单数量
|
||||||
|
|
||||||
|
3. 但是,前端缺少完整的黑名单管理界面,包括:
|
||||||
|
- 黑名单列表展示
|
||||||
|
- 添加/编辑/删除黑名单的功能
|
||||||
|
- 启用/禁用黑名单的功能
|
||||||
|
- 更新黑名单的功能
|
||||||
|
|
||||||
|
## 实现方案
|
||||||
|
|
||||||
|
1. **添加黑名单管理HTML界面**:
|
||||||
|
- 在shield-content中添加黑名单管理区域
|
||||||
|
- 包括黑名单列表、添加/编辑表单、操作按钮等
|
||||||
|
|
||||||
|
2. **实现黑名单列表的加载和展示**:
|
||||||
|
- 完善loadRemoteBlacklists函数,将黑名单数据渲染到页面上
|
||||||
|
- 显示黑名单的名称、URL、状态、更新时间等信息
|
||||||
|
- 添加操作按钮(编辑、删除、启用/禁用、更新)
|
||||||
|
|
||||||
|
3. **实现添加/编辑黑名单功能**:
|
||||||
|
- 添加表单用于输入黑名单名称、URL等信息
|
||||||
|
- 实现表单提交功能,调用后端API添加/编辑黑名单
|
||||||
|
|
||||||
|
4. **实现删除黑名单功能**:
|
||||||
|
- 为删除按钮添加事件监听器
|
||||||
|
- 调用后端API删除黑名单
|
||||||
|
- 更新黑名单列表
|
||||||
|
|
||||||
|
5. **实现启用/禁用黑名单功能**:
|
||||||
|
- 为启用/禁用按钮添加事件监听器
|
||||||
|
- 调用后端API更新黑名单状态
|
||||||
|
- 更新黑名单列表
|
||||||
|
|
||||||
|
6. **实现更新黑名单功能**:
|
||||||
|
- 为更新按钮添加事件监听器
|
||||||
|
- 调用后端API更新黑名单
|
||||||
|
- 显示更新状态
|
||||||
|
|
||||||
|
## 实现步骤
|
||||||
|
|
||||||
|
1. **修改HTML文件**:
|
||||||
|
- 在shield-content中添加黑名单管理区域
|
||||||
|
- 添加黑名单列表表格
|
||||||
|
- 添加添加/编辑黑名单表单
|
||||||
|
- 添加操作按钮
|
||||||
|
|
||||||
|
2. **修改shield.js文件**:
|
||||||
|
- 完善loadRemoteBlacklists函数,渲染黑名单列表
|
||||||
|
- 添加添加/编辑/删除黑名单的函数
|
||||||
|
- 添加启用/禁用黑名单的函数
|
||||||
|
- 添加更新黑名单的函数
|
||||||
|
- 完善setupShieldEventListeners函数,添加黑名单管理相关的事件监听器
|
||||||
|
|
||||||
|
3. **测试功能**:
|
||||||
|
- 测试黑名单列表的加载和展示
|
||||||
|
- 测试添加/编辑/删除黑名单功能
|
||||||
|
- 测试启用/禁用黑名单功能
|
||||||
|
- 测试更新黑名单功能
|
||||||
|
|
||||||
|
## 预期效果
|
||||||
|
|
||||||
|
- 用户可以在web界面上查看所有黑名单
|
||||||
|
- 用户可以添加新的黑名单
|
||||||
|
- 用户可以编辑现有黑名单
|
||||||
|
- 用户可以删除黑名单
|
||||||
|
- 用户可以启用/禁用黑名单
|
||||||
|
- 用户可以手动更新黑名单
|
||||||
|
- 所有操作都能实时反映到页面上
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- 确保界面设计与现有系统风格一致
|
||||||
|
- 确保所有操作都有适当的错误处理和提示
|
||||||
|
- 确保所有操作都能实时更新页面数据
|
||||||
|
- 确保功能实现符合后端API的要求
|
||||||
|
|
||||||
|
## 技术细节
|
||||||
|
|
||||||
|
- 使用HTML、CSS(Tailwind CSS)和JavaScript实现前端界面
|
||||||
|
- 使用fetch API调用后端API
|
||||||
|
- 使用事件监听器处理用户交互
|
||||||
|
- 使用DOM操作更新页面内容
|
||||||
|
- 使用异步/等待处理异步操作
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# 实现远程黑名单管理功能
|
||||||
|
|
||||||
|
## 1. 分析现有代码
|
||||||
|
- 远程黑名单管理功能已经在`shield.js`文件中实现
|
||||||
|
- 页面结构已经存在于HTML文件中
|
||||||
|
- 存在`blacklists.js`和`shield.js`两个可能冲突的实现
|
||||||
|
|
||||||
|
## 2. 解决方案
|
||||||
|
### 2.1 检查并解决代码冲突
|
||||||
|
- 移除或整合`blacklists.js`文件,避免与`shield.js`冲突
|
||||||
|
- 确保只使用一个实现来管理远程黑名单
|
||||||
|
|
||||||
|
### 2.2 确保页面加载时正确初始化
|
||||||
|
- 检查`shield.js`中的初始化逻辑
|
||||||
|
- 确保`initShieldPage`函数在页面加载和切换到屏蔽管理页面时被正确调用
|
||||||
|
|
||||||
|
### 2.3 确保web更新数据时同时更新服务器
|
||||||
|
- 检查现有的添加、更新、删除、启用/禁用黑名单的功能
|
||||||
|
- 确保每个操作都通过API请求更新服务器数据
|
||||||
|
- 验证操作完成后是否重新加载数据
|
||||||
|
|
||||||
|
## 3. 实现步骤
|
||||||
|
1. 移除`blacklists.js`文件,避免与`shield.js`冲突
|
||||||
|
2. 检查并确保`shield.js`中的初始化逻辑正确
|
||||||
|
3. 测试远程黑名单管理功能的各个操作
|
||||||
|
4. 验证页面加载时是否正确拉取服务器数据
|
||||||
|
5. 验证web更新数据时是否同时更新服务器数据
|
||||||
|
|
||||||
|
## 4. 预期结果
|
||||||
|
- 页面加载时自动拉取服务器数据
|
||||||
|
- 添加、更新、删除、启用/禁用黑名单时,同时更新服务器数据
|
||||||
|
- 操作完成后,页面数据自动刷新
|
||||||
|
- 没有代码冲突,功能正常运行
|
||||||
@@ -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查询结果的各种网络环境。
|
|
||||||
+9
-5
@@ -20,26 +20,30 @@
|
|||||||
{
|
{
|
||||||
"name": "AdGuard DNS filter",
|
"name": "AdGuard DNS filter",
|
||||||
"url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/filter.txt",
|
"url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/filter.txt",
|
||||||
"enabled": true
|
"enabled": true,
|
||||||
|
"lastUpdateTime": "2025-11-28T14:43:26.972Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Adaway Default Blocklist",
|
"name": "Adaway Default Blocklist",
|
||||||
"url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-Filters/raw/branch/main/hosts/adaway.txt",
|
"url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-Filters/raw/branch/main/hosts/adaway.txt",
|
||||||
"enabled": true
|
"enabled": true,
|
||||||
|
"lastUpdateTime": "2025-11-28T14:41:39.123Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "CHN-anti-AD",
|
"name": "CHN-anti-AD",
|
||||||
"url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-Filters/raw/branch/main/list/easylist.txt",
|
"url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-Filters/raw/branch/main/list/easylist.txt",
|
||||||
"enabled": true
|
"enabled": true,
|
||||||
|
"lastUpdateTime": "2025-11-28T14:41:41.318Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "My GitHub Rules",
|
"name": "My GitHub Rules",
|
||||||
"url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/rules/costomize.txt",
|
"url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/rules/costomize.txt",
|
||||||
"enabled": true
|
"enabled": true,
|
||||||
|
"lastUpdateTime": "2025-11-28T14:42:09.271Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "China List",
|
"name": "China List",
|
||||||
"url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-Filters/raw/branch/main/list/china.txt",
|
"url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/list/china.list",
|
||||||
"enabled": true
|
"enabled": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
# DNS Server Hosts File
|
|
||||||
# Generated by DNS Server
|
|
||||||
|
|
||||||
::1 localhost
|
|
||||||
-10607
File diff suppressed because it is too large
Load Diff
-82585
File diff suppressed because it is too large
Load Diff
-53291
File diff suppressed because it is too large
Load Diff
-1176
File diff suppressed because it is too large
Load Diff
-549303
File diff suppressed because it is too large
Load Diff
@@ -1,2 +0,0 @@
|
|||||||
/example.com/
|
|
||||||
/ad./
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"blockedDomainsCount": {},
|
|
||||||
"resolvedDomainsCount": {},
|
|
||||||
"lastSaved": "2025-11-28T19:19:31.852280335+08:00"
|
|
||||||
}
|
|
||||||
-3584
File diff suppressed because it is too large
Load Diff
BIN
Binary file not shown.
+26
-8
@@ -875,23 +875,41 @@ func (s *Server) handleShieldBlacklists(w http.ResponseWriter, r *http.Request)
|
|||||||
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
|
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
|
||||||
|
|
||||||
case http.MethodPut:
|
case http.MethodPut:
|
||||||
// 更新所有远程黑名单
|
// 更新黑名单列表(包括启用/禁用状态)
|
||||||
blacklists := s.shieldManager.GetBlacklists()
|
var updatedBlacklists []struct {
|
||||||
for i := range blacklists {
|
Name string `json:"Name" json:"name"`
|
||||||
// 更新每个黑名单的时间戳
|
URL string `json:"URL" json:"url"`
|
||||||
blacklists[i].LastUpdateTime = time.Now().Format(time.RFC3339)
|
Enabled bool `json:"Enabled" json:"enabled"`
|
||||||
|
LastUpdateTime string `json:"LastUpdateTime" json:"lastUpdateTime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
s.shieldManager.UpdateBlacklist(blacklists)
|
if err := json.NewDecoder(r.Body).Decode(&updatedBlacklists); err != nil {
|
||||||
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为config.BlacklistEntry类型
|
||||||
|
var newBlacklists []config.BlacklistEntry
|
||||||
|
for _, entry := range updatedBlacklists {
|
||||||
|
newBlacklists = append(newBlacklists, config.BlacklistEntry{
|
||||||
|
Name: entry.Name,
|
||||||
|
URL: entry.URL,
|
||||||
|
Enabled: entry.Enabled,
|
||||||
|
LastUpdateTime: entry.LastUpdateTime,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新黑名单
|
||||||
|
s.shieldManager.UpdateBlacklist(newBlacklists)
|
||||||
// 更新全局配置中的黑名单
|
// 更新全局配置中的黑名单
|
||||||
s.globalConfig.Shield.Blacklists = blacklists
|
s.globalConfig.Shield.Blacklists = newBlacklists
|
||||||
// 保存配置到文件
|
// 保存配置到文件
|
||||||
if err := saveConfigToFile(s.globalConfig, "config.json"); err != nil {
|
if err := saveConfigToFile(s.globalConfig, "config.json"); err != nil {
|
||||||
logger.Error("保存配置文件失败", "error", err)
|
logger.Error("保存配置文件失败", "error", err)
|
||||||
http.Error(w, "保存配置失败", http.StatusInternalServerError)
|
http.Error(w, "保存配置失败", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 重新加载所有规则
|
// 重新加载规则
|
||||||
s.shieldManager.LoadRules()
|
s.shieldManager.LoadRules()
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
|
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
|
||||||
|
|||||||
-119935
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+13
-7
@@ -691,35 +691,39 @@
|
|||||||
<h4 class="text-sm font-medium text-gray-500">域名例外</h4>
|
<h4 class="text-sm font-medium text-gray-500">域名例外</h4>
|
||||||
<i class="fa fa-check-circle text-green-500"></i>
|
<i class="fa fa-check-circle text-green-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-2xl font-bold" id="domain-exceptions-count">0</p>
|
<p class="text-2xl font-bold counter" id="domain-exceptions-count">0</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-purple-50 p-4 rounded-lg">
|
<div class="bg-purple-50 p-4 rounded-lg">
|
||||||
<div class="flex items-center justify-between mb-2">
|
<div class="flex items-center justify-between mb-2">
|
||||||
<h4 class="text-sm font-medium text-gray-500">正则规则</h4>
|
<h4 class="text-sm font-medium text-gray-500">正则规则</h4>
|
||||||
<i class="fa fa-code text-purple-500"></i>
|
<i class="fa fa-code text-purple-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-2xl font-bold" id="regex-rules-count">0</p>
|
<p class="text-2xl font-bold counter" id="regex-rules-count">0</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-yellow-50 p-4 rounded-lg">
|
<div class="bg-yellow-50 p-4 rounded-lg">
|
||||||
<div class="flex items-center justify-between mb-2">
|
<div class="flex items-center justify-between mb-2">
|
||||||
<h4 class="text-sm font-medium text-gray-500">正则例外</h4>
|
<h4 class="text-sm font-medium text-gray-500">正则例外</h4>
|
||||||
<i class="fa fa-exclamation-circle text-yellow-500"></i>
|
<i class="fa fa-exclamation-circle text-yellow-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-2xl font-bold" id="regex-exceptions-count">0</p>
|
<p class="text-2xl font-bold counter" id="regex-exceptions-count">0</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-red-50 p-4 rounded-lg">
|
<div class="bg-red-50 p-4 rounded-lg">
|
||||||
<div class="flex items-center justify-between mb-2">
|
<div class="flex items-center justify-between mb-2">
|
||||||
<h4 class="text-sm font-medium text-gray-500">Hosts规则</h4>
|
<h4 class="text-sm font-medium text-gray-500">Hosts规则</h4>
|
||||||
<i class="fa fa-file-text text-red-500"></i>
|
<i class="fa fa-file-text text-red-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-2xl font-bold" id="hosts-rules-count">0</p>
|
<p class="text-2xl font-bold counter" id="hosts-rules-count">0</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-indigo-50 p-4 rounded-lg">
|
<div class="bg-indigo-50 p-4 rounded-lg">
|
||||||
<div class="flex items-center justify-between mb-2">
|
<div class="flex items-center justify-between mb-2">
|
||||||
<h4 class="text-sm font-medium text-gray-500">黑名单数量</h4>
|
<h4 class="text-sm font-medium text-gray-500">黑名单数量</h4>
|
||||||
<i class="fa fa-ban text-indigo-500"></i>
|
<i class="fa fa-ban text-indigo-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-2xl font-bold" id="blacklist-count">0</p>
|
<p class="text-2xl font-bold counter" id="blacklist-count">0</p>
|
||||||
|
<div class="flex items-center justify-between mt-2">
|
||||||
|
<h5 class="text-xs font-medium text-gray-500">禁用数量</h5>
|
||||||
|
<p class="text-sm font-bold text-red-600 counter" id="blacklist-disabled-count">0</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -771,10 +775,11 @@
|
|||||||
<label for="blacklist-url" class="block text-sm font-medium text-gray-700 mb-1">URL</label>
|
<label for="blacklist-url" class="block text-sm font-medium text-gray-700 mb-1">URL</label>
|
||||||
<input type="text" id="blacklist-url" placeholder="输入黑名单URL" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
|
<input type="text" id="blacklist-url" placeholder="输入黑名单URL" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-end">
|
<div class="flex items-end space-x-2">
|
||||||
<button id="save-blacklist-btn" class="px-4 py-2 bg-success text-white rounded-md hover:bg-success/90 transition-colors">
|
<button id="save-blacklist-btn" class="px-4 py-2 bg-success text-white rounded-md hover:bg-success/90 transition-colors">
|
||||||
保存
|
保存
|
||||||
</button>
|
</button>
|
||||||
|
<div id="save-blacklist-status" class="flex items-center text-sm"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -787,12 +792,13 @@
|
|||||||
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500">名称</th>
|
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500">名称</th>
|
||||||
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500">URL</th>
|
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500">URL</th>
|
||||||
<th class="text-center py-3 px-4 text-sm font-medium text-gray-500">状态</th>
|
<th class="text-center py-3 px-4 text-sm font-medium text-gray-500">状态</th>
|
||||||
|
<th class="text-center py-3 px-4 text-sm font-medium text-gray-500">更新状态</th>
|
||||||
<th class="text-right py-3 px-4 text-sm font-medium text-gray-500">操作</th>
|
<th class="text-right py-3 px-4 text-sm font-medium text-gray-500">操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="blacklists-table-body">
|
<tbody id="blacklists-table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4" class="py-4 text-center text-gray-500">暂无黑名单</td>
|
<td colspan="5" class="py-4 text-center text-gray-500">暂无黑名单</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
+332
-53
@@ -12,16 +12,115 @@ async function initShieldPage() {
|
|||||||
setupShieldEventListeners();
|
setupShieldEventListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新状态显示函数
|
||||||
|
function updateStatus(url, status, message) {
|
||||||
|
const statusElement = document.getElementById(`update-status-${encodeURIComponent(url)}`);
|
||||||
|
if (!statusElement) return;
|
||||||
|
|
||||||
|
let statusHTML = '';
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case 'loading':
|
||||||
|
statusHTML = '<span class="text-blue-500"><i class="fa fa-spinner fa-spin"></i> 处理中...</span>';
|
||||||
|
break;
|
||||||
|
case 'success':
|
||||||
|
statusHTML = `<span class="text-green-500"><i class="fa fa-check"></i> ${message || '成功'}</span>`;
|
||||||
|
break;
|
||||||
|
case 'error':
|
||||||
|
statusHTML = `<span class="text-red-500"><i class="fa fa-times"></i> ${message || '失败'}</span>`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
statusHTML = '<span class="text-gray-400">-</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
statusElement.innerHTML = statusHTML;
|
||||||
|
|
||||||
|
// 如果是成功或失败状态,3秒后恢复默认状态
|
||||||
|
if (status === 'success' || status === 'error') {
|
||||||
|
setTimeout(() => {
|
||||||
|
updateStatus(url, 'default');
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数字更新动画函数
|
||||||
|
function animateCounter(element, target, duration = 1000) {
|
||||||
|
// 确保element存在
|
||||||
|
if (!element) return;
|
||||||
|
|
||||||
|
// 清除元素上可能存在的现有定时器
|
||||||
|
if (element.animationTimer) {
|
||||||
|
clearInterval(element.animationTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保target是数字
|
||||||
|
const targetNum = typeof target === 'number' ? target : parseInt(target) || 0;
|
||||||
|
|
||||||
|
// 获取起始值,使用更安全的方法
|
||||||
|
const startText = element.textContent.replace(/[^0-9]/g, '');
|
||||||
|
const start = parseInt(startText) || 0;
|
||||||
|
|
||||||
|
// 如果起始值和目标值相同,直接返回
|
||||||
|
if (start === targetNum) {
|
||||||
|
element.textContent = targetNum;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let current = start;
|
||||||
|
const increment = (targetNum - start) / (duration / 16); // 16ms per frame
|
||||||
|
|
||||||
|
// 使用requestAnimationFrame实现更平滑的动画
|
||||||
|
let startTime = null;
|
||||||
|
|
||||||
|
function updateCounter(timestamp) {
|
||||||
|
if (!startTime) startTime = timestamp;
|
||||||
|
const elapsed = timestamp - startTime;
|
||||||
|
const progress = Math.min(elapsed / duration, 1);
|
||||||
|
|
||||||
|
// 使用缓动函数使动画更自然
|
||||||
|
const easeOutQuad = progress * (2 - progress);
|
||||||
|
current = start + (targetNum - start) * easeOutQuad;
|
||||||
|
|
||||||
|
// 根据方向使用floor或ceil确保平滑过渡
|
||||||
|
const displayValue = targetNum > start ? Math.floor(current) : Math.ceil(current);
|
||||||
|
element.textContent = displayValue;
|
||||||
|
|
||||||
|
if (progress < 1) {
|
||||||
|
// 继续动画
|
||||||
|
element.animationTimer = requestAnimationFrame(updateCounter);
|
||||||
|
} else {
|
||||||
|
// 动画结束,确保显示准确值
|
||||||
|
element.textContent = targetNum;
|
||||||
|
// 清除定时器引用
|
||||||
|
element.animationTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始动画
|
||||||
|
element.animationTimer = requestAnimationFrame(updateCounter);
|
||||||
|
}
|
||||||
|
|
||||||
// 加载屏蔽规则统计信息
|
// 加载屏蔽规则统计信息
|
||||||
async function loadShieldStats() {
|
async function loadShieldStats() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/shield');
|
// 获取屏蔽规则统计信息
|
||||||
|
const shieldResponse = await fetch('/api/shield');
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!shieldResponse.ok) {
|
||||||
throw new Error(`加载失败: ${response.status}`);
|
throw new Error(`加载屏蔽统计失败: ${shieldResponse.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const stats = await response.json();
|
const stats = await shieldResponse.json();
|
||||||
|
|
||||||
|
// 获取黑名单列表,计算禁用数量
|
||||||
|
const blacklistsResponse = await fetch('/api/shield/blacklists');
|
||||||
|
|
||||||
|
if (!blacklistsResponse.ok) {
|
||||||
|
throw new Error(`加载黑名单列表失败: ${blacklistsResponse.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const blacklists = await blacklistsResponse.json();
|
||||||
|
const disabledBlacklistCount = blacklists.filter(blacklist => !blacklist.enabled).length;
|
||||||
|
|
||||||
// 更新统计信息
|
// 更新统计信息
|
||||||
const elements = [
|
const elements = [
|
||||||
@@ -36,12 +135,18 @@ async function loadShieldStats() {
|
|||||||
elements.forEach(item => {
|
elements.forEach(item => {
|
||||||
const element = document.getElementById(item.id);
|
const element = document.getElementById(item.id);
|
||||||
if (element) {
|
if (element) {
|
||||||
element.textContent = item.value || 0;
|
animateCounter(element, item.value || 0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 更新禁用黑名单数量
|
||||||
|
const disabledBlacklistElement = document.getElementById('blacklist-disabled-count');
|
||||||
|
if (disabledBlacklistElement) {
|
||||||
|
animateCounter(disabledBlacklistElement, disabledBlacklistCount);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载屏蔽规则统计信息失败:', error);
|
console.error('加载屏蔽规则统计信息失败:', error);
|
||||||
showErrorMessage('加载屏蔽规则统计信息失败');
|
showNotification('加载屏蔽规则统计信息失败', 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +191,7 @@ async function loadLocalRules() {
|
|||||||
updateRulesTable(rules);
|
updateRulesTable(rules);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载本地规则失败:', error);
|
console.error('加载本地规则失败:', error);
|
||||||
showErrorMessage('加载本地规则失败');
|
showNotification('加载本地规则失败', 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +235,7 @@ async function loadRemoteRules() {
|
|||||||
updateRulesTable(rules);
|
updateRulesTable(rules);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载远程规则失败:', error);
|
console.error('加载远程规则失败:', error);
|
||||||
showErrorMessage('加载远程规则失败');
|
showNotification('加载远程规则失败', 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,7 +356,7 @@ async function handleDeleteRule(e) {
|
|||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
console.log('Response data:', responseData);
|
console.log('Response data:', responseData);
|
||||||
|
|
||||||
showSuccessMessage('规则删除成功');
|
showNotification('规则删除成功', 'success');
|
||||||
console.log('Current rules type:', currentRulesType);
|
console.log('Current rules type:', currentRulesType);
|
||||||
// 根据当前显示的规则类型重新加载对应的规则列表
|
// 根据当前显示的规则类型重新加载对应的规则列表
|
||||||
if (currentRulesType === 'local') {
|
if (currentRulesType === 'local') {
|
||||||
@@ -265,7 +370,7 @@ async function handleDeleteRule(e) {
|
|||||||
loadShieldStats();
|
loadShieldStats();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting rule:', error);
|
console.error('Error deleting rule:', error);
|
||||||
showErrorMessage('删除规则失败: ' + error.message);
|
showNotification('删除规则失败: ' + error.message, 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +378,7 @@ async function handleDeleteRule(e) {
|
|||||||
async function handleAddRule() {
|
async function handleAddRule() {
|
||||||
const rule = document.getElementById('new-rule').value.trim();
|
const rule = document.getElementById('new-rule').value.trim();
|
||||||
if (!rule) {
|
if (!rule) {
|
||||||
showErrorMessage('规则不能为空');
|
showNotification('规则不能为空', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,7 +395,7 @@ async function handleAddRule() {
|
|||||||
throw new Error('Failed to add rule');
|
throw new Error('Failed to add rule');
|
||||||
}
|
}
|
||||||
|
|
||||||
showSuccessMessage('规则添加成功');
|
showNotification('规则添加成功', 'success');
|
||||||
// 清空输入框
|
// 清空输入框
|
||||||
document.getElementById('new-rule').value = '';
|
document.getElementById('new-rule').value = '';
|
||||||
// 重新加载规则
|
// 重新加载规则
|
||||||
@@ -299,7 +404,7 @@ async function handleAddRule() {
|
|||||||
loadShieldStats();
|
loadShieldStats();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error adding rule:', error);
|
console.error('Error adding rule:', error);
|
||||||
showErrorMessage('添加规则失败');
|
showNotification('添加规则失败', 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,7 +424,7 @@ async function loadRemoteBlacklists() {
|
|||||||
updateBlacklistsTable(blacklistArray);
|
updateBlacklistsTable(blacklistArray);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载远程黑名单失败:', error);
|
console.error('加载远程黑名单失败:', error);
|
||||||
showErrorMessage('加载远程黑名单失败');
|
showNotification('加载远程黑名单失败', 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,30 +470,24 @@ function updateBlacklistsTable(blacklists) {
|
|||||||
// 名称单元格
|
// 名称单元格
|
||||||
const tdName = document.createElement('td');
|
const tdName = document.createElement('td');
|
||||||
tdName.className = 'py-3 px-4';
|
tdName.className = 'py-3 px-4';
|
||||||
tdName.textContent = blacklist.Name || '未命名';
|
tdName.textContent = blacklist.name || '未命名';
|
||||||
|
|
||||||
// URL单元格
|
// URL单元格
|
||||||
const tdUrl = document.createElement('td');
|
const tdUrl = document.createElement('td');
|
||||||
tdUrl.className = 'py-3 px-4 truncate max-w-xs';
|
tdUrl.className = 'py-3 px-4 truncate max-w-xs';
|
||||||
tdUrl.textContent = blacklist.URL;
|
tdUrl.textContent = blacklist.url;
|
||||||
|
|
||||||
// 状态单元格
|
// 状态单元格
|
||||||
const tdStatus = document.createElement('td');
|
const tdStatus = document.createElement('td');
|
||||||
tdStatus.className = 'py-3 px-4 text-center';
|
tdStatus.className = 'py-3 px-4 text-center';
|
||||||
|
|
||||||
// 判断状态颜色:绿色(正常)、黄色(过期)、灰色(禁用)
|
// 判断状态颜色:绿色(启用)、灰色(禁用)
|
||||||
let statusColor = 'bg-gray-300'; // 默认禁用
|
let statusColor = 'bg-gray-300'; // 默认禁用
|
||||||
let statusText = '禁用';
|
let statusText = '禁用';
|
||||||
|
|
||||||
if (blacklist.Enabled) {
|
if (blacklist.enabled) {
|
||||||
const expired = isBlacklistExpired(blacklist.lastUpdateTime || blacklist.LastUpdateTime);
|
statusColor = 'bg-success'; // 绿色表示启用
|
||||||
if (expired) {
|
statusText = '启用';
|
||||||
statusColor = 'bg-warning'; // 黄色表示过期
|
|
||||||
statusText = '过期';
|
|
||||||
} else {
|
|
||||||
statusColor = 'bg-success'; // 绿色表示正常
|
|
||||||
statusText = '正常';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusContainer = document.createElement('div');
|
const statusContainer = document.createElement('div');
|
||||||
@@ -406,14 +505,29 @@ function updateBlacklistsTable(blacklists) {
|
|||||||
statusContainer.appendChild(statusTextSpan);
|
statusContainer.appendChild(statusTextSpan);
|
||||||
tdStatus.appendChild(statusContainer);
|
tdStatus.appendChild(statusContainer);
|
||||||
|
|
||||||
|
// 更新状态单元格
|
||||||
|
const tdUpdateStatus = document.createElement('td');
|
||||||
|
tdUpdateStatus.className = 'py-3 px-4 text-center';
|
||||||
|
tdUpdateStatus.id = `update-status-${encodeURIComponent(blacklist.url)}`;
|
||||||
|
tdUpdateStatus.innerHTML = '<span class="text-gray-400">-</span>';
|
||||||
|
|
||||||
// 操作单元格
|
// 操作单元格
|
||||||
const tdActions = document.createElement('td');
|
const tdActions = document.createElement('td');
|
||||||
tdActions.className = 'py-3 px-4 text-right space-x-2';
|
tdActions.className = 'py-3 px-4 text-right space-x-2';
|
||||||
|
|
||||||
|
// 启用/禁用按钮
|
||||||
|
const toggleBtn = document.createElement('button');
|
||||||
|
toggleBtn.className = `toggle-blacklist-btn px-3 py-1 rounded-md transition-colors text-sm ${blacklist.enabled ? 'bg-warning text-white hover:bg-warning/90' : 'bg-success text-white hover:bg-success/90'}`;
|
||||||
|
toggleBtn.dataset.url = blacklist.url;
|
||||||
|
toggleBtn.dataset.enabled = blacklist.enabled;
|
||||||
|
toggleBtn.innerHTML = `<i class="fa fa-${blacklist.enabled ? 'toggle-on' : 'toggle-off'}"></i>`;
|
||||||
|
toggleBtn.title = blacklist.enabled ? '禁用黑名单' : '启用黑名单';
|
||||||
|
toggleBtn.addEventListener('click', handleToggleBlacklist);
|
||||||
|
|
||||||
// 刷新按钮
|
// 刷新按钮
|
||||||
const refreshBtn = document.createElement('button');
|
const refreshBtn = document.createElement('button');
|
||||||
refreshBtn.className = 'update-blacklist-btn px-3 py-1 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors text-sm';
|
refreshBtn.className = 'update-blacklist-btn px-3 py-1 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors text-sm';
|
||||||
refreshBtn.dataset.url = blacklist.URL;
|
refreshBtn.dataset.url = blacklist.url;
|
||||||
refreshBtn.innerHTML = '<i class="fa fa-refresh"></i>';
|
refreshBtn.innerHTML = '<i class="fa fa-refresh"></i>';
|
||||||
refreshBtn.title = '刷新黑名单';
|
refreshBtn.title = '刷新黑名单';
|
||||||
refreshBtn.addEventListener('click', handleUpdateBlacklist);
|
refreshBtn.addEventListener('click', handleUpdateBlacklist);
|
||||||
@@ -421,17 +535,19 @@ function updateBlacklistsTable(blacklists) {
|
|||||||
// 删除按钮
|
// 删除按钮
|
||||||
const deleteBtn = document.createElement('button');
|
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.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.dataset.url = blacklist.url;
|
||||||
deleteBtn.innerHTML = '<i class="fa fa-trash"></i>';
|
deleteBtn.innerHTML = '<i class="fa fa-trash"></i>';
|
||||||
deleteBtn.title = '删除黑名单';
|
deleteBtn.title = '删除黑名单';
|
||||||
deleteBtn.addEventListener('click', handleDeleteBlacklist);
|
deleteBtn.addEventListener('click', handleDeleteBlacklist);
|
||||||
|
|
||||||
|
tdActions.appendChild(toggleBtn);
|
||||||
tdActions.appendChild(refreshBtn);
|
tdActions.appendChild(refreshBtn);
|
||||||
tdActions.appendChild(deleteBtn);
|
tdActions.appendChild(deleteBtn);
|
||||||
|
|
||||||
tr.appendChild(tdName);
|
tr.appendChild(tdName);
|
||||||
tr.appendChild(tdUrl);
|
tr.appendChild(tdUrl);
|
||||||
tr.appendChild(tdStatus);
|
tr.appendChild(tdStatus);
|
||||||
|
tr.appendChild(tdUpdateStatus);
|
||||||
tr.appendChild(tdActions);
|
tr.appendChild(tdActions);
|
||||||
fragment.appendChild(tr);
|
fragment.appendChild(tr);
|
||||||
});
|
});
|
||||||
@@ -449,43 +565,93 @@ function updateBlacklistsTable(blacklists) {
|
|||||||
|
|
||||||
// 处理更新单个黑名单
|
// 处理更新单个黑名单
|
||||||
async function handleUpdateBlacklist(e) {
|
async function handleUpdateBlacklist(e) {
|
||||||
const url = e.target.closest('.update-blacklist-btn').dataset.url;
|
// 确保获取到正确的按钮元素
|
||||||
|
const btn = e.target.closest('.update-blacklist-btn');
|
||||||
|
if (!btn) {
|
||||||
|
console.error('未找到更新按钮元素');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = btn.dataset.url;
|
||||||
|
|
||||||
if (!url) {
|
if (!url) {
|
||||||
showToast('无效的黑名单URL', 'error');
|
showNotification('无效的黑名单URL', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/shield/blacklists/${encodeURIComponent(url)}/update`, {
|
// 显示加载状态
|
||||||
method: 'POST',
|
updateStatus(url, 'loading');
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
// 获取当前所有黑名单
|
||||||
|
const response = await fetch('/api/shield/blacklists');
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`获取黑名单失败: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const blacklists = await response.json();
|
||||||
|
|
||||||
|
// 找到目标黑名单并更新其状态
|
||||||
|
const updatedBlacklists = blacklists.map(blacklist => {
|
||||||
|
if (blacklist.url === url) {
|
||||||
|
return {
|
||||||
|
Name: blacklist.name,
|
||||||
|
URL: blacklist.url,
|
||||||
|
Enabled: blacklist.enabled,
|
||||||
|
LastUpdateTime: new Date().toISOString()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
return {
|
||||||
|
Name: blacklist.name,
|
||||||
|
URL: blacklist.url,
|
||||||
|
Enabled: blacklist.enabled,
|
||||||
|
LastUpdateTime: blacklist.lastUpdateTime || blacklist.LastUpdateTime
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
// 发送更新请求
|
||||||
const errorData = await response.json().catch(() => ({}));
|
const updateResponse = await fetch('/api/shield/blacklists', {
|
||||||
throw new Error(errorData.error || 'Failed to update blacklist');
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(updatedBlacklists)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!updateResponse.ok) {
|
||||||
|
const errorText = await updateResponse.text();
|
||||||
|
throw new Error(`更新失败: ${updateResponse.status} ${errorText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 显示成功状态
|
||||||
|
updateStatus(url, 'success');
|
||||||
|
|
||||||
// 重新加载黑名单
|
// 重新加载黑名单
|
||||||
loadRemoteBlacklists();
|
loadRemoteBlacklists();
|
||||||
// 重新加载统计信息
|
// 重新加载统计信息
|
||||||
loadShieldStats();
|
loadShieldStats();
|
||||||
showToast('黑名单更新成功');
|
showNotification('黑名单更新成功', 'success');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('更新黑名单失败:', error);
|
console.error('更新黑名单失败:', error);
|
||||||
showToast('更新黑名单失败: ' + error.message, 'error');
|
// 显示错误状态
|
||||||
|
updateStatus(url, 'error', error.message);
|
||||||
|
showNotification('更新黑名单失败: ' + error.message, 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理删除黑名单
|
// 处理删除黑名单
|
||||||
async function handleDeleteBlacklist(e) {
|
async function handleDeleteBlacklist(e) {
|
||||||
const url = e.target.closest('.delete-blacklist-btn').dataset.url;
|
// 确保获取到正确的按钮元素
|
||||||
|
const btn = e.target.closest('.delete-blacklist-btn');
|
||||||
|
if (!btn) {
|
||||||
|
console.error('未找到删除按钮元素');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = btn.dataset.url;
|
||||||
|
|
||||||
if (!url) {
|
if (!url) {
|
||||||
showToast('无效的黑名单URL', 'error');
|
showNotification('无效的黑名单URL', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -495,26 +661,124 @@ async function handleDeleteBlacklist(e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/shield/blacklists/${encodeURIComponent(url)}`, {
|
// 显示加载状态
|
||||||
method: 'DELETE',
|
updateStatus(url, 'loading');
|
||||||
|
|
||||||
|
// 获取当前所有黑名单
|
||||||
|
const response = await fetch('/api/shield/blacklists');
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`获取黑名单失败: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const blacklists = await response.json();
|
||||||
|
|
||||||
|
// 过滤掉要删除的黑名单
|
||||||
|
const updatedBlacklists = blacklists.filter(blacklist => blacklist.url !== url);
|
||||||
|
|
||||||
|
// 发送更新请求
|
||||||
|
const updateResponse = await fetch('/api/shield/blacklists', {
|
||||||
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
}
|
},
|
||||||
|
body: JSON.stringify(updatedBlacklists)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!updateResponse.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
const errorText = await updateResponse.text();
|
||||||
throw new Error(errorData.error || 'Failed to delete blacklist');
|
throw new Error(`删除失败: ${updateResponse.status} ${errorText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 显示成功状态
|
||||||
|
updateStatus(url, 'success', '已删除');
|
||||||
|
|
||||||
// 重新加载黑名单
|
// 重新加载黑名单
|
||||||
loadRemoteBlacklists();
|
loadRemoteBlacklists();
|
||||||
// 重新加载统计信息
|
// 重新加载统计信息
|
||||||
loadShieldStats();
|
loadShieldStats();
|
||||||
showToast('黑名单删除成功');
|
showNotification('黑名单删除成功', 'success');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('删除黑名单失败:', error);
|
console.error('删除黑名单失败:', error);
|
||||||
showToast('删除黑名单失败: ' + error.message, 'error');
|
// 显示错误状态
|
||||||
|
updateStatus(url, 'error', error.message);
|
||||||
|
showNotification('删除黑名单失败: ' + error.message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理启用/禁用黑名单
|
||||||
|
async function handleToggleBlacklist(e) {
|
||||||
|
// 确保获取到正确的按钮元素
|
||||||
|
const btn = e.target.closest('.toggle-blacklist-btn');
|
||||||
|
if (!btn) {
|
||||||
|
console.error('未找到启用/禁用按钮元素');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = btn.dataset.url;
|
||||||
|
const currentEnabled = btn.dataset.enabled === 'true';
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
showNotification('无效的黑名单URL', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 显示加载状态
|
||||||
|
updateStatus(url, 'loading');
|
||||||
|
|
||||||
|
// 获取当前所有黑名单
|
||||||
|
const response = await fetch('/api/shield/blacklists');
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`获取黑名单失败: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const blacklists = await response.json();
|
||||||
|
|
||||||
|
// 找到目标黑名单并更新其状态
|
||||||
|
const updatedBlacklists = blacklists.map(blacklist => {
|
||||||
|
if (blacklist.url === url) {
|
||||||
|
return {
|
||||||
|
Name: blacklist.name,
|
||||||
|
URL: blacklist.url,
|
||||||
|
Enabled: !currentEnabled,
|
||||||
|
LastUpdateTime: blacklist.lastUpdateTime || blacklist.LastUpdateTime
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
Name: blacklist.name,
|
||||||
|
URL: blacklist.url,
|
||||||
|
Enabled: blacklist.enabled,
|
||||||
|
LastUpdateTime: blacklist.lastUpdateTime || blacklist.LastUpdateTime
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 发送更新请求
|
||||||
|
const updateResponse = await fetch('/api/shield/blacklists', {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(updatedBlacklists)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!updateResponse.ok) {
|
||||||
|
const errorText = await updateResponse.text();
|
||||||
|
throw new Error(`更新状态失败: ${updateResponse.status} ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示成功状态
|
||||||
|
updateStatus(url, 'success', currentEnabled ? '已禁用' : '已启用');
|
||||||
|
|
||||||
|
// 重新加载黑名单
|
||||||
|
loadRemoteBlacklists();
|
||||||
|
// 重新加载统计信息
|
||||||
|
loadShieldStats();
|
||||||
|
showNotification(`黑名单已${currentEnabled ? '禁用' : '启用'}`, 'success');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('启用/禁用黑名单失败:', error);
|
||||||
|
// 显示错误状态
|
||||||
|
updateStatus(url, 'error', error.message);
|
||||||
|
showNotification('启用/禁用黑名单失败: ' + error.message, 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,13 +791,14 @@ async function handleAddBlacklist(event) {
|
|||||||
|
|
||||||
const nameInput = document.getElementById('blacklist-name');
|
const nameInput = document.getElementById('blacklist-name');
|
||||||
const urlInput = document.getElementById('blacklist-url');
|
const urlInput = document.getElementById('blacklist-url');
|
||||||
|
const statusElement = document.getElementById('save-blacklist-status');
|
||||||
|
|
||||||
const name = nameInput ? nameInput.value.trim() : '';
|
const name = nameInput ? nameInput.value.trim() : '';
|
||||||
const url = urlInput ? urlInput.value.trim() : '';
|
const url = urlInput ? urlInput.value.trim() : '';
|
||||||
|
|
||||||
// 简单验证
|
// 简单验证
|
||||||
if (!name || !url) {
|
if (!name || !url) {
|
||||||
showErrorMessage('名称和URL不能为空');
|
showNotification('名称和URL不能为空', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,11 +806,15 @@ async function handleAddBlacklist(event) {
|
|||||||
try {
|
try {
|
||||||
new URL(url);
|
new URL(url);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showErrorMessage('URL格式不正确');
|
showNotification('URL格式不正确', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 显示加载状态
|
||||||
|
statusElement.innerHTML = '<i class="fa fa-spinner fa-spin text-blue-500"></i> 正在添加...';
|
||||||
|
|
||||||
|
// 发送添加请求
|
||||||
const response = await fetch('/api/shield/blacklists', {
|
const response = await fetch('/api/shield/blacklists', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -568,7 +837,10 @@ async function handleAddBlacklist(event) {
|
|||||||
throw new Error(errorMessage);
|
throw new Error(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
showSuccessMessage('黑名单添加成功');
|
// 显示成功状态
|
||||||
|
statusElement.innerHTML = '<span class="text-green-500"><i class="fa fa-check"></i> 成功</span>';
|
||||||
|
|
||||||
|
showNotification('黑名单添加成功', 'success');
|
||||||
// 清空输入框
|
// 清空输入框
|
||||||
if (nameInput) nameInput.value = '';
|
if (nameInput) nameInput.value = '';
|
||||||
if (urlInput) urlInput.value = '';
|
if (urlInput) urlInput.value = '';
|
||||||
@@ -578,7 +850,14 @@ async function handleAddBlacklist(event) {
|
|||||||
loadShieldStats();
|
loadShieldStats();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error adding blacklist:', error);
|
console.error('Error adding blacklist:', error);
|
||||||
showErrorMessage(error.message || '添加黑名单失败');
|
// 显示错误状态
|
||||||
|
statusElement.innerHTML = `<span class="text-red-500"><i class="fa fa-times"></i> 失败</span>`;
|
||||||
|
showNotification(error.message || '添加黑名单失败', 'error');
|
||||||
|
} finally {
|
||||||
|
// 3秒后清除状态显示
|
||||||
|
setTimeout(() => {
|
||||||
|
statusElement.innerHTML = '';
|
||||||
|
}, 3000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user