Compare commits
3 Commits
beta2
...
Qimeng-DNS
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
356310ae75 | ||
|
|
fe77539da1 | ||
|
|
1f8bd6b97f |
46
CHANGELOG.md
46
CHANGELOG.md
@@ -2,6 +2,52 @@
|
|||||||
|
|
||||||
所有对本项目的显著更改都将记录在此文件中。
|
所有对本项目的显著更改都将记录在此文件中。
|
||||||
|
|
||||||
|
## [1.2.0] - 2025-12-24
|
||||||
|
|
||||||
|
### 添加
|
||||||
|
- 在查询日志详情的域名左侧添加DNSSEC状态锁图标和跟踪器状态图标
|
||||||
|
- 实现跟踪器状态显示(匹配tracker/trackers.json数据库)
|
||||||
|
- 添加跟踪器详情浮窗(鼠标悬停在眼睛图标上时显示跟踪器名称、类别、URL、来源等信息)
|
||||||
|
- 实现日志页面页码跳转功能(输入框+"前往"按钮)
|
||||||
|
- 实现日志页面显示数量选择功能(下拉框)
|
||||||
|
|
||||||
|
### 修改
|
||||||
|
- 异步加载跟踪器数据库并缓存,优化性能
|
||||||
|
- 将日志渲染逻辑改为支持异步操作的for...of循环
|
||||||
|
- 修复跟踪器浮窗CSS样式语法错误
|
||||||
|
- 在后端添加/tracker目录静态文件服务路由
|
||||||
|
|
||||||
|
## [1.1.4] - 2025-12-21
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
- 修复规则优先级问题:确保自定义规则优先于远程规则
|
||||||
|
- 修复添加自定义规则后需要重启服务器的问题:通过在添加或删除规则后清空DNS缓存实现
|
||||||
|
|
||||||
|
## [1.1.3] - 2025-12-19
|
||||||
|
|
||||||
|
### 移除
|
||||||
|
- 移除search domain功能,不再支持自动添加域名前缀进行查询
|
||||||
|
- 移除DNSConfig结构体中的PrefixDomain字段
|
||||||
|
- 移除配置文件中的prefixDomain配置项
|
||||||
|
|
||||||
|
## [1.1.2] - 2025-12-19
|
||||||
|
|
||||||
|
### 添加
|
||||||
|
- 添加不验证DNSSEC的域名功能,支持通过配置文件指定需要跳过DNSSEC验证的域名模式
|
||||||
|
- 在DNSConfig结构体中增加NoDNSSECDomains字段,用于存储不验证DNSSEC的域名模式列表
|
||||||
|
|
||||||
|
### 修改
|
||||||
|
- 在forwardDNSRequestWithCache函数中添加域名匹配逻辑,检查域名是否包含不验证DNSSEC的模式
|
||||||
|
- 在所有查询模式(parallel、loadbalance、fastest-ip、default)中实现跳过DNSSEC验证的功能
|
||||||
|
|
||||||
|
## [1.1.1] - 2025-12-19
|
||||||
|
|
||||||
|
### 修改
|
||||||
|
- 修复NXDOMAIN响应传播逻辑,确保上游DNS服务器返回的NXDOMAIN响应能正确传递给客户端
|
||||||
|
- 优化loadbalance、fastest-ip和parallel查询模式下的NXDOMAIN响应选择机制
|
||||||
|
- 确保不存在的域名能被正确识别并返回NXDOMAIN状态码
|
||||||
|
- 修复服务器绑定地址配置,确保IPv4兼容性
|
||||||
|
|
||||||
## [1.0.0] - 2025-12-16
|
## [1.0.0] - 2025-12-16
|
||||||
|
|
||||||
### 添加
|
### 添加
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
- 支持域名规则和正则表达式规则
|
- 支持域名规则和正则表达式规则
|
||||||
- 支持规则例外
|
- 支持规则例外
|
||||||
- 支持远程规则列表
|
- 支持远程规则列表
|
||||||
- 支持本地规则管理
|
- 支持自定义规则管理
|
||||||
|
|
||||||
### 3. 查询日志记录和统计
|
### 3. 查询日志记录和统计
|
||||||
- 实时记录DNS查询日志
|
- 实时记录DNS查询日志
|
||||||
@@ -116,7 +116,7 @@ http://localhost:8080
|
|||||||
|
|
||||||
1. 登录Web控制台
|
1. 登录Web控制台
|
||||||
2. 点击左侧菜单中的"屏蔽管理"
|
2. 点击左侧菜单中的"屏蔽管理"
|
||||||
3. 在"本地规则管理"中添加或删除规则
|
3. 在"自定义规则管理"中添加或删除规则
|
||||||
4. 在"远程黑名单管理"中添加或删除远程规则列表
|
4. 在"远程黑名单管理"中添加或删除远程规则列表
|
||||||
|
|
||||||
### 查看查询日志
|
### 查看查询日志
|
||||||
|
|||||||
5
build.sh
Executable file
5
build.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
CGO_ENABLED=1 \
|
||||||
|
GOOS=linux \
|
||||||
|
GOARCH=amd64 \
|
||||||
|
CC=gcc \
|
||||||
|
go build -ldflags "-linkmode external -extldflags '-static -pthread'" -o dns-server main.go
|
||||||
54
config.json
54
config.json
@@ -2,18 +2,50 @@
|
|||||||
"dns": {
|
"dns": {
|
||||||
"port": 53,
|
"port": 53,
|
||||||
"upstreamDNS": [
|
"upstreamDNS": [
|
||||||
"223.5.5.5:53",
|
"223.5.5.5:53"
|
||||||
"223.6.6.6:53",
|
|
||||||
"117.50.10.10:53"
|
|
||||||
],
|
],
|
||||||
"dnssecUpstreamDNS": [
|
"dnssecUpstreamDNS": [
|
||||||
"1.1.1.1:53"
|
"117.50.10.10:53",
|
||||||
|
"101.226.4.6:53",
|
||||||
|
"218.30.118.6:53",
|
||||||
|
"208.67.220.220:53",
|
||||||
|
"208.67.222.222:53"
|
||||||
],
|
],
|
||||||
"timeout": 5000,
|
"timeout": 5000,
|
||||||
"statsFile": "data/stats.json",
|
"statsFile": "data/stats.json",
|
||||||
"saveInterval": 300,
|
"saveInterval": 300,
|
||||||
"cacheTTL": 30,
|
"cacheTTL": 10,
|
||||||
"enableDNSSEC": true
|
"enableDNSSEC": true,
|
||||||
|
"queryMode": "loadbalance",
|
||||||
|
"domainSpecificDNS": {
|
||||||
|
"addr.arpa": [
|
||||||
|
"10.35.10.200:53"
|
||||||
|
],
|
||||||
|
"akadns": [
|
||||||
|
"4.2.2.1:53"
|
||||||
|
],
|
||||||
|
"akamai": [
|
||||||
|
"4.2.2.1:53"
|
||||||
|
],
|
||||||
|
"amazehome.cn": [
|
||||||
|
"10.35.10.200:53"
|
||||||
|
],
|
||||||
|
"amazehome.xyz": [
|
||||||
|
"10.35.10.200:53"
|
||||||
|
],
|
||||||
|
"microsoft.com": [
|
||||||
|
"4.2.2.1:53"
|
||||||
|
],
|
||||||
|
"steam": [
|
||||||
|
"4.2.2.1:53"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"noDNSSECDomains": [
|
||||||
|
"amazehome.cn",
|
||||||
|
"addr.arpa",
|
||||||
|
"amazehome.xyz",
|
||||||
|
".cn"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"http": {
|
"http": {
|
||||||
"port": 8080,
|
"port": 8080,
|
||||||
@@ -29,7 +61,7 @@
|
|||||||
"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-28T16:13:03.564Z"
|
"lastUpdateTime": "2025-12-21T10:46:36.629Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Adaway Default Blocklist",
|
"name": "Adaway Default Blocklist",
|
||||||
@@ -62,13 +94,14 @@
|
|||||||
{
|
{
|
||||||
"name": "Hate \u0026 Junk",
|
"name": "Hate \u0026 Junk",
|
||||||
"url": "http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/hate-and-junk-extended.txt",
|
"url": "http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/hate-and-junk-extended.txt",
|
||||||
"enabled": true
|
"enabled": true,
|
||||||
|
"lastUpdateTime": "2025-12-21T10:46:43.522Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "My Gitlab Hosts",
|
"name": "My Gitlab Hosts",
|
||||||
"url": "http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/hosts/costomize.txt",
|
"url": "http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/hosts/costomize.txt",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"lastUpdateTime": "2025-11-29T17:11:28.130Z"
|
"lastUpdateTime": "2025-12-18T10:39:39.333Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Anti Remote Requests",
|
"name": "Anti Remote Requests",
|
||||||
@@ -83,7 +116,8 @@
|
|||||||
{
|
{
|
||||||
"name": "My Gitlab A/T Rules",
|
"name": "My Gitlab A/T Rules",
|
||||||
"url": "http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/rules/ads-and-trackers.txt",
|
"url": "http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/rules/ads-and-trackers.txt",
|
||||||
"enabled": true
|
"enabled": true,
|
||||||
|
"lastUpdateTime": "2025-12-18T10:38:42.344Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "My Gitlab Malware List",
|
"name": "My Gitlab Malware List",
|
||||||
|
|||||||
@@ -5,16 +5,26 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DomainSpecificDNS 域名特定DNS服务器配置
|
||||||
|
// 格式:{"domainMatch": ["dns1", "dns2"]}
|
||||||
|
// domainMatch: 域名匹配字符串,当域名中包含该字符串时使用对应的DNS服务器
|
||||||
|
// dns1, dns2: 用于解析匹配域名的DNS服务器列表
|
||||||
|
|
||||||
|
type DomainSpecificDNS map[string][]string
|
||||||
|
|
||||||
// DNSConfig DNS配置
|
// DNSConfig DNS配置
|
||||||
type DNSConfig struct {
|
type DNSConfig struct {
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
UpstreamDNS []string `json:"upstreamDNS"`
|
UpstreamDNS []string `json:"upstreamDNS"`
|
||||||
DNSSECUpstreamDNS []string `json:"dnssecUpstreamDNS"` // 用于DNSSEC查询的专用服务器
|
DNSSECUpstreamDNS []string `json:"dnssecUpstreamDNS"` // 用于DNSSEC查询的专用服务器
|
||||||
Timeout int `json:"timeout"`
|
Timeout int `json:"timeout"`
|
||||||
StatsFile string `json:"statsFile"` // 统计数据持久化文件
|
StatsFile string `json:"statsFile"` // 统计数据持久化文件
|
||||||
SaveInterval int `json:"saveInterval"` // 数据保存间隔(秒)
|
SaveInterval int `json:"saveInterval"` // 数据保存间隔(秒)
|
||||||
CacheTTL int `json:"cacheTTL"` // DNS缓存过期时间(分钟)
|
CacheTTL int `json:"cacheTTL"` // DNS缓存过期时间(分钟)
|
||||||
EnableDNSSEC bool `json:"enableDNSSEC"` // 是否启用DNSSEC支持
|
EnableDNSSEC bool `json:"enableDNSSEC"` // 是否启用DNSSEC支持
|
||||||
|
QueryMode string `json:"queryMode"` // 查询模式:"loadbalance"(负载均衡)、"parallel"(并行请求)、"fastest-ip"(最快的IP地址)
|
||||||
|
DomainSpecificDNS DomainSpecificDNS `json:"domainSpecificDNS"` // 域名特定DNS服务器配置
|
||||||
|
NoDNSSECDomains []string `json:"noDNSSECDomains"` // 不验证DNSSEC的域名模式列表
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPConfig HTTP控制台配置
|
// HTTPConfig HTTP控制台配置
|
||||||
@@ -82,6 +92,9 @@ func LoadConfig(path string) (*Config, error) {
|
|||||||
if config.DNS.Port == 0 {
|
if config.DNS.Port == 0 {
|
||||||
config.DNS.Port = 53
|
config.DNS.Port = 53
|
||||||
}
|
}
|
||||||
|
if config.DNS.Timeout == 0 {
|
||||||
|
config.DNS.Timeout = 5000 // 默认超时时间为5000毫秒
|
||||||
|
}
|
||||||
if len(config.DNS.UpstreamDNS) == 0 {
|
if len(config.DNS.UpstreamDNS) == 0 {
|
||||||
config.DNS.UpstreamDNS = []string{"223.5.5.5:53", "223.6.6.6:53"}
|
config.DNS.UpstreamDNS = []string{"223.5.5.5:53", "223.6.6.6:53"}
|
||||||
}
|
}
|
||||||
@@ -101,6 +114,15 @@ func LoadConfig(path string) (*Config, error) {
|
|||||||
if len(config.DNS.DNSSECUpstreamDNS) == 0 {
|
if len(config.DNS.DNSSECUpstreamDNS) == 0 {
|
||||||
config.DNS.DNSSECUpstreamDNS = []string{"8.8.8.8:53", "1.1.1.1:53"}
|
config.DNS.DNSSECUpstreamDNS = []string{"8.8.8.8:53", "1.1.1.1:53"}
|
||||||
}
|
}
|
||||||
|
// 查询模式默认配置
|
||||||
|
if config.DNS.QueryMode == "" {
|
||||||
|
config.DNS.QueryMode = "parallel" // 默认使用并行请求模式
|
||||||
|
}
|
||||||
|
// 域名特定DNS服务器配置默认值
|
||||||
|
if config.DNS.DomainSpecificDNS == nil {
|
||||||
|
config.DNS.DomainSpecificDNS = make(DomainSpecificDNS) // 默认为空映射
|
||||||
|
}
|
||||||
|
|
||||||
if config.HTTP.Port == 0 {
|
if config.HTTP.Port == 0 {
|
||||||
config.HTTP.Port = 8080
|
config.HTTP.Port = 8080
|
||||||
}
|
}
|
||||||
|
|||||||
1053
dns/server.go
1053
dns/server.go
File diff suppressed because it is too large
Load Diff
@@ -154,6 +154,17 @@ func (s *Server) Start() error {
|
|||||||
http.ServeFile(w, r, "./static/login.html")
|
http.ServeFile(w, r, "./static/login.html")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Tracker目录静态文件服务
|
||||||
|
trackerFileServer := http.FileServer(http.Dir("./tracker"))
|
||||||
|
mux.HandleFunc("/tracker/", s.loginRequired(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// 添加Cache-Control头,禁用浏览器缓存
|
||||||
|
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
w.Header().Set("Pragma", "no-cache")
|
||||||
|
w.Header().Set("Expires", "Thu, 01 Jan 1970 00:00:00 GMT")
|
||||||
|
// 使用StripPrefix处理路径
|
||||||
|
http.StripPrefix("/tracker", trackerFileServer).ServeHTTP(w, r)
|
||||||
|
}))
|
||||||
|
|
||||||
// 其他静态文件需要登录
|
// 其他静态文件需要登录
|
||||||
mux.HandleFunc("/", s.loginRequired(func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/", s.loginRequired(func(w http.ResponseWriter, r *http.Request) {
|
||||||
// 添加Cache-Control头,禁用浏览器缓存
|
// 添加Cache-Control头,禁用浏览器缓存
|
||||||
@@ -832,6 +843,9 @@ func (s *Server) handleShield(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清空DNS缓存,使新规则立即生效
|
||||||
|
s.dnsServer.DnsCache.Clear()
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
|
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
|
||||||
return
|
return
|
||||||
case http.MethodDelete:
|
case http.MethodDelete:
|
||||||
@@ -850,6 +864,9 @@ func (s *Server) handleShield(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清空DNS缓存,使规则变更立即生效
|
||||||
|
s.dnsServer.DnsCache.Clear()
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
|
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
|
||||||
return
|
return
|
||||||
case http.MethodPut:
|
case http.MethodPut:
|
||||||
|
|||||||
15
main.go
15
main.go
@@ -37,9 +37,16 @@ func createDefaultConfig(configFile string) error {
|
|||||||
"223.5.5.5:53",
|
"223.5.5.5:53",
|
||||||
"223.6.6.6:53"
|
"223.6.6.6:53"
|
||||||
],
|
],
|
||||||
|
"dnssecUpstreamDNS": [
|
||||||
|
"8.8.8.8:53",
|
||||||
|
"1.1.1.1:53"
|
||||||
|
],
|
||||||
"timeout": 5000,
|
"timeout": 5000,
|
||||||
"statsFile": "./data/stats.json",
|
"statsFile": "./data/stats.json",
|
||||||
"saveInterval": 300
|
"saveInterval": 300,
|
||||||
|
"cacheTTL": 30,
|
||||||
|
"enableDNSSEC": true,
|
||||||
|
"queryMode": "parallel"
|
||||||
},
|
},
|
||||||
"http": {
|
"http": {
|
||||||
"port": 8080,
|
"port": 8080,
|
||||||
@@ -114,10 +121,10 @@ func createRequiredFiles(cfg *config.Config) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建本地规则文件
|
// 创建自定义规则文件
|
||||||
if _, err := os.Stat(cfg.Shield.LocalRulesFile); os.IsNotExist(err) {
|
if _, err := os.Stat(cfg.Shield.LocalRulesFile); os.IsNotExist(err) {
|
||||||
if err := os.WriteFile(cfg.Shield.LocalRulesFile, []byte("# 本地规则文件\n# 格式:域名\n# 例如:example.com\n"), 0644); err != nil {
|
if err := os.WriteFile(cfg.Shield.LocalRulesFile, []byte("# 自定义规则文件\n# 格式:域名\n# 例如:example.com\n"), 0644); err != nil {
|
||||||
return fmt.Errorf("创建本地规则文件失败: %w", err)
|
return fmt.Errorf("创建自定义规则文件失败: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
8670
server.log
8670
server.log
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
|||||||
132708
|
|
||||||
@@ -30,7 +30,7 @@ type ShieldStatsData struct {
|
|||||||
type regexRule struct {
|
type regexRule struct {
|
||||||
pattern *regexp.Regexp
|
pattern *regexp.Regexp
|
||||||
original string
|
original string
|
||||||
isLocal bool // 是否为本地规则
|
isLocal bool // 是否为自定义规则
|
||||||
source string // 规则来源
|
source string // 规则来源
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,8 +39,8 @@ type ShieldManager struct {
|
|||||||
config *config.ShieldConfig
|
config *config.ShieldConfig
|
||||||
domainRules map[string]bool
|
domainRules map[string]bool
|
||||||
domainExceptions map[string]bool
|
domainExceptions map[string]bool
|
||||||
domainRulesIsLocal map[string]bool // 标记域名规则是否为本地规则
|
domainRulesIsLocal map[string]bool // 标记域名规则是否为自定义规则
|
||||||
domainExceptionsIsLocal map[string]bool // 标记域名排除规则是否为本地规则
|
domainExceptionsIsLocal map[string]bool // 标记域名排除规则是否为自定义规则
|
||||||
domainRulesSource map[string]string // 标记域名规则来源
|
domainRulesSource map[string]string // 标记域名规则来源
|
||||||
domainExceptionsSource map[string]string // 标记域名排除规则来源
|
domainExceptionsSource map[string]string // 标记域名排除规则来源
|
||||||
domainRulesOriginal map[string]string // 存储域名规则的原始字符串
|
domainRulesOriginal map[string]string // 存储域名规则的原始字符串
|
||||||
@@ -54,7 +54,7 @@ type ShieldManager struct {
|
|||||||
updateCtx context.Context
|
updateCtx context.Context
|
||||||
updateCancel context.CancelFunc
|
updateCancel context.CancelFunc
|
||||||
updateRunning bool
|
updateRunning bool
|
||||||
localRulesCount int // 本地规则数量
|
localRulesCount int // 自定义规则数量
|
||||||
remoteRulesCount int // 远程规则数量
|
remoteRulesCount int // 远程规则数量
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,9 +109,9 @@ func (m *ShieldManager) LoadRules() error {
|
|||||||
m.remoteRulesCount = 0
|
m.remoteRulesCount = 0
|
||||||
// 保留计数数据,不随规则重新加载而清空
|
// 保留计数数据,不随规则重新加载而清空
|
||||||
|
|
||||||
// 加载本地规则文件
|
// 加载自定义规则文件
|
||||||
if err := m.loadLocalRules(); err != nil {
|
if err := m.loadLocalRules(); err != nil {
|
||||||
logger.Error("加载本地规则失败", "error", err)
|
logger.Error("加载自定义规则失败", "error", err)
|
||||||
// 继续执行,不返回错误
|
// 继续执行,不返回错误
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ func (m *ShieldManager) LoadRules() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadLocalRules 加载本地规则文件
|
// loadLocalRules 加载自定义规则文件
|
||||||
func (m *ShieldManager) loadLocalRules() error {
|
func (m *ShieldManager) loadLocalRules() error {
|
||||||
if m.config.LocalRulesFile == "" {
|
if m.config.LocalRulesFile == "" {
|
||||||
return nil
|
return nil
|
||||||
@@ -144,7 +144,7 @@ func (m *ShieldManager) loadLocalRules() error {
|
|||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
// 记录加载前的规则数量,用于计算本地规则数量
|
// 记录加载前的规则数量,用于计算自定义规则数量
|
||||||
beforeDomainRules := len(m.domainRules)
|
beforeDomainRules := len(m.domainRules)
|
||||||
beforeRegexRules := len(m.regexRules)
|
beforeRegexRules := len(m.regexRules)
|
||||||
|
|
||||||
@@ -154,10 +154,10 @@ func (m *ShieldManager) loadLocalRules() error {
|
|||||||
if line == "" || strings.HasPrefix(line, "#") {
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
m.parseRule(line, true, "本地规则") // 本地规则,isLocal=true,来源为"本地规则"
|
m.parseRule(line, true, "自定义规则") // 自定义规则,isLocal=true,来源为"自定义规则"
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新本地规则计数
|
// 更新自定义规则计数
|
||||||
m.localRulesCount = (len(m.domainRules) - beforeDomainRules) + (len(m.regexRules) - beforeRegexRules)
|
m.localRulesCount = (len(m.domainRules) - beforeDomainRules) + (len(m.regexRules) - beforeRegexRules)
|
||||||
|
|
||||||
return scanner.Err()
|
return scanner.Err()
|
||||||
@@ -445,10 +445,16 @@ func (m *ShieldManager) parseRuleOptions(optionsStr string) map[string]string {
|
|||||||
// addDomainRule 添加域名规则,支持是否为阻止规则
|
// addDomainRule 添加域名规则,支持是否为阻止规则
|
||||||
func (m *ShieldManager) addDomainRule(domain string, block bool, isLocal bool, source string, original string) {
|
func (m *ShieldManager) addDomainRule(domain string, block bool, isLocal bool, source string, original string) {
|
||||||
if block {
|
if block {
|
||||||
// 如果是远程规则,检查是否已经存在本地规则,如果存在则不覆盖
|
// 如果是远程规则,检查是否已经存在自定义规则(阻止或排除),如果存在则不覆盖
|
||||||
if !isLocal {
|
if !isLocal {
|
||||||
|
// 检查是否存在自定义阻止规则
|
||||||
if _, exists := m.domainRulesIsLocal[domain]; exists && m.domainRulesIsLocal[domain] {
|
if _, exists := m.domainRulesIsLocal[domain]; exists && m.domainRulesIsLocal[domain] {
|
||||||
// 已经存在本地规则,不覆盖
|
// 已经存在自定义规则,不覆盖
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 检查是否存在自定义排除规则
|
||||||
|
if _, exists := m.domainExceptionsIsLocal[domain]; exists && m.domainExceptionsIsLocal[domain] {
|
||||||
|
// 已经存在自定义规则,不覆盖
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -458,10 +464,16 @@ func (m *ShieldManager) addDomainRule(domain string, block bool, isLocal bool, s
|
|||||||
m.domainRulesOriginal[domain] = original
|
m.domainRulesOriginal[domain] = original
|
||||||
} else {
|
} else {
|
||||||
// 添加到排除规则
|
// 添加到排除规则
|
||||||
// 如果是远程规则,检查是否已经存在本地规则,如果存在则不覆盖
|
// 如果是远程规则,检查是否已经存在自定义规则(阻止或排除),如果存在则不覆盖
|
||||||
if !isLocal {
|
if !isLocal {
|
||||||
|
// 检查是否存在自定义阻止规则
|
||||||
|
if _, exists := m.domainRulesIsLocal[domain]; exists && m.domainRulesIsLocal[domain] {
|
||||||
|
// 已经存在自定义规则,不覆盖
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 检查是否存在自定义排除规则
|
||||||
if _, exists := m.domainExceptionsIsLocal[domain]; exists && m.domainExceptionsIsLocal[domain] {
|
if _, exists := m.domainExceptionsIsLocal[domain]; exists && m.domainExceptionsIsLocal[domain] {
|
||||||
// 已经存在本地规则,不覆盖
|
// 已经存在自定义规则,不覆盖
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -481,11 +493,19 @@ func (m *ShieldManager) addRegexRule(re *regexp.Regexp, original string, block b
|
|||||||
source: source,
|
source: source,
|
||||||
}
|
}
|
||||||
if block {
|
if block {
|
||||||
// 如果是远程规则,检查是否已经存在相同的本地规则,如果存在则不添加
|
// 如果是远程规则,检查是否已经存在任何自定义规则,如果存在则不添加
|
||||||
if !isLocal {
|
if !isLocal {
|
||||||
|
// 检查是否存在自定义阻止规则
|
||||||
for _, existingRule := range m.regexRules {
|
for _, existingRule := range m.regexRules {
|
||||||
if existingRule.original == original && existingRule.isLocal {
|
if existingRule.pattern.String() == re.String() && existingRule.isLocal {
|
||||||
// 已经存在相同的本地规则,不添加
|
// 已经存在相同的自定义阻止规则,不添加
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 检查是否存在自定义排除规则
|
||||||
|
for _, existingRule := range m.regexExceptions {
|
||||||
|
if existingRule.pattern.String() == re.String() && existingRule.isLocal {
|
||||||
|
// 已经存在相同的自定义排除规则,不添加
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -493,11 +513,19 @@ func (m *ShieldManager) addRegexRule(re *regexp.Regexp, original string, block b
|
|||||||
m.regexRules = append(m.regexRules, rule)
|
m.regexRules = append(m.regexRules, rule)
|
||||||
} else {
|
} else {
|
||||||
// 添加到排除规则
|
// 添加到排除规则
|
||||||
// 如果是远程规则,检查是否已经存在相同的本地规则,如果存在则不添加
|
// 如果是远程规则,检查是否已经存在任何自定义规则,如果存在则不添加
|
||||||
if !isLocal {
|
if !isLocal {
|
||||||
|
// 检查是否存在自定义阻止规则
|
||||||
|
for _, existingRule := range m.regexRules {
|
||||||
|
if existingRule.pattern.String() == re.String() && existingRule.isLocal {
|
||||||
|
// 已经存在相同的自定义阻止规则,不添加
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 检查是否存在自定义排除规则
|
||||||
for _, existingRule := range m.regexExceptions {
|
for _, existingRule := range m.regexExceptions {
|
||||||
if existingRule.original == original && existingRule.isLocal {
|
if existingRule.pattern.String() == re.String() && existingRule.isLocal {
|
||||||
// 已经存在相同的本地规则,不添加
|
// 已经存在相同的自定义排除规则,不添加
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -537,8 +565,17 @@ func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interf
|
|||||||
result["hostsIP"] = hostsIP
|
result["hostsIP"] = hostsIP
|
||||||
|
|
||||||
// 检查排除规则(优先级最高)
|
// 检查排除规则(优先级最高)
|
||||||
// 检查域名排除规则
|
// 1. 先检查本地域名排除规则
|
||||||
if m.domainExceptions[domain] {
|
if m.domainExceptions[domain] && m.domainExceptionsIsLocal[domain] {
|
||||||
|
result["excluded"] = true
|
||||||
|
result["excludeRule"] = m.domainExceptionsOriginal[domain]
|
||||||
|
result["excludeRuleType"] = "exact_domain"
|
||||||
|
result["blocksource"] = m.domainExceptionsSource[domain]
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 再检查远程域名排除规则
|
||||||
|
if m.domainExceptions[domain] && !m.domainExceptionsIsLocal[domain] {
|
||||||
result["excluded"] = true
|
result["excluded"] = true
|
||||||
result["excludeRule"] = m.domainExceptionsOriginal[domain]
|
result["excludeRule"] = m.domainExceptionsOriginal[domain]
|
||||||
result["excludeRuleType"] = "exact_domain"
|
result["excludeRuleType"] = "exact_domain"
|
||||||
@@ -548,9 +585,11 @@ func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interf
|
|||||||
|
|
||||||
// 检查子域名排除规则
|
// 检查子域名排除规则
|
||||||
parts := strings.Split(domain, ".")
|
parts := strings.Split(domain, ".")
|
||||||
|
|
||||||
|
// 3. 先检查本地子域名排除规则
|
||||||
for i := 0; i < len(parts)-1; i++ {
|
for i := 0; i < len(parts)-1; i++ {
|
||||||
subdomain := strings.Join(parts[i:], ".")
|
subdomain := strings.Join(parts[i:], ".")
|
||||||
if m.domainExceptions[subdomain] {
|
if m.domainExceptions[subdomain] && m.domainExceptionsIsLocal[subdomain] {
|
||||||
result["excluded"] = true
|
result["excluded"] = true
|
||||||
result["excludeRule"] = m.domainExceptionsOriginal[subdomain]
|
result["excludeRule"] = m.domainExceptionsOriginal[subdomain]
|
||||||
result["excludeRuleType"] = "subdomain"
|
result["excludeRuleType"] = "subdomain"
|
||||||
@@ -559,9 +598,32 @@ func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查正则表达式排除规则
|
// 4. 再检查远程子域名排除规则
|
||||||
|
for i := 0; i < len(parts)-1; i++ {
|
||||||
|
subdomain := strings.Join(parts[i:], ".")
|
||||||
|
if m.domainExceptions[subdomain] && !m.domainExceptionsIsLocal[subdomain] {
|
||||||
|
result["excluded"] = true
|
||||||
|
result["excludeRule"] = m.domainExceptionsOriginal[subdomain]
|
||||||
|
result["excludeRuleType"] = "subdomain"
|
||||||
|
result["blocksource"] = m.domainExceptionsSource[subdomain]
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 先检查本地正则表达式排除规则
|
||||||
for _, re := range m.regexExceptions {
|
for _, re := range m.regexExceptions {
|
||||||
if re.pattern.MatchString(domain) {
|
if re.isLocal && re.pattern.MatchString(domain) {
|
||||||
|
result["excluded"] = true
|
||||||
|
result["excludeRule"] = re.original
|
||||||
|
result["excludeRuleType"] = "regex"
|
||||||
|
result["blocksource"] = re.source
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 再检查远程正则表达式排除规则
|
||||||
|
for _, re := range m.regexExceptions {
|
||||||
|
if !re.isLocal && re.pattern.MatchString(domain) {
|
||||||
result["excluded"] = true
|
result["excluded"] = true
|
||||||
result["excludeRule"] = re.original
|
result["excludeRule"] = re.original
|
||||||
result["excludeRuleType"] = "regex"
|
result["excludeRuleType"] = "regex"
|
||||||
@@ -571,8 +633,17 @@ func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interf
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查阻止规则 - 先检查精确域名匹配,再检查子域名匹配
|
// 检查阻止规则 - 先检查精确域名匹配,再检查子域名匹配
|
||||||
// 检查精确域名匹配
|
// 7. 先检查本地域名阻止规则
|
||||||
if m.domainRules[domain] {
|
if m.domainRules[domain] && m.domainRulesIsLocal[domain] {
|
||||||
|
result["blocked"] = true
|
||||||
|
result["blockRule"] = m.domainRulesOriginal[domain]
|
||||||
|
result["blockRuleType"] = "exact_domain"
|
||||||
|
result["blocksource"] = m.domainRulesSource[domain]
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. 再检查远程域名阻止规则
|
||||||
|
if m.domainRules[domain] && !m.domainRulesIsLocal[domain] {
|
||||||
result["blocked"] = true
|
result["blocked"] = true
|
||||||
result["blockRule"] = m.domainRulesOriginal[domain]
|
result["blockRule"] = m.domainRulesOriginal[domain]
|
||||||
result["blockRuleType"] = "exact_domain"
|
result["blockRuleType"] = "exact_domain"
|
||||||
@@ -582,9 +653,11 @@ func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interf
|
|||||||
|
|
||||||
// 检查子域名匹配(AdGuardHome风格)
|
// 检查子域名匹配(AdGuardHome风格)
|
||||||
// 从最长的子域名开始匹配,确保优先级正确
|
// 从最长的子域名开始匹配,确保优先级正确
|
||||||
|
|
||||||
|
// 9. 先检查本地子域名阻止规则
|
||||||
for i := 0; i < len(parts)-1; i++ {
|
for i := 0; i < len(parts)-1; i++ {
|
||||||
subdomain := strings.Join(parts[i:], ".")
|
subdomain := strings.Join(parts[i:], ".")
|
||||||
if m.domainRules[subdomain] {
|
if m.domainRules[subdomain] && m.domainRulesIsLocal[subdomain] {
|
||||||
result["blocked"] = true
|
result["blocked"] = true
|
||||||
result["blockRule"] = m.domainRulesOriginal[subdomain]
|
result["blockRule"] = m.domainRulesOriginal[subdomain]
|
||||||
result["blockRuleType"] = "subdomain"
|
result["blockRuleType"] = "subdomain"
|
||||||
@@ -593,9 +666,32 @@ func (m *ShieldManager) CheckDomainBlockDetails(domain string) map[string]interf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查正则表达式匹配
|
// 10. 再检查远程子域名阻止规则
|
||||||
|
for i := 0; i < len(parts)-1; i++ {
|
||||||
|
subdomain := strings.Join(parts[i:], ".")
|
||||||
|
if m.domainRules[subdomain] && !m.domainRulesIsLocal[subdomain] {
|
||||||
|
result["blocked"] = true
|
||||||
|
result["blockRule"] = m.domainRulesOriginal[subdomain]
|
||||||
|
result["blockRuleType"] = "subdomain"
|
||||||
|
result["blocksource"] = m.domainRulesSource[subdomain]
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 11. 先检查本地正则表达式阻止规则
|
||||||
for _, re := range m.regexRules {
|
for _, re := range m.regexRules {
|
||||||
if re.pattern.MatchString(domain) {
|
if re.isLocal && re.pattern.MatchString(domain) {
|
||||||
|
result["blocked"] = true
|
||||||
|
result["blockRule"] = re.original
|
||||||
|
result["blockRuleType"] = "regex"
|
||||||
|
result["blocksource"] = re.source
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 12. 再检查远程正则表达式阻止规则
|
||||||
|
for _, re := range m.regexRules {
|
||||||
|
if !re.isLocal && re.pattern.MatchString(domain) {
|
||||||
result["blocked"] = true
|
result["blocked"] = true
|
||||||
result["blockRule"] = re.original
|
result["blockRule"] = re.original
|
||||||
result["blockRuleType"] = "regex"
|
result["blockRuleType"] = "regex"
|
||||||
@@ -722,13 +818,13 @@ func (m *ShieldManager) GetHostsIP(domain string) (string, bool) {
|
|||||||
return ip, exists
|
return ip, exists
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddRule 添加屏蔽规则,用户添加的规则是本地规则
|
// AddRule 添加屏蔽规则,用户添加的规则是自定义规则
|
||||||
func (m *ShieldManager) AddRule(rule string) error {
|
func (m *ShieldManager) AddRule(rule string) error {
|
||||||
m.rulesMutex.Lock()
|
m.rulesMutex.Lock()
|
||||||
defer m.rulesMutex.Unlock()
|
defer m.rulesMutex.Unlock()
|
||||||
|
|
||||||
// 解析并添加规则到内存,isLocal=true表示本地规则,来源为"本地规则"
|
// 解析并添加规则到内存,isLocal=true表示自定义规则,来源为"自定义规则"
|
||||||
m.parseRule(rule, true, "本地规则")
|
m.parseRule(rule, true, "自定义规则")
|
||||||
|
|
||||||
// 持久化保存规则到文件
|
// 持久化保存规则到文件
|
||||||
if m.config.LocalRulesFile != "" {
|
if m.config.LocalRulesFile != "" {
|
||||||
@@ -957,7 +1053,7 @@ func (m *ShieldManager) StopAutoUpdate() {
|
|||||||
logger.Info("规则自动更新已停止")
|
logger.Info("规则自动更新已停止")
|
||||||
}
|
}
|
||||||
|
|
||||||
// saveRulesToFile 保存规则到文件,只保存本地规则
|
// saveRulesToFile 保存规则到文件,只保存自定义规则
|
||||||
func (m *ShieldManager) saveRulesToFile() error {
|
func (m *ShieldManager) saveRulesToFile() error {
|
||||||
var rules []string
|
var rules []string
|
||||||
|
|
||||||
@@ -1293,12 +1389,12 @@ func (m *ShieldManager) GetHostsCount() int {
|
|||||||
return len(m.hostsMap)
|
return len(m.hostsMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLocalRules 获取仅本地规则
|
// GetLocalRules 获取仅自定义规则
|
||||||
func (m *ShieldManager) GetLocalRules() map[string]interface{} {
|
func (m *ShieldManager) GetLocalRules() map[string]interface{} {
|
||||||
m.rulesMutex.RLock()
|
m.rulesMutex.RLock()
|
||||||
defer m.rulesMutex.RUnlock()
|
defer m.rulesMutex.RUnlock()
|
||||||
|
|
||||||
// 转换map和slice为字符串列表,只包含本地规则
|
// 转换map和slice为字符串列表,只包含自定义规则
|
||||||
domainRulesList := make([]string, 0)
|
domainRulesList := make([]string, 0)
|
||||||
for domain, isLocal := range m.domainRulesIsLocal {
|
for domain, isLocal := range m.domainRulesIsLocal {
|
||||||
if isLocal && m.domainRules[domain] {
|
if isLocal && m.domainRules[domain] {
|
||||||
@@ -1329,7 +1425,7 @@ func (m *ShieldManager) GetLocalRules() map[string]interface{} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算本地规则数量
|
// 计算自定义规则数量
|
||||||
localDomainRulesCount := 0
|
localDomainRulesCount := 0
|
||||||
for _, isLocal := range m.domainRulesIsLocal {
|
for _, isLocal := range m.domainRulesIsLocal {
|
||||||
if isLocal {
|
if isLocal {
|
||||||
|
|||||||
@@ -1138,12 +1138,12 @@
|
|||||||
},
|
},
|
||||||
"/shield/localrules": {
|
"/shield/localrules": {
|
||||||
"get": {
|
"get": {
|
||||||
"summary": "获取本地规则",
|
"summary": "获取自定义规则",
|
||||||
"description": "获取Shield的本地规则列表。",
|
"description": "获取Shield的自定义规则列表。"
|
||||||
"tags": ["shield"],
|
"tags": ["shield"],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "成功获取本地规则",
|
"description": "成功获取自定义规则",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
@@ -1166,8 +1166,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"summary": "添加本地规则",
|
"summary": "添加自定义规则",
|
||||||
"description": "添加新的本地规则。",
|
"description": "添加新的自定义规则。",
|
||||||
"tags": ["shield"],
|
"tags": ["shield"],
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"required": true,
|
"required": true,
|
||||||
@@ -1205,8 +1205,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
"summary": "删除本地规则",
|
"summary": "删除自定义规则",
|
||||||
"description": "删除指定ID的本地规则。",
|
"description": "删除指定ID的自定义规则。",
|
||||||
"tags": ["shield"],
|
"tags": ["shield"],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -1500,7 +1500,7 @@
|
|||||||
"blocked": true,
|
"blocked": true,
|
||||||
"blockRule": "example.com",
|
"blockRule": "example.com",
|
||||||
"blockRuleType": "exact_domain",
|
"blockRuleType": "exact_domain",
|
||||||
"blocksource": "本地规则",
|
"blocksource": "自定义规则",
|
||||||
"excluded": false,
|
"excluded": false,
|
||||||
"excludeRule": "",
|
"excludeRule": "",
|
||||||
"excludeRuleType": "",
|
"excludeRuleType": "",
|
||||||
|
|||||||
@@ -1064,3 +1064,78 @@ tr:hover {
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 跟踪器状态图标容器 */
|
||||||
|
.tracker-icon-container {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 跟踪器浮窗样式 */
|
||||||
|
.tracker-tooltip {
|
||||||
|
position: absolute;
|
||||||
|
top: -10px;
|
||||||
|
left: 100%;
|
||||||
|
margin-left: 10px;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
padding: 12px;
|
||||||
|
min-width: 250px;
|
||||||
|
z-index: 50;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
/* 添加箭头 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 浮窗箭头 */
|
||||||
|
.tracker-tooltip::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
left: -10px;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-top: 10px solid transparent;
|
||||||
|
border-bottom: 10px solid transparent;
|
||||||
|
border-right: 10px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tracker-tooltip::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
left: -11px;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-top: 10px solid transparent;
|
||||||
|
border-bottom: 10px solid transparent;
|
||||||
|
border-right: 10px solid #e2e8f0;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 浮窗标题 */
|
||||||
|
.tracker-tooltip .font-semibold {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2d3748;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 浮窗内容项 */
|
||||||
|
.tracker-tooltip > div {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 浮窗链接样式 */
|
||||||
|
.tracker-tooltip a {
|
||||||
|
color: #3182ce;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tracker-tooltip a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
|
|
||||||
<!-- 底部信息 -->
|
<!-- 底部信息 -->
|
||||||
<div class="p-4 border-t border-gray-200 text-center text-gray-500 text-sm">
|
<div class="p-4 border-t border-gray-200 text-center text-gray-500 text-sm">
|
||||||
<p>DNS服务器 v1.0.0</p>
|
<p>DNS服务器 v1.2.0</p>
|
||||||
<p class="mt-1" id="uptime">正常运行中</p>
|
<p class="mt-1" id="uptime">正常运行中</p>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
@@ -630,9 +630,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 本地规则管理 -->
|
<!-- 自定义规则管理 -->
|
||||||
<div class="bg-white rounded-lg p-6 card-shadow">
|
<div class="bg-white rounded-lg p-6 card-shadow">
|
||||||
<h3 class="text-lg font-semibold mb-6">本地规则管理</h3>
|
<h3 class="text-lg font-semibold mb-6">自定义规则管理</h3>
|
||||||
|
|
||||||
<!-- 添加规则表单 -->
|
<!-- 添加规则表单 -->
|
||||||
<div id="add-rule-form" class="mb-6 bg-gray-50 p-4 rounded-lg">
|
<div id="add-rule-form" class="mb-6 bg-gray-50 p-4 rounded-lg">
|
||||||
@@ -976,11 +976,12 @@
|
|||||||
</th>
|
</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">响应时间</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">屏蔽规则</th>
|
||||||
|
<th class="text-center py-3 px-4 text-sm font-medium text-gray-500">操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="logs-table-body">
|
<tbody id="logs-table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" class="py-8 text-center text-gray-500 border-b border-gray-100">
|
<td colspan="6" class="py-8 text-center text-gray-500 border-b border-gray-100">
|
||||||
<i class="fa fa-file-text-o text-4xl mb-2 text-gray-300"></i>
|
<i class="fa fa-file-text-o text-4xl mb-2 text-gray-300"></i>
|
||||||
<div>暂无查询日志</div>
|
<div>暂无查询日志</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -994,13 +995,32 @@
|
|||||||
<div class="text-sm text-gray-500">
|
<div class="text-sm text-gray-500">
|
||||||
显示 <span id="logs-current-page">1</span> / <span id="logs-total-pages">1</span> 页
|
显示 <span id="logs-current-page">1</span> / <span id="logs-total-pages">1</span> 页
|
||||||
</div>
|
</div>
|
||||||
<div class="flex space-x-2">
|
<div class="flex items-center space-x-4">
|
||||||
<button id="logs-prev-page" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:border-transparent" disabled>
|
<div class="flex items-center space-x-2">
|
||||||
<i class="fa fa-chevron-left"></i>
|
<span class="text-sm text-gray-500">每页显示:</span>
|
||||||
</button>
|
<select id="logs-per-page" class="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
|
||||||
<button id="logs-next-page" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:border-transparent" disabled>
|
<option value="10">10条</option>
|
||||||
<i class="fa fa-chevron-right"></i>
|
<option value="20">20条</option>
|
||||||
</button>
|
<option value="30" selected>30条</option>
|
||||||
|
<option value="50">50条</option>
|
||||||
|
<option value="100">100条</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<span class="text-sm text-gray-500">页码:</span>
|
||||||
|
<input type="number" id="logs-page-input" min="1" max="1" value="1" class="w-16 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-center">
|
||||||
|
<button id="logs-go-page" class="px-4 py-2 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors">
|
||||||
|
前往
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-2">
|
||||||
|
<button id="logs-prev-page" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:border-transparent" disabled>
|
||||||
|
<i class="fa fa-chevron-left"></i>
|
||||||
|
</button>
|
||||||
|
<button id="logs-next-page" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:border-transparent" disabled>
|
||||||
|
<i class="fa fa-chevron-right"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1056,7 +1076,7 @@
|
|||||||
<h4 class="text-md font-medium mb-4">屏蔽配置</h4>
|
<h4 class="text-md font-medium mb-4">屏蔽配置</h4>
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 gap-6">
|
||||||
<div>
|
<div>
|
||||||
<label for="shield-local-rules-file" class="block text-sm font-medium text-gray-700 mb-1">本地规则文件</label>
|
<label for="shield-local-rules-file" class="block text-sm font-medium text-gray-700 mb-1">自定义规则文件</label>
|
||||||
<input type="text" id="shield-local-rules-file" 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" placeholder="./rules.txt">
|
<input type="text" id="shield-local-rules-file" 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" placeholder="./rules.txt">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -14,10 +14,83 @@ let currentSortDirection = 'desc'; // 默认降序
|
|||||||
let ipGeolocationCache = {};
|
let ipGeolocationCache = {};
|
||||||
const GEOLOCATION_CACHE_EXPIRY = 24 * 60 * 60 * 1000; // 缓存有效期24小时
|
const GEOLOCATION_CACHE_EXPIRY = 24 * 60 * 60 * 1000; // 缓存有效期24小时
|
||||||
|
|
||||||
|
// 跟踪器数据库缓存
|
||||||
|
let trackersDatabase = null;
|
||||||
|
let trackersLoaded = false;
|
||||||
|
let trackersLoading = false;
|
||||||
|
|
||||||
// WebSocket连接和重连计时器
|
// WebSocket连接和重连计时器
|
||||||
let logsWsConnection = null;
|
let logsWsConnection = null;
|
||||||
let logsWsReconnectTimer = null;
|
let logsWsReconnectTimer = null;
|
||||||
|
|
||||||
|
// 加载跟踪器数据库
|
||||||
|
async function loadTrackersDatabase() {
|
||||||
|
if (trackersLoaded) return trackersDatabase;
|
||||||
|
if (trackersLoading) {
|
||||||
|
// 等待正在进行的加载完成
|
||||||
|
while (trackersLoading) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
}
|
||||||
|
return trackersDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
|
trackersLoading = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/tracker/trackers.json');
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error('加载跟踪器数据库失败:', response.statusText);
|
||||||
|
trackersDatabase = { trackers: {} };
|
||||||
|
return trackersDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
|
trackersDatabase = await response.json();
|
||||||
|
trackersLoaded = true;
|
||||||
|
return trackersDatabase;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载跟踪器数据库失败:', error);
|
||||||
|
trackersDatabase = { trackers: {} };
|
||||||
|
return trackersDatabase;
|
||||||
|
} finally {
|
||||||
|
trackersLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查域名是否在跟踪器数据库中,并返回跟踪器信息
|
||||||
|
async function isDomainInTrackerDatabase(domain) {
|
||||||
|
if (!trackersDatabase || !trackersLoaded) {
|
||||||
|
await loadTrackersDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!trackersDatabase || !trackersDatabase.trackers) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查域名是否直接作为跟踪器键存在
|
||||||
|
if (trackersDatabase.trackers.hasOwnProperty(domain)) {
|
||||||
|
return trackersDatabase.trackers[domain];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查域名是否在跟踪器URL中
|
||||||
|
for (const trackerKey in trackersDatabase.trackers) {
|
||||||
|
if (trackersDatabase.trackers.hasOwnProperty(trackerKey)) {
|
||||||
|
const tracker = trackersDatabase.trackers[trackerKey];
|
||||||
|
if (tracker && tracker.url) {
|
||||||
|
try {
|
||||||
|
const trackerUrl = new URL(tracker.url);
|
||||||
|
if (trackerUrl.hostname === domain || trackerUrl.hostname.includes(domain)) {
|
||||||
|
return tracker;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 忽略无效URL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化查询日志页面
|
// 初始化查询日志页面
|
||||||
function initLogsPage() {
|
function initLogsPage() {
|
||||||
console.log('初始化查询日志页面');
|
console.log('初始化查询日志页面');
|
||||||
@@ -122,6 +195,34 @@ function bindLogsEvents() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 页码跳转
|
||||||
|
const pageInput = document.getElementById('logs-page-input');
|
||||||
|
const goBtn = document.getElementById('logs-go-page');
|
||||||
|
|
||||||
|
if (pageInput) {
|
||||||
|
// 页码输入框回车事件
|
||||||
|
pageInput.addEventListener('keypress', (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
const page = parseInt(pageInput.value);
|
||||||
|
if (page >= 1 && page <= totalPages) {
|
||||||
|
currentPage = page;
|
||||||
|
loadLogs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (goBtn) {
|
||||||
|
// 前往按钮点击事件
|
||||||
|
goBtn.addEventListener('click', () => {
|
||||||
|
const page = parseInt(pageInput.value);
|
||||||
|
if (page >= 1 && page <= totalPages) {
|
||||||
|
currentPage = page;
|
||||||
|
loadLogs();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 时间范围切换
|
// 时间范围切换
|
||||||
const timeRangeBtns = document.querySelectorAll('.time-range-btn');
|
const timeRangeBtns = document.querySelectorAll('.time-range-btn');
|
||||||
timeRangeBtns.forEach(btn => {
|
timeRangeBtns.forEach(btn => {
|
||||||
@@ -282,6 +383,9 @@ function loadLogs() {
|
|||||||
// 更新日志表格
|
// 更新日志表格
|
||||||
updateLogsTable(logs);
|
updateLogsTable(logs);
|
||||||
|
|
||||||
|
// 绑定操作按钮事件
|
||||||
|
bindActionButtonsEvents();
|
||||||
|
|
||||||
// 更新分页信息
|
// 更新分页信息
|
||||||
updateLogsPagination();
|
updateLogsPagination();
|
||||||
|
|
||||||
@@ -301,7 +405,7 @@ function loadLogs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 更新日志表格
|
// 更新日志表格
|
||||||
function updateLogsTable(logs) {
|
async function updateLogsTable(logs) {
|
||||||
const tableBody = document.getElementById('logs-table-body');
|
const tableBody = document.getElementById('logs-table-body');
|
||||||
if (!tableBody) return;
|
if (!tableBody) return;
|
||||||
|
|
||||||
@@ -322,7 +426,7 @@ function updateLogsTable(logs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 填充表格
|
// 填充表格
|
||||||
logs.forEach(log => {
|
for (const log of logs) {
|
||||||
const row = document.createElement('tr');
|
const row = document.createElement('tr');
|
||||||
row.className = 'border-b border-gray-100 hover:bg-gray-50 transition-colors';
|
row.className = 'border-b border-gray-100 hover:bg-gray-50 transition-colors';
|
||||||
|
|
||||||
@@ -383,30 +487,98 @@ function updateLogsTable(logs) {
|
|||||||
statusClass = '';
|
statusClass = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建行内容 - 两行显示,时间列显示时间和日期,请求列显示域名和类型状态
|
// 检查域名是否在跟踪器数据库中
|
||||||
// 添加缓存状态显示
|
const trackerInfo = await isDomainInTrackerDatabase(log.Domain);
|
||||||
const cacheStatusClass = log.FromCache ? 'text-primary' : 'text-gray-500';
|
const isTracker = trackerInfo !== null;
|
||||||
const cacheStatusText = log.FromCache ? '缓存' : '非缓存';
|
|
||||||
|
|
||||||
row.innerHTML = `
|
// 构建行内容 - 两行显示,时间列显示时间和日期,请求列显示域名和类型状态
|
||||||
<td class="py-3 px-4">
|
// 添加缓存状态显示
|
||||||
<div class="text-sm font-medium">${formattedTime}</div>
|
const cacheStatusClass = log.FromCache ? 'text-primary' : 'text-gray-500';
|
||||||
<div class="text-xs text-gray-500 mt-1">${formattedDate}</div>
|
const cacheStatusText = log.FromCache ? '缓存' : '非缓存';
|
||||||
</td>
|
|
||||||
<td class="py-3 px-4 text-sm">
|
// 检查域名是否被拦截
|
||||||
<div class="font-medium">${log.ClientIP}</div>
|
const isBlocked = log.Result === 'blocked';
|
||||||
<div class="text-xs text-gray-500 mt-1">${log.Location || '未知 未知'}</div>
|
|
||||||
</td>
|
// 构建跟踪器浮窗内容
|
||||||
<td class="py-3 px-4 text-sm">
|
const trackerTooltip = isTracker ? `
|
||||||
<div class="font-medium">${log.Domain}</div>
|
<div class="tracker-tooltip absolute z-50 bg-white shadow-lg rounded-md border p-3 min-w-64 text-sm">
|
||||||
<div class="text-xs text-gray-500 mt-1">类型: ${log.QueryType}, <span class="${statusClass}">${statusText}</span>, <span class="${cacheStatusClass}">${log.FromCache ? '缓存' : '实时'}</span>${log.DNSSEC ? ', <span class="text-green-500"><i class="fa fa-lock"></i> DNSSEC</span>' : ''}${log.EDNS ? ', <span class="text-blue-500"><i class="fa fa-exchange"></i> EDNS</span>' : ''}</div>
|
<div class="font-semibold mb-1">已知跟踪器</div>
|
||||||
</td>
|
<div class="mb-1">名称: ${trackerInfo.name}</div>
|
||||||
<td class="py-3 px-4 text-sm">${log.ResponseTime}ms</td>
|
<div class="mb-1">类别: ${trackersDatabase.categories[trackerInfo.categoryId] || '未知'}</div>
|
||||||
<td class="py-3 px-4 text-sm text-gray-500">${log.BlockRule || '-'}</td>
|
${trackerInfo.url ? `<div class="mb-1">URL: <a href="${trackerInfo.url}" target="_blank" class="text-blue-500 hover:underline">${trackerInfo.url}</a></div>` : ''}
|
||||||
`;
|
${trackerInfo.source ? `<div class="mb-1">源: ${trackerInfo.source}</div>` : ''}
|
||||||
|
</div>
|
||||||
|
` : '';
|
||||||
|
|
||||||
|
row.innerHTML = `
|
||||||
|
<td class="py-3 px-4">
|
||||||
|
<div class="text-sm font-medium">${formattedTime}</div>
|
||||||
|
<div class="text-xs text-gray-500 mt-1">${formattedDate}</div>
|
||||||
|
</td>
|
||||||
|
<td class="py-3 px-4 text-sm">
|
||||||
|
<div class="font-medium">${log.ClientIP}</div>
|
||||||
|
<div class="text-xs text-gray-500 mt-1">${log.Location || '未知 未知'}</div>
|
||||||
|
</td>
|
||||||
|
<td class="py-3 px-4 text-sm">
|
||||||
|
<div class="font-medium flex items-center relative">
|
||||||
|
${log.DNSSEC ? '<i class="fa fa-lock text-green-500 mr-1" title="DNSSEC已启用"></i>' : ''}
|
||||||
|
<div class="tracker-icon-container relative">
|
||||||
|
${isTracker ? '<i class="fa fa-eye text-red-500 mr-1"></i>' : '<i class="fa fa-eye-slash text-gray-300 mr-1"></i>'}
|
||||||
|
${trackerTooltip}
|
||||||
|
</div>
|
||||||
|
${log.Domain}
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-gray-500 mt-1">类型: ${log.QueryType}, <span class="${statusClass}">${statusText}</span>, <span class="${cacheStatusClass}">${log.FromCache ? '缓存' : '实时'}</span>${log.DNSSEC ? ', <span class="text-green-500"><i class="fa fa-lock"></i> DNSSEC</span>' : ''}${log.EDNS ? ', <span class="text-blue-500"><i class="fa fa-exchange"></i> EDNS</span>' : ''}</div>
|
||||||
|
<div class="text-xs text-gray-500 mt-1">DNS 服务器: ${log.DNSServer || '无'}, DNSSEC专用: ${log.DNSSECServer || '无'}</div>
|
||||||
|
</td>
|
||||||
|
<td class="py-3 px-4 text-sm">${log.ResponseTime}ms</td>
|
||||||
|
<td class="py-3 px-4 text-sm text-gray-500">${log.BlockRule || '-'}</td>
|
||||||
|
<td class="py-3 px-4 text-sm text-center">
|
||||||
|
${isBlocked ?
|
||||||
|
`<button class="unblock-btn px-3 py-1 bg-green-500 text-white rounded-md hover:bg-green-600 transition-colors text-xs" data-domain="${log.Domain}">放行</button>` :
|
||||||
|
`<button class="block-btn px-3 py-1 bg-red-500 text-white rounded-md hover:bg-red-600 transition-colors text-xs" data-domain="${log.Domain}">拦截</button>`
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 添加跟踪器图标悬停事件
|
||||||
|
if (isTracker) {
|
||||||
|
const iconContainer = row.querySelector('.tracker-icon-container');
|
||||||
|
const tooltip = iconContainer.querySelector('.tracker-tooltip');
|
||||||
|
if (iconContainer && tooltip) {
|
||||||
|
tooltip.style.display = 'none';
|
||||||
|
|
||||||
|
iconContainer.addEventListener('mouseenter', () => {
|
||||||
|
tooltip.style.display = 'block';
|
||||||
|
});
|
||||||
|
|
||||||
|
iconContainer.addEventListener('mouseleave', () => {
|
||||||
|
tooltip.style.display = 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定按钮事件
|
||||||
|
const blockBtn = row.querySelector('.block-btn');
|
||||||
|
if (blockBtn) {
|
||||||
|
blockBtn.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const domain = e.currentTarget.dataset.domain;
|
||||||
|
blockDomain(domain);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const unblockBtn = row.querySelector('.unblock-btn');
|
||||||
|
if (unblockBtn) {
|
||||||
|
unblockBtn.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const domain = e.currentTarget.dataset.domain;
|
||||||
|
unblockDomain(domain);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
tableBody.appendChild(row);
|
tableBody.appendChild(row);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新分页信息
|
// 更新分页信息
|
||||||
@@ -415,6 +587,13 @@ function updateLogsPagination() {
|
|||||||
document.getElementById('logs-current-page').textContent = currentPage;
|
document.getElementById('logs-current-page').textContent = currentPage;
|
||||||
document.getElementById('logs-total-pages').textContent = totalPages;
|
document.getElementById('logs-total-pages').textContent = totalPages;
|
||||||
|
|
||||||
|
// 更新页码输入框
|
||||||
|
const pageInput = document.getElementById('logs-page-input');
|
||||||
|
if (pageInput) {
|
||||||
|
pageInput.max = totalPages;
|
||||||
|
pageInput.value = currentPage;
|
||||||
|
}
|
||||||
|
|
||||||
// 更新按钮状态
|
// 更新按钮状态
|
||||||
const prevBtn = document.getElementById('logs-prev-page');
|
const prevBtn = document.getElementById('logs-prev-page');
|
||||||
const nextBtn = document.getElementById('logs-next-page');
|
const nextBtn = document.getElementById('logs-next-page');
|
||||||
@@ -613,6 +792,146 @@ function updateLogsStatsFromWebSocket(stats) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 拦截域名
|
||||||
|
async function blockDomain(domain) {
|
||||||
|
try {
|
||||||
|
console.log(`开始拦截域名: ${domain}`);
|
||||||
|
|
||||||
|
// 创建拦截规则,使用AdBlock Plus格式
|
||||||
|
const blockRule = `||${domain}^`;
|
||||||
|
console.log(`创建的拦截规则: ${blockRule}`);
|
||||||
|
|
||||||
|
// 调用API添加拦截规则
|
||||||
|
console.log(`调用API添加拦截规则,路径: /shield, 方法: POST`);
|
||||||
|
const response = await apiRequest('/shield', 'POST', { rule: blockRule });
|
||||||
|
|
||||||
|
console.log(`API响应:`, response);
|
||||||
|
|
||||||
|
// 处理不同的响应格式
|
||||||
|
if (response && (response.success || response.status === 'success')) {
|
||||||
|
// 重新加载日志,显示更新后的状态
|
||||||
|
loadLogs();
|
||||||
|
|
||||||
|
// 刷新规则列表
|
||||||
|
refreshRulesList();
|
||||||
|
|
||||||
|
// 显示成功通知
|
||||||
|
if (typeof window.showNotification === 'function') {
|
||||||
|
window.showNotification(`已成功拦截域名: ${domain}`, 'success');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const errorMsg = response ? (response.message || '添加拦截规则失败') : '添加拦截规则失败: 无效的API响应';
|
||||||
|
console.error(`拦截域名失败: ${errorMsg}`);
|
||||||
|
throw new Error(errorMsg);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('拦截域名失败:', error);
|
||||||
|
|
||||||
|
// 显示错误通知
|
||||||
|
if (typeof window.showNotification === 'function') {
|
||||||
|
window.showNotification(`拦截域名失败: ${error.message}`, 'danger');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定操作按钮事件
|
||||||
|
function bindActionButtonsEvents() {
|
||||||
|
// 绑定拦截按钮事件
|
||||||
|
const blockBtns = document.querySelectorAll('.block-btn');
|
||||||
|
blockBtns.forEach(btn => {
|
||||||
|
btn.addEventListener('click', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const domain = e.currentTarget.dataset.domain;
|
||||||
|
await blockDomain(domain);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 绑定放行按钮事件
|
||||||
|
const unblockBtns = document.querySelectorAll('.unblock-btn');
|
||||||
|
unblockBtns.forEach(btn => {
|
||||||
|
btn.addEventListener('click', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const domain = e.currentTarget.dataset.domain;
|
||||||
|
await unblockDomain(domain);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新规则列表
|
||||||
|
async function refreshRulesList() {
|
||||||
|
try {
|
||||||
|
// 调用API重新加载规则
|
||||||
|
const response = await apiRequest('/shield', 'GET');
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
// 处理规则列表响应
|
||||||
|
let allRules = [];
|
||||||
|
if (response && typeof response === 'object') {
|
||||||
|
// 合并所有类型的规则到一个数组
|
||||||
|
if (Array.isArray(response.domainRules)) allRules = allRules.concat(response.domainRules);
|
||||||
|
if (Array.isArray(response.domainExceptions)) allRules = allRules.concat(response.domainExceptions);
|
||||||
|
if (Array.isArray(response.regexRules)) allRules = allRules.concat(response.regexRules);
|
||||||
|
if (Array.isArray(response.regexExceptions)) allRules = allRules.concat(response.regexExceptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新规则列表
|
||||||
|
if (window.rules) {
|
||||||
|
rules = allRules;
|
||||||
|
filteredRules = [...rules];
|
||||||
|
|
||||||
|
// 更新规则数量统计
|
||||||
|
if (window.updateRulesCount && typeof window.updateRulesCount === 'function') {
|
||||||
|
window.updateRulesCount(rules.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('刷新规则列表失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 放行域名
|
||||||
|
async function unblockDomain(domain) {
|
||||||
|
try {
|
||||||
|
console.log(`开始放行域名: ${domain}`);
|
||||||
|
|
||||||
|
// 创建放行规则,使用AdBlock Plus格式
|
||||||
|
const allowRule = `@@||${domain}^`;
|
||||||
|
console.log(`创建的放行规则: ${allowRule}`);
|
||||||
|
|
||||||
|
// 调用API添加放行规则
|
||||||
|
console.log(`调用API添加放行规则,路径: /shield, 方法: POST`);
|
||||||
|
const response = await apiRequest('/shield', 'POST', { rule: allowRule });
|
||||||
|
|
||||||
|
console.log(`API响应:`, response);
|
||||||
|
|
||||||
|
// 处理不同的响应格式
|
||||||
|
if (response && (response.success || response.status === 'success')) {
|
||||||
|
// 重新加载日志,显示更新后的状态
|
||||||
|
loadLogs();
|
||||||
|
|
||||||
|
// 刷新规则列表
|
||||||
|
refreshRulesList();
|
||||||
|
|
||||||
|
// 显示成功通知
|
||||||
|
if (typeof window.showNotification === 'function') {
|
||||||
|
window.showNotification(`已成功放行域名: ${domain}`, 'success');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const errorMsg = response ? (response.message || '添加放行规则失败') : '添加放行规则失败: 无效的API响应';
|
||||||
|
console.error(`放行域名失败: ${errorMsg}`);
|
||||||
|
throw new Error(errorMsg);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('放行域名失败:', error);
|
||||||
|
|
||||||
|
// 显示错误通知
|
||||||
|
if (typeof window.showNotification === 'function') {
|
||||||
|
window.showNotification(`放行域名失败: ${error.message}`, 'danger');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 定期更新日志统计数据(备用方案)
|
// 定期更新日志统计数据(备用方案)
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
// 只有在查询日志页面时才更新
|
// 只有在查询日志页面时才更新
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ async function loadShieldStats() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载本地规则
|
// 加载自定义规则
|
||||||
async function loadLocalRules() {
|
async function loadLocalRules() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/shield/localrules');
|
const response = await fetch('/api/shield/localrules');
|
||||||
@@ -242,7 +242,7 @@ async function loadLocalRules() {
|
|||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
// 更新本地规则数量显示
|
// 更新自定义规则数量显示
|
||||||
if (document.getElementById('local-rules-count')) {
|
if (document.getElementById('local-rules-count')) {
|
||||||
document.getElementById('local-rules-count').textContent = data.localRulesCount || 0;
|
document.getElementById('local-rules-count').textContent = data.localRulesCount || 0;
|
||||||
}
|
}
|
||||||
@@ -250,7 +250,7 @@ async function loadLocalRules() {
|
|||||||
// 设置当前规则类型
|
// 设置当前规则类型
|
||||||
currentRulesType = 'local';
|
currentRulesType = 'local';
|
||||||
|
|
||||||
// 合并所有本地规则
|
// 合并所有自定义规则
|
||||||
let rules = [];
|
let rules = [];
|
||||||
// 添加域名规则
|
// 添加域名规则
|
||||||
if (Array.isArray(data.domainRules)) {
|
if (Array.isArray(data.domainRules)) {
|
||||||
@@ -271,8 +271,8 @@ async function loadLocalRules() {
|
|||||||
|
|
||||||
updateRulesTable(rules);
|
updateRulesTable(rules);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载本地规则失败:', error);
|
console.error('加载自定义规则失败:', error);
|
||||||
showNotification('加载本地规则失败', 'error');
|
showNotification('加载自定义规则失败', 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1202,7 +1202,7 @@ let currentRulesType = 'local';
|
|||||||
|
|
||||||
// 设置事件监听器
|
// 设置事件监听器
|
||||||
function setupShieldEventListeners() {
|
function setupShieldEventListeners() {
|
||||||
// 本地规则管理事件
|
// 自定义规则管理事件
|
||||||
const saveRuleBtn = document.getElementById('save-rule-btn');
|
const saveRuleBtn = document.getElementById('save-rule-btn');
|
||||||
if (saveRuleBtn) {
|
if (saveRuleBtn) {
|
||||||
saveRuleBtn.addEventListener('click', handleAddRule);
|
saveRuleBtn.addEventListener('click', handleAddRule);
|
||||||
@@ -1214,7 +1214,7 @@ function setupShieldEventListeners() {
|
|||||||
saveBlacklistBtn.addEventListener('click', handleAddBlacklist);
|
saveBlacklistBtn.addEventListener('click', handleAddBlacklist);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加切换查看本地规则和远程规则的事件监听
|
// 添加切换查看自定义规则和远程规则的事件监听
|
||||||
const viewLocalRulesBtn = document.getElementById('view-local-rules-btn');
|
const viewLocalRulesBtn = document.getElementById('view-local-rules-btn');
|
||||||
if (viewLocalRulesBtn) {
|
if (viewLocalRulesBtn) {
|
||||||
viewLocalRulesBtn.addEventListener('click', loadLocalRules);
|
viewLocalRulesBtn.addEventListener('click', loadLocalRules);
|
||||||
|
|||||||
25333
tracker/trackers.json
Normal file
25333
tracker/trackers.json
Normal file
File diff suppressed because it is too large
Load Diff
25333
tracker/trackers.json.bak
Normal file
25333
tracker/trackers.json.bak
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user