屏蔽规则页面丰富显示
This commit is contained in:
96
.trae/documents/实现web远程黑名单管理功能.md
Normal file
96
.trae/documents/实现web远程黑名单管理功能.md
Normal file
@@ -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操作更新页面内容
|
||||
- 使用异步/等待处理异步操作
|
||||
33
.trae/documents/实现远程黑名单管理功能.md
Normal file
33
.trae/documents/实现远程黑名单管理功能.md
Normal file
@@ -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. 预期结果
|
||||
- 页面加载时自动拉取服务器数据
|
||||
- 添加、更新、删除、启用/禁用黑名单时,同时更新服务器数据
|
||||
- 操作完成后,页面数据自动刷新
|
||||
- 没有代码冲突,功能正常运行
|
||||
81
ReadMe.md
81
ReadMe.md
@@ -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查询结果的各种网络环境。
|
||||
14
config.json
14
config.json
@@ -20,26 +20,30 @@
|
||||
{
|
||||
"name": "AdGuard DNS filter",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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
|
||||
}
|
||||
],
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# DNS Server Hosts File
|
||||
# Generated by DNS Server
|
||||
|
||||
::1 localhost
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
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
data/stats.json
3584
data/stats.json
File diff suppressed because it is too large
Load Diff
BIN
dns-server
BIN
dns-server
Binary file not shown.
@@ -875,23 +875,41 @@ func (s *Server) handleShieldBlacklists(w http.ResponseWriter, r *http.Request)
|
||||
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
|
||||
|
||||
case http.MethodPut:
|
||||
// 更新所有远程黑名单
|
||||
blacklists := s.shieldManager.GetBlacklists()
|
||||
for i := range blacklists {
|
||||
// 更新每个黑名单的时间戳
|
||||
blacklists[i].LastUpdateTime = time.Now().Format(time.RFC3339)
|
||||
// 更新黑名单列表(包括启用/禁用状态)
|
||||
var updatedBlacklists []struct {
|
||||
Name string `json:"Name" json:"name"`
|
||||
URL string `json:"URL" json:"url"`
|
||||
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 {
|
||||
logger.Error("保存配置文件失败", "error", err)
|
||||
http.Error(w, "保存配置失败", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// 重新加载所有规则
|
||||
// 重新加载规则
|
||||
s.shieldManager.LoadRules()
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
|
||||
|
||||
119935
logs/dns-server.log
119935
logs/dns-server.log
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -691,35 +691,39 @@
|
||||
<h4 class="text-sm font-medium text-gray-500">域名例外</h4>
|
||||
<i class="fa fa-check-circle text-green-500"></i>
|
||||
</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 class="bg-purple-50 p-4 rounded-lg">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h4 class="text-sm font-medium text-gray-500">正则规则</h4>
|
||||
<i class="fa fa-code text-purple-500"></i>
|
||||
</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 class="bg-yellow-50 p-4 rounded-lg">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h4 class="text-sm font-medium text-gray-500">正则例外</h4>
|
||||
<i class="fa fa-exclamation-circle text-yellow-500"></i>
|
||||
</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 class="bg-red-50 p-4 rounded-lg">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h4 class="text-sm font-medium text-gray-500">Hosts规则</h4>
|
||||
<i class="fa fa-file-text text-red-500"></i>
|
||||
</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 class="bg-indigo-50 p-4 rounded-lg">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h4 class="text-sm font-medium text-gray-500">黑名单数量</h4>
|
||||
<i class="fa fa-ban text-indigo-500"></i>
|
||||
</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>
|
||||
@@ -771,10 +775,11 @@
|
||||
<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">
|
||||
</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>
|
||||
<div id="save-blacklist-status" class="flex items-center text-sm"></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">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-right py-3 px-4 text-sm font-medium text-gray-500">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="blacklists-table-body">
|
||||
<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>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -12,16 +12,115 @@ async function initShieldPage() {
|
||||
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() {
|
||||
try {
|
||||
const response = await fetch('/api/shield');
|
||||
// 获取屏蔽规则统计信息
|
||||
const shieldResponse = await fetch('/api/shield');
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`加载失败: ${response.status}`);
|
||||
if (!shieldResponse.ok) {
|
||||
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 = [
|
||||
@@ -36,12 +135,18 @@ async function loadShieldStats() {
|
||||
elements.forEach(item => {
|
||||
const element = document.getElementById(item.id);
|
||||
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) {
|
||||
console.error('加载屏蔽规则统计信息失败:', error);
|
||||
showErrorMessage('加载屏蔽规则统计信息失败');
|
||||
showNotification('加载屏蔽规则统计信息失败', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +191,7 @@ async function loadLocalRules() {
|
||||
updateRulesTable(rules);
|
||||
} catch (error) {
|
||||
console.error('加载本地规则失败:', error);
|
||||
showErrorMessage('加载本地规则失败');
|
||||
showNotification('加载本地规则失败', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +235,7 @@ async function loadRemoteRules() {
|
||||
updateRulesTable(rules);
|
||||
} catch (error) {
|
||||
console.error('加载远程规则失败:', error);
|
||||
showErrorMessage('加载远程规则失败');
|
||||
showNotification('加载远程规则失败', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,7 +356,7 @@ async function handleDeleteRule(e) {
|
||||
const responseData = await response.json();
|
||||
console.log('Response data:', responseData);
|
||||
|
||||
showSuccessMessage('规则删除成功');
|
||||
showNotification('规则删除成功', 'success');
|
||||
console.log('Current rules type:', currentRulesType);
|
||||
// 根据当前显示的规则类型重新加载对应的规则列表
|
||||
if (currentRulesType === 'local') {
|
||||
@@ -265,7 +370,7 @@ async function handleDeleteRule(e) {
|
||||
loadShieldStats();
|
||||
} catch (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() {
|
||||
const rule = document.getElementById('new-rule').value.trim();
|
||||
if (!rule) {
|
||||
showErrorMessage('规则不能为空');
|
||||
showNotification('规则不能为空', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -290,7 +395,7 @@ async function handleAddRule() {
|
||||
throw new Error('Failed to add rule');
|
||||
}
|
||||
|
||||
showSuccessMessage('规则添加成功');
|
||||
showNotification('规则添加成功', 'success');
|
||||
// 清空输入框
|
||||
document.getElementById('new-rule').value = '';
|
||||
// 重新加载规则
|
||||
@@ -299,7 +404,7 @@ async function handleAddRule() {
|
||||
loadShieldStats();
|
||||
} catch (error) {
|
||||
console.error('Error adding rule:', error);
|
||||
showErrorMessage('添加规则失败');
|
||||
showNotification('添加规则失败', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,7 +424,7 @@ async function loadRemoteBlacklists() {
|
||||
updateBlacklistsTable(blacklistArray);
|
||||
} catch (error) {
|
||||
console.error('加载远程黑名单失败:', error);
|
||||
showErrorMessage('加载远程黑名单失败');
|
||||
showNotification('加载远程黑名单失败', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,30 +470,24 @@ function updateBlacklistsTable(blacklists) {
|
||||
// 名称单元格
|
||||
const tdName = document.createElement('td');
|
||||
tdName.className = 'py-3 px-4';
|
||||
tdName.textContent = blacklist.Name || '未命名';
|
||||
tdName.textContent = blacklist.name || '未命名';
|
||||
|
||||
// URL单元格
|
||||
const tdUrl = document.createElement('td');
|
||||
tdUrl.className = 'py-3 px-4 truncate max-w-xs';
|
||||
tdUrl.textContent = blacklist.URL;
|
||||
tdUrl.textContent = blacklist.url;
|
||||
|
||||
// 状态单元格
|
||||
const tdStatus = document.createElement('td');
|
||||
tdStatus.className = 'py-3 px-4 text-center';
|
||||
|
||||
// 判断状态颜色:绿色(正常)、黄色(过期)、灰色(禁用)
|
||||
// 判断状态颜色:绿色(启用)、灰色(禁用)
|
||||
let statusColor = 'bg-gray-300'; // 默认禁用
|
||||
let statusText = '禁用';
|
||||
|
||||
if (blacklist.Enabled) {
|
||||
const expired = isBlacklistExpired(blacklist.lastUpdateTime || blacklist.LastUpdateTime);
|
||||
if (expired) {
|
||||
statusColor = 'bg-warning'; // 黄色表示过期
|
||||
statusText = '过期';
|
||||
} else {
|
||||
statusColor = 'bg-success'; // 绿色表示正常
|
||||
statusText = '正常';
|
||||
}
|
||||
if (blacklist.enabled) {
|
||||
statusColor = 'bg-success'; // 绿色表示启用
|
||||
statusText = '启用';
|
||||
}
|
||||
|
||||
const statusContainer = document.createElement('div');
|
||||
@@ -406,14 +505,29 @@ function updateBlacklistsTable(blacklists) {
|
||||
statusContainer.appendChild(statusTextSpan);
|
||||
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');
|
||||
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');
|
||||
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.title = '刷新黑名单';
|
||||
refreshBtn.addEventListener('click', handleUpdateBlacklist);
|
||||
@@ -421,17 +535,19 @@ function updateBlacklistsTable(blacklists) {
|
||||
// 删除按钮
|
||||
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.dataset.url = blacklist.url;
|
||||
deleteBtn.innerHTML = '<i class="fa fa-trash"></i>';
|
||||
deleteBtn.title = '删除黑名单';
|
||||
deleteBtn.addEventListener('click', handleDeleteBlacklist);
|
||||
|
||||
tdActions.appendChild(toggleBtn);
|
||||
tdActions.appendChild(refreshBtn);
|
||||
tdActions.appendChild(deleteBtn);
|
||||
|
||||
tr.appendChild(tdName);
|
||||
tr.appendChild(tdUrl);
|
||||
tr.appendChild(tdStatus);
|
||||
tr.appendChild(tdUpdateStatus);
|
||||
tr.appendChild(tdActions);
|
||||
fragment.appendChild(tr);
|
||||
});
|
||||
@@ -449,43 +565,93 @@ function updateBlacklistsTable(blacklists) {
|
||||
|
||||
// 处理更新单个黑名单
|
||||
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) {
|
||||
showToast('无效的黑名单URL', 'error');
|
||||
showNotification('无效的黑名单URL', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/shield/blacklists/${encodeURIComponent(url)}/update`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
// 显示加载状态
|
||||
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: 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(() => ({}));
|
||||
throw new Error(errorData.error || 'Failed to update blacklist');
|
||||
// 发送更新请求
|
||||
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');
|
||||
|
||||
// 重新加载黑名单
|
||||
loadRemoteBlacklists();
|
||||
// 重新加载统计信息
|
||||
loadShieldStats();
|
||||
showToast('黑名单更新成功');
|
||||
showNotification('黑名单更新成功', 'success');
|
||||
} catch (error) {
|
||||
console.error('更新黑名单失败:', error);
|
||||
showToast('更新黑名单失败: ' + error.message, 'error');
|
||||
// 显示错误状态
|
||||
updateStatus(url, 'error', error.message);
|
||||
showNotification('更新黑名单失败: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 处理删除黑名单
|
||||
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) {
|
||||
showToast('无效的黑名单URL', 'error');
|
||||
showNotification('无效的黑名单URL', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -495,26 +661,124 @@ async function handleDeleteBlacklist(e) {
|
||||
}
|
||||
|
||||
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: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
},
|
||||
body: JSON.stringify(updatedBlacklists)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(errorData.error || 'Failed to delete blacklist');
|
||||
if (!updateResponse.ok) {
|
||||
const errorText = await updateResponse.text();
|
||||
throw new Error(`删除失败: ${updateResponse.status} ${errorText}`);
|
||||
}
|
||||
|
||||
// 显示成功状态
|
||||
updateStatus(url, 'success', '已删除');
|
||||
|
||||
// 重新加载黑名单
|
||||
loadRemoteBlacklists();
|
||||
// 重新加载统计信息
|
||||
loadShieldStats();
|
||||
showToast('黑名单删除成功');
|
||||
showNotification('黑名单删除成功', 'success');
|
||||
} catch (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 urlInput = document.getElementById('blacklist-url');
|
||||
const statusElement = document.getElementById('save-blacklist-status');
|
||||
|
||||
const name = nameInput ? nameInput.value.trim() : '';
|
||||
const url = urlInput ? urlInput.value.trim() : '';
|
||||
|
||||
// 简单验证
|
||||
if (!name || !url) {
|
||||
showErrorMessage('名称和URL不能为空');
|
||||
showNotification('名称和URL不能为空', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -541,11 +806,15 @@ async function handleAddBlacklist(event) {
|
||||
try {
|
||||
new URL(url);
|
||||
} catch (e) {
|
||||
showErrorMessage('URL格式不正确');
|
||||
showNotification('URL格式不正确', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 显示加载状态
|
||||
statusElement.innerHTML = '<i class="fa fa-spinner fa-spin text-blue-500"></i> 正在添加...';
|
||||
|
||||
// 发送添加请求
|
||||
const response = await fetch('/api/shield/blacklists', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -568,7 +837,10 @@ async function handleAddBlacklist(event) {
|
||||
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 (urlInput) urlInput.value = '';
|
||||
@@ -578,7 +850,14 @@ async function handleAddBlacklist(event) {
|
||||
loadShieldStats();
|
||||
} catch (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