日志详情界面更新,增加日志详情API。
This commit is contained in:
21
CHANGELOG.md
21
CHANGELOG.md
@@ -2,6 +2,27 @@
|
||||
|
||||
所有对本项目的显著更改都将记录在此文件中。
|
||||
|
||||
## [1.2.4] - 2025-12-26
|
||||
|
||||
### 改进
|
||||
- 修复DNS解析记录显示,现在显示完整格式:"A: 104.26.24.30 (ttl=193)" 而不仅仅是IP地址
|
||||
- 移除了查询日志列表中的"屏蔽规则"列,但在详情弹窗中仍保留
|
||||
- 在弹窗日志详情中,只有被屏蔽或者有自定义规则时才显示规则信息
|
||||
- 改进了日志详情弹窗的UI/UX,使用现代化设计和动画效果
|
||||
- 移除了右上角的服务器状态卡片(CPU、查询统计等)
|
||||
- 实现了页面滚动时,菜单栏和顶部标题栏保持固定
|
||||
- 优化了页面适应窗口大小的改变,确保在所有设备上都能正确显示
|
||||
### 修复
|
||||
- 修复了移动端侧边栏在打开时遮挡页面内容的问题
|
||||
- 修复了侧边栏布局,分离了桌面端和移动端侧边栏,使用CSS媒体查询控制显示
|
||||
|
||||
## [1.2.3] - 2025-12-25
|
||||
### 修复
|
||||
- 修复DNS服务器地址缺少端口号导致的Server Failed问题
|
||||
- 添加normalizeDNSServerAddress函数,确保DNS服务器地址始终包含端口号,默认添加53端口
|
||||
- 修改所有resolver.Exchange()调用,确保传递的服务器地址包含端口号
|
||||
- 优化DNSSEC服务器合并逻辑,确保DNSSEC服务器地址也包含端口号
|
||||
|
||||
## [1.2.2] - 2025-12-25
|
||||
|
||||
### 新增
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"dns": {
|
||||
"port": 53,
|
||||
"upstreamDNS": [
|
||||
"223.5.5.5"
|
||||
"223.5.5.5:53"
|
||||
],
|
||||
"dnssecUpstreamDNS": [
|
||||
"117.50.10.10",
|
||||
|
||||
213
dns/server.go
213
dns/server.go
@@ -22,6 +22,17 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// 确保DNS服务器地址包含端口号,默认添加53端口
|
||||
func normalizeDNSServerAddress(address string) string {
|
||||
// 检查地址是否已经包含端口号
|
||||
if _, _, err := net.SplitHostPort(address); err != nil {
|
||||
// 如果没有端口号,添加默认的53端口
|
||||
return net.JoinHostPort(address, "53")
|
||||
}
|
||||
// 已经有端口号,直接返回
|
||||
return address
|
||||
}
|
||||
|
||||
// BlockedDomain 屏蔽域名统计
|
||||
type BlockedDomain struct {
|
||||
Domain string
|
||||
@@ -54,21 +65,22 @@ type DNSAnswer struct {
|
||||
|
||||
// QueryLog 查询日志记录
|
||||
type QueryLog struct {
|
||||
Timestamp time.Time // 查询时间
|
||||
ClientIP string // 客户端IP
|
||||
Location string // IP地理位置(国家 城市)
|
||||
Domain string // 查询域名
|
||||
QueryType string // 查询类型
|
||||
ResponseTime int64 // 响应时间(ms)
|
||||
Result string // 查询结果(allowed, blocked, error)
|
||||
BlockRule string // 屏蔽规则(如果被屏蔽)
|
||||
BlockType string // 屏蔽类型(如果被屏蔽)
|
||||
FromCache bool // 是否来自缓存
|
||||
DNSSEC bool // 是否使用了DNSSEC
|
||||
EDNS bool // 是否使用了EDNS
|
||||
DNSServer string // 使用的DNS服务器
|
||||
DNSSECServer string // 使用的DNSSEC专用服务器
|
||||
Answers []DNSAnswer `json:"answers"` // 解析记录
|
||||
Timestamp time.Time `json:"timestamp"` // 查询时间
|
||||
ClientIP string `json:"clientIP"` // 客户端IP
|
||||
Location string `json:"location"` // IP地理位置(国家 城市)
|
||||
Domain string `json:"domain"` // 查询域名
|
||||
QueryType string `json:"queryType"` // 查询类型
|
||||
ResponseTime int64 `json:"responseTime"` // 响应时间(ms)
|
||||
Result string `json:"result"` // 查询结果(allowed, blocked, error)
|
||||
BlockRule string `json:"blockRule"` // 屏蔽规则(如果被屏蔽)
|
||||
BlockType string `json:"blockType"` // 屏蔽类型(如果被屏蔽)
|
||||
FromCache bool `json:"fromCache"` // 是否来自缓存
|
||||
DNSSEC bool `json:"dnssec"` // 是否使用了DNSSEC
|
||||
EDNS bool `json:"edns"` // 是否使用了EDNS
|
||||
DNSServer string `json:"dnsServer"` // 使用的DNS服务器
|
||||
DNSSECServer string `json:"dnssecServer"` // 使用的DNSSEC专用服务器
|
||||
Answers []DNSAnswer `json:"answers"` // 解析记录
|
||||
ResponseCode int `json:"responseCode"` // DNS响应代码
|
||||
}
|
||||
|
||||
// StatsData 用于持久化的统计数据结构
|
||||
@@ -375,7 +387,7 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
||||
})
|
||||
|
||||
// 添加查询日志
|
||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "error", "", "", false, false, true, "", "", nil)
|
||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "error", "", "", false, false, true, "", "", nil, dns.RcodeNameError)
|
||||
logger.Debug("IPv6解析已禁用,拒绝AAAA记录查询", "domain", domain)
|
||||
return
|
||||
}
|
||||
@@ -401,7 +413,7 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
||||
})
|
||||
|
||||
// 添加查询日志
|
||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "error", "", "", false, false, true, "", "", nil)
|
||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "error", "", "", false, false, true, "", "", nil, dns.RcodeRefused)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -438,9 +450,16 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
||||
}
|
||||
})
|
||||
|
||||
// 添加查询日志
|
||||
// 添加查询日志 - 被屏蔽域名
|
||||
blockedAnswers := []DNSAnswer{}
|
||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "blocked", blockRule, blockType, false, false, true, "无", "无", blockedAnswers)
|
||||
// 根据屏蔽方法确定响应代码
|
||||
blockedRcode := dns.RcodeNameError // 默认NXDOMAIN
|
||||
if blockMethod := s.shieldConfig.BlockMethod; blockMethod == "refused" {
|
||||
blockedRcode = dns.RcodeRefused
|
||||
} else if blockMethod == "emptyIP" || blockMethod == "customIP" {
|
||||
blockedRcode = dns.RcodeSuccess
|
||||
}
|
||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "blocked", blockRule, blockType, false, false, true, "无", "无", blockedAnswers, blockedRcode)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -525,7 +544,12 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
||||
}
|
||||
|
||||
// 添加查询日志 - 标记为缓存
|
||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", true, cachedDNSSEC, true, "缓存", "无", cachedAnswers)
|
||||
// 从缓存响应中获取响应代码
|
||||
cacheRcode := dns.RcodeSuccess // 默认成功
|
||||
if cachedResponse != nil {
|
||||
cacheRcode = cachedResponse.Rcode
|
||||
}
|
||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", true, cachedDNSSEC, true, "缓存", "无", cachedAnswers, cacheRcode)
|
||||
logger.Debug("从缓存返回DNS响应", "domain", domain, "type", queryType, "dnssec", cachedDNSSEC)
|
||||
return
|
||||
}
|
||||
@@ -622,7 +646,12 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
||||
}
|
||||
|
||||
// 添加查询日志 - 标记为实时
|
||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", false, responseDNSSEC, true, dnsServer, dnssecServer, responseAnswers)
|
||||
// 从响应中获取响应代码
|
||||
realRcode := dns.RcodeSuccess // 默认成功
|
||||
if response != nil {
|
||||
realRcode = response.Rcode
|
||||
}
|
||||
s.addQueryLog(sourceIP, domain, queryType, responseTime, "allowed", "", "", false, responseDNSSEC, true, dnsServer, dnssecServer, responseAnswers, realRcode)
|
||||
}
|
||||
|
||||
// handleHostsResponse 处理hosts文件匹配的响应
|
||||
@@ -795,17 +824,19 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
|
||||
|
||||
// 如果启用了DNSSEC且有配置DNSSEC专用服务器,并且域名不匹配NoDNSSECDomains,则将DNSSEC专用服务器添加到列表中
|
||||
if s.config.EnableDNSSEC && len(s.config.DNSSECUpstreamDNS) > 0 && !noDNSSEC {
|
||||
// 合并DNSSEC专用服务器到上游服务器列表,避免重复
|
||||
// 合并DNSSEC专用服务器到上游服务器列表,避免重复,并确保包含端口号
|
||||
for _, dnssecServer := range s.config.DNSSECUpstreamDNS {
|
||||
hasDuplicate := false
|
||||
// 确保DNSSEC服务器地址包含端口号
|
||||
normalizedDnssecServer := normalizeDNSServerAddress(dnssecServer)
|
||||
for _, upstream := range finalUpstreamDNS {
|
||||
if upstream == dnssecServer {
|
||||
if upstream == normalizedDnssecServer {
|
||||
hasDuplicate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasDuplicate {
|
||||
finalUpstreamDNS = append(finalUpstreamDNS, dnssecServer)
|
||||
finalUpstreamDNS = append(finalUpstreamDNS, normalizedDnssecServer)
|
||||
}
|
||||
}
|
||||
logger.Debug("合并DNSSEC专用服务器到上游服务器列表", "servers", finalUpstreamDNS)
|
||||
@@ -843,8 +874,8 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
|
||||
go func(server string) {
|
||||
defer wg.Done()
|
||||
|
||||
// 发送请求并获取响应
|
||||
response, rtt, err := s.resolver.Exchange(r, server)
|
||||
// 发送请求并获取响应,确保服务器地址包含端口号
|
||||
response, rtt, err := s.resolver.Exchange(r, normalizeDNSServerAddress(server))
|
||||
|
||||
select {
|
||||
case responses <- serverResponse{response, rtt, server, err}:
|
||||
@@ -899,72 +930,103 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
|
||||
resp.response.AuthenticatedData = false
|
||||
}
|
||||
|
||||
// 如果响应成功或为NXDOMAIN,根据DNSSEC状态选择最佳响应
|
||||
if resp.response.Rcode == dns.RcodeSuccess || resp.response.Rcode == dns.RcodeNameError {
|
||||
// 检查当前使用的服务器是否是DNSSEC专用服务器
|
||||
for _, dnssecServer := range dnssecServers {
|
||||
if dnssecServer == resp.server {
|
||||
usedDNSSECServer = resp.server
|
||||
break
|
||||
}
|
||||
// 检查当前服务器是否是DNSSEC专用服务器
|
||||
for _, dnssecServer := range dnssecServers {
|
||||
if dnssecServer == resp.server {
|
||||
usedDNSSECServer = resp.server
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if resp.response.Rcode == dns.RcodeSuccess {
|
||||
// 处理成功响应
|
||||
// 检查当前服务器是否是用户配置的上游DNS服务器(优先使用用户配置的服务器)
|
||||
isUserUpstream := false
|
||||
for _, userServer := range s.config.UpstreamDNS {
|
||||
if userServer == resp.server {
|
||||
isUserUpstream = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// 检查当前服务器是否是用户配置的上游DNS服务器
|
||||
isUserUpstream := false
|
||||
for _, userServer := range s.config.UpstreamDNS {
|
||||
if userServer == resp.server {
|
||||
isUserUpstream = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 优先选择用户配置的上游DNS服务器的响应
|
||||
if isUserUpstream {
|
||||
bestResponse = resp.response
|
||||
bestRtt = resp.rtt
|
||||
hasBestResponse = true
|
||||
hasDNSSECResponse = containsDNSSEC
|
||||
usedDNSServer = resp.server
|
||||
logger.Debug("使用用户配置的上游服务器响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
|
||||
} else if containsDNSSEC {
|
||||
// 如果不是用户配置的服务器,优先选择带有DNSSEC记录的响应
|
||||
// 处理响应,优先选择用户配置的主DNS服务器
|
||||
if resp.response.Rcode == dns.RcodeSuccess {
|
||||
// 成功响应,优先使用
|
||||
if isUserUpstream {
|
||||
// 用户配置的主DNS服务器响应,直接设置为最佳响应
|
||||
bestResponse = resp.response
|
||||
bestRtt = resp.rtt
|
||||
hasBestResponse = true
|
||||
hasDNSSECResponse = containsDNSSEC
|
||||
usedDNSServer = resp.server
|
||||
logger.Debug("使用用户配置的上游服务器响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
|
||||
} else if containsDNSSEC {
|
||||
// 非用户配置服务器,但有DNSSEC记录
|
||||
if !hasBestResponse || !isUserUpstream {
|
||||
// 如果还没有最佳响应,或者当前最佳响应不是用户配置的服务器,则更新
|
||||
bestResponse = resp.response
|
||||
bestRtt = resp.rtt
|
||||
hasBestResponse = true
|
||||
hasDNSSECResponse = true
|
||||
usedDNSServer = resp.server
|
||||
logger.Debug("找到带DNSSEC的最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
|
||||
} else if !hasBestResponse {
|
||||
// 没有带DNSSEC的响应时,保存第一个成功响应
|
||||
}
|
||||
} else {
|
||||
// 非用户配置服务器,没有DNSSEC记录
|
||||
if !hasBestResponse {
|
||||
// 如果还没有最佳响应,设置为最佳响应
|
||||
bestResponse = resp.response
|
||||
bestRtt = resp.rtt
|
||||
hasBestResponse = true
|
||||
usedDNSServer = resp.server
|
||||
logger.Debug("找到最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
|
||||
}
|
||||
} else if resp.response.Rcode == dns.RcodeNameError {
|
||||
// 处理NXDOMAIN响应
|
||||
// 如果还没有最佳响应,或者最佳响应也是NXDOMAIN,优先选择更快的NXDOMAIN响应
|
||||
if !hasBestResponse || bestResponse.Rcode == dns.RcodeNameError {
|
||||
// 如果还没有最佳响应,或者当前响应更快,更新最佳响应
|
||||
if !hasBestResponse || resp.rtt < bestRtt {
|
||||
bestResponse = resp.response
|
||||
bestRtt = resp.rtt
|
||||
hasBestResponse = true
|
||||
usedDNSServer = resp.server
|
||||
logger.Debug("找到NXDOMAIN最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
|
||||
}
|
||||
}
|
||||
} else if resp.response.Rcode == dns.RcodeNameError {
|
||||
// NXDOMAIN响应
|
||||
if !hasBestResponse || bestResponse.Rcode == dns.RcodeNameError {
|
||||
// 如果还没有最佳响应,或者最佳响应也是NXDOMAIN
|
||||
if isUserUpstream {
|
||||
// 用户配置的服务器,直接使用
|
||||
bestResponse = resp.response
|
||||
bestRtt = resp.rtt
|
||||
hasBestResponse = true
|
||||
usedDNSServer = resp.server
|
||||
logger.Debug("使用用户配置的上游服务器NXDOMAIN响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
|
||||
} else if !hasBestResponse || resp.rtt < bestRtt {
|
||||
// 非用户配置服务器,选择更快的响应
|
||||
bestResponse = resp.response
|
||||
bestRtt = resp.rtt
|
||||
hasBestResponse = true
|
||||
usedDNSServer = resp.server
|
||||
logger.Debug("找到NXDOMAIN最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
|
||||
}
|
||||
}
|
||||
// 保存为备选响应
|
||||
}
|
||||
|
||||
// 更新备选响应,确保总有一个可用的响应
|
||||
if resp.response != nil {
|
||||
if !hasBackup {
|
||||
// 第一次保存备选响应
|
||||
backupResponse = resp.response
|
||||
backupRtt = resp.rtt
|
||||
hasBackup = true
|
||||
} else {
|
||||
// 后续响应,优先保存用户配置的服务器响应作为备选
|
||||
if isUserUpstream {
|
||||
backupResponse = resp.response
|
||||
backupRtt = resp.rtt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 即使响应不是成功或NXDOMAIN,也保存为最佳响应(如果还没有的话)
|
||||
// 确保总有一个响应返回给客户端
|
||||
if !hasBestResponse {
|
||||
bestResponse = resp.response
|
||||
bestRtt = resp.rtt
|
||||
hasBestResponse = true
|
||||
usedDNSServer = resp.server
|
||||
logger.Debug("使用非成功响应作为最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt, "rcode", resp.response.Rcode)
|
||||
}
|
||||
} else {
|
||||
// 更新服务器统计信息(失败)
|
||||
s.updateServerStats(resp.server, false, 0)
|
||||
@@ -1011,7 +1073,7 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
|
||||
}, 1)
|
||||
|
||||
go func() {
|
||||
response, rtt, err := s.resolver.Exchange(r, selectedServer)
|
||||
response, rtt, err := s.resolver.Exchange(r, normalizeDNSServerAddress(selectedServer))
|
||||
resultChan <- struct {
|
||||
response *dns.Msg
|
||||
rtt time.Duration
|
||||
@@ -1137,7 +1199,7 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
|
||||
}, 1)
|
||||
|
||||
go func() {
|
||||
resp, r, e := s.resolver.Exchange(r, fastestServer)
|
||||
resp, r, e := s.resolver.Exchange(r, normalizeDNSServerAddress(fastestServer))
|
||||
resultChan <- struct {
|
||||
response *dns.Msg
|
||||
rtt time.Duration
|
||||
@@ -1259,7 +1321,7 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
|
||||
defer wg.Done()
|
||||
|
||||
// 发送请求并获取响应
|
||||
response, rtt, err := s.resolver.Exchange(r, server)
|
||||
response, rtt, err := s.resolver.Exchange(r, normalizeDNSServerAddress(server))
|
||||
|
||||
select {
|
||||
case responses <- serverResponse{response, rtt, server, err}:
|
||||
@@ -1400,7 +1462,7 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
|
||||
}, 1)
|
||||
|
||||
go func() {
|
||||
response, rtt, err := s.resolver.Exchange(r, selectedDnssecServer)
|
||||
response, rtt, err := s.resolver.Exchange(r, normalizeDNSServerAddress(selectedDnssecServer))
|
||||
resultChan <- struct {
|
||||
response *dns.Msg
|
||||
rtt time.Duration
|
||||
@@ -1498,7 +1560,7 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
|
||||
}, 1)
|
||||
|
||||
go func() {
|
||||
resp, r, e := s.resolver.Exchange(r, localServer)
|
||||
resp, r, e := s.resolver.Exchange(r, normalizeDNSServerAddress(localServer))
|
||||
resultChan <- struct {
|
||||
response *dns.Msg
|
||||
rtt time.Duration
|
||||
@@ -2083,7 +2145,7 @@ func (s *Server) updateStats(update func(*Stats)) {
|
||||
}
|
||||
|
||||
// addQueryLog 添加查询日志
|
||||
func (s *Server) addQueryLog(clientIP, domain, queryType string, responseTime int64, result, blockRule, blockType string, fromCache, dnssec, edns bool, dnsServer, dnssecServer string, answers []DNSAnswer) {
|
||||
func (s *Server) addQueryLog(clientIP, domain, queryType string, responseTime int64, result, blockRule, blockType string, fromCache, dnssec, edns bool, dnsServer, dnssecServer string, answers []DNSAnswer, responseCode int) {
|
||||
// 获取IP地理位置
|
||||
location := s.getIpGeolocation(clientIP)
|
||||
|
||||
@@ -2104,6 +2166,7 @@ func (s *Server) addQueryLog(clientIP, domain, queryType string, responseTime in
|
||||
DNSServer: dnsServer,
|
||||
DNSSECServer: dnssecServer,
|
||||
Answers: answers,
|
||||
ResponseCode: responseCode,
|
||||
}
|
||||
|
||||
// 添加到日志列表
|
||||
|
||||
@@ -18,9 +18,67 @@
|
||||
<body class="bg-gray-50 text-dark font-sans">
|
||||
<div class="flex h-screen overflow-hidden">
|
||||
<!-- 侧边栏 -->
|
||||
<aside id="sidebar" class="fixed inset-y-0 left-0 w-64 bg-white border-r border-gray-200 flex flex-col transition-transform duration-300 z-50 md:relative md:translate-x-0 -translate-x-full shadow-lg">
|
||||
<aside id="sidebar" class="fixed inset-y-0 left-0 w-64 bg-white border-r border-gray-200 flex flex-col transition-transform duration-300 z-50 hidden md:flex">
|
||||
<!-- Logo -->
|
||||
<div class="flex items-center justify-center h-16 border-b border-gray-200">
|
||||
<i class="fa fa-server text-3xl text-primary mr-3"></i>
|
||||
<h1 class="text-xl font-bold text-primary">DNS 控制台</h1>
|
||||
</div>
|
||||
|
||||
<!-- 菜单 -->
|
||||
<nav class="flex-1 overflow-y-auto p-4">
|
||||
<ul class="space-y-1">
|
||||
<li>
|
||||
<a href="#dashboard" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all sidebar-item-active">
|
||||
<i class="fa fa-tachometer mr-3 text-lg"></i>
|
||||
<span>仪表盘</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#shield" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all">
|
||||
<i class="fa fa-shield mr-3 text-lg"></i>
|
||||
<span>屏蔽管理</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#hosts" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all">
|
||||
<i class="fa fa-file-text mr-3 text-lg"></i>
|
||||
<span>Hosts管理</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="#query" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all">
|
||||
<i class="fa fa-search mr-3 text-lg"></i>
|
||||
<span>DNS屏蔽查询</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#logs" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all">
|
||||
<i class="fa fa-file-text-o mr-3 text-lg"></i>
|
||||
<span>查询日志</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#config" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all">
|
||||
<i class="fa fa-cog mr-3 text-lg"></i>
|
||||
<span>系统设置</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- 底部信息 -->
|
||||
<div class="p-4 border-t border-gray-200 text-center text-gray-500 text-sm">
|
||||
<p>DNS服务器 v1.2.0</p>
|
||||
<p class="mt-1" id="uptime">正常运行中</p>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- 移动端侧边栏 -->
|
||||
<aside id="mobile-sidebar" class="fixed inset-y-0 left-0 w-64 bg-white border-r border-gray-200 flex flex-col transition-transform duration-300 z-50 -translate-x-full md:hidden">
|
||||
<!-- 移动端关闭按钮 -->
|
||||
<div class="absolute top-4 right-4 md:hidden">
|
||||
<div class="absolute top-4 right-4">
|
||||
<button id="close-sidebar" class="p-2 text-gray-500 hover:text-gray-700 focus:outline-none">
|
||||
<i class="fa fa-times text-xl"></i>
|
||||
</button>
|
||||
@@ -85,9 +143,9 @@
|
||||
<div id="sidebar-overlay" class="fixed inset-0 bg-black bg-opacity-50 z-40 hidden md:hidden"></div>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<main class="flex-1 overflow-y-auto">
|
||||
<main class="flex-1 flex flex-col md:ml-64">
|
||||
<!-- 顶部导航栏 -->
|
||||
<header class="bg-white border-b border-gray-200 h-16 flex items-center justify-between px-6">
|
||||
<header class="bg-white border-b border-gray-200 h-16 flex items-center justify-between px-6 sticky top-0 z-30">
|
||||
<div class="flex items-center">
|
||||
<button id="toggle-sidebar" class="block md:hidden text-gray-500 hover:text-gray-700 focus:outline-none">
|
||||
<i class="fa fa-bars text-xl"></i>
|
||||
@@ -96,55 +154,6 @@
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-4">
|
||||
<!-- 服务器状态组件 -->
|
||||
<div class="relative bg-white rounded-lg shadow-md px-3 py-2 flex items-center space-x-2 server-status-widget md:min-w-[300px] sm:min-w-[250px] min-w-[180px]" id="server-status-widget">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center">
|
||||
<span class="text-xs font-medium text-gray-500">CPU</span>
|
||||
<span id="server-cpu-value" class="ml-2 text-sm font-semibold">0%</span>
|
||||
</div>
|
||||
<div class="w-16 h-1 bg-gray-100 rounded-full mt-1">
|
||||
<div id="server-cpu-bar" class="h-full bg-warning rounded-full" style="width: 0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-1 h-8 bg-gray-200 rounded-full mx-1"></div>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center">
|
||||
<span class="text-xs font-medium text-gray-500">查询</span>
|
||||
<span id="server-queries-value" class="ml-2 text-sm font-semibold">0</span>
|
||||
</div>
|
||||
<div class="w-16 h-1 bg-gray-100 rounded-full mt-1">
|
||||
<div id="server-queries-bar" class="h-full bg-primary rounded-full" style="width: 0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 额外指标区域 - 初始隐藏,只在非首页显示 -->
|
||||
<div id="server-additional-stats" class="hidden md:flex items-center">
|
||||
<div class="w-1 h-8 bg-gray-200 rounded-full mx-1"></div>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center">
|
||||
<span class="text-xs font-medium text-gray-500">总量</span>
|
||||
<span id="server-total-queries" class="ml-2 text-sm font-semibold">0</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-1 h-8 bg-gray-200 rounded-full mx-1"></div>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center">
|
||||
<span class="text-xs font-medium text-gray-500">屏蔽</span>
|
||||
<span id="server-blocked-queries" class="ml-2 text-sm font-semibold">0</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-1 h-8 bg-gray-200 rounded-full mx-1"></div>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center">
|
||||
<span class="text-xs font-medium text-gray-500">正常</span>
|
||||
<span id="server-allowed-queries" class="ml-2 text-sm font-semibold">0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute top-1 right-1">
|
||||
<span id="server-status-indicator" class="inline-block w-2 h-2 bg-success rounded-full"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100">
|
||||
<i class="fa fa-bell text-lg"></i>
|
||||
@@ -170,7 +179,7 @@
|
||||
</header>
|
||||
|
||||
<!-- 页面内容 -->
|
||||
<div class="p-6">
|
||||
<div class="p-6 overflow-y-auto flex-1">
|
||||
<!-- 仪表盘部分 -->
|
||||
<div id="dashboard-content" class="space-y-6">
|
||||
<!-- 统计卡片 -->
|
||||
@@ -975,13 +984,12 @@
|
||||
</div>
|
||||
</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>
|
||||
</thead>
|
||||
<tbody id="logs-table-body">
|
||||
<tr>
|
||||
<td colspan="6" class="py-8 text-center text-gray-500 border-b border-gray-100">
|
||||
<td colspan="5" 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>
|
||||
<div>暂无查询日志</div>
|
||||
</td>
|
||||
@@ -1166,7 +1174,6 @@
|
||||
<script src="js/main.js"></script>
|
||||
<script src="js/api.js"></script>
|
||||
<script src="js/dashboard.js"></script>
|
||||
<script src="js/server-status.js"></script>
|
||||
<script src="js/shield.js"></script>
|
||||
<script src="js/hosts.js"></script>
|
||||
<script src="js/query.js"></script>
|
||||
|
||||
@@ -2964,33 +2964,10 @@ window.addEventListener('hashchange', handleHashChange);
|
||||
// 初始化hash路由 - 确保在页面加载时就能被调用
|
||||
initHashRoute();
|
||||
|
||||
// 侧边栏切换
|
||||
function toggleSidebar() {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
sidebar.classList.toggle('-translate-x-full');
|
||||
}
|
||||
|
||||
// 响应式处理
|
||||
function handleResponsive() {
|
||||
const toggleBtn = document.getElementById('toggle-sidebar');
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
|
||||
toggleBtn.addEventListener('click', toggleSidebar);
|
||||
|
||||
// 初始状态处理
|
||||
function updateSidebarState() {
|
||||
if (window.innerWidth < 1024) {
|
||||
sidebar.classList.add('-translate-x-full');
|
||||
} else {
|
||||
sidebar.classList.remove('-translate-x-full');
|
||||
}
|
||||
}
|
||||
|
||||
updateSidebarState();
|
||||
|
||||
// 窗口大小改变时处理
|
||||
window.addEventListener('resize', () => {
|
||||
updateSidebarState();
|
||||
|
||||
// 更新所有图表大小
|
||||
if (dnsRequestsChart) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -28,7 +28,7 @@ function setupNavigation() {
|
||||
// 移动端侧边栏切换
|
||||
const toggleSidebar = document.getElementById('toggle-sidebar');
|
||||
const closeSidebarBtn = document.getElementById('close-sidebar');
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const sidebar = document.getElementById('mobile-sidebar');
|
||||
const sidebarOverlay = document.getElementById('sidebar-overlay');
|
||||
|
||||
// 打开侧边栏函数
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
{
|
||||
"dns": {
|
||||
"port": 5353,
|
||||
"upstreamDNS": [
|
||||
"223.5.5.5:53",
|
||||
"223.6.6.6:53",
|
||||
"117.50.10.10:53",
|
||||
"10.35.10.200:53"
|
||||
],
|
||||
"dnssecUpstreamDNS": [
|
||||
"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,
|
||||
"statsFile": "data/stats.json",
|
||||
"saveInterval": 300,
|
||||
"cacheTTL": 30,
|
||||
"enableDNSSEC": true,
|
||||
"queryMode": "parallel",
|
||||
"domainSpecificDNS": {
|
||||
"amazehome.xyz": ["10.35.10.200:53"]
|
||||
}
|
||||
},
|
||||
"http": {
|
||||
"port": 8081,
|
||||
"host": "0.0.0.0",
|
||||
"enableAPI": true,
|
||||
"username": "admin",
|
||||
"password": "admin"
|
||||
},
|
||||
"shield": {
|
||||
"localRulesFile": "data/rules.txt",
|
||||
"blacklists": [],
|
||||
"updateInterval": 3600,
|
||||
"hostsFile": "data/hosts.txt",
|
||||
"blockMethod": "NXDOMAIN",
|
||||
"customBlockIP": "",
|
||||
"statsFile": "./data/shield_stats.json",
|
||||
"statsSaveInterval": 60,
|
||||
"remoteRulesCacheDir": "data/remote_rules"
|
||||
},
|
||||
"log": {
|
||||
"file": "logs/dns-server-5353.log",
|
||||
"level": "debug",
|
||||
"maxSize": 100,
|
||||
"maxBackups": 10,
|
||||
"maxAge": 30
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user