日志详情界面更新,增加日志详情API。

This commit is contained in:
Alex Yang
2025-12-26 00:53:18 +08:00
parent 01f2152e46
commit 42c4d6cfeb
9 changed files with 2449 additions and 787 deletions

View File

@@ -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 ## [1.2.2] - 2025-12-25
### 新增 ### 新增

View File

@@ -2,7 +2,7 @@
"dns": { "dns": {
"port": 53, "port": 53,
"upstreamDNS": [ "upstreamDNS": [
"223.5.5.5" "223.5.5.5:53"
], ],
"dnssecUpstreamDNS": [ "dnssecUpstreamDNS": [
"117.50.10.10", "117.50.10.10",

View File

@@ -22,6 +22,17 @@ import (
"github.com/miekg/dns" "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 屏蔽域名统计 // BlockedDomain 屏蔽域名统计
type BlockedDomain struct { type BlockedDomain struct {
Domain string Domain string
@@ -54,21 +65,22 @@ type DNSAnswer struct {
// QueryLog 查询日志记录 // QueryLog 查询日志记录
type QueryLog struct { type QueryLog struct {
Timestamp time.Time // 查询时间 Timestamp time.Time `json:"timestamp"` // 查询时间
ClientIP string // 客户端IP ClientIP string `json:"clientIP"` // 客户端IP
Location string // IP地理位置国家 城市) Location string `json:"location"` // IP地理位置国家 城市)
Domain string // 查询域名 Domain string `json:"domain"` // 查询域名
QueryType string // 查询类型 QueryType string `json:"queryType"` // 查询类型
ResponseTime int64 // 响应时间(ms) ResponseTime int64 `json:"responseTime"` // 响应时间(ms)
Result string // 查询结果allowed, blocked, error Result string `json:"result"` // 查询结果allowed, blocked, error
BlockRule string // 屏蔽规则(如果被屏蔽) BlockRule string `json:"blockRule"` // 屏蔽规则(如果被屏蔽)
BlockType string // 屏蔽类型(如果被屏蔽) BlockType string `json:"blockType"` // 屏蔽类型(如果被屏蔽)
FromCache bool // 是否来自缓存 FromCache bool `json:"fromCache"` // 是否来自缓存
DNSSEC bool // 是否使用了DNSSEC DNSSEC bool `json:"dnssec"` // 是否使用了DNSSEC
EDNS bool // 是否使用了EDNS EDNS bool `json:"edns"` // 是否使用了EDNS
DNSServer string // 使用的DNS服务器 DNSServer string `json:"dnsServer"` // 使用的DNS服务器
DNSSECServer string // 使用的DNSSEC专用服务器 DNSSECServer string `json:"dnssecServer"` // 使用的DNSSEC专用服务器
Answers []DNSAnswer `json:"answers"` // 解析记录 Answers []DNSAnswer `json:"answers"` // 解析记录
ResponseCode int `json:"responseCode"` // DNS响应代码
} }
// StatsData 用于持久化的统计数据结构 // 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) logger.Debug("IPv6解析已禁用拒绝AAAA记录查询", "domain", domain)
return 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 return
} }
@@ -438,9 +450,16 @@ func (s *Server) handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
} }
}) })
// 添加查询日志 // 添加查询日志 - 被屏蔽域名
blockedAnswers := []DNSAnswer{} 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 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) logger.Debug("从缓存返回DNS响应", "domain", domain, "type", queryType, "dnssec", cachedDNSSEC)
return 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文件匹配的响应 // handleHostsResponse 处理hosts文件匹配的响应
@@ -795,17 +824,19 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
// 如果启用了DNSSEC且有配置DNSSEC专用服务器并且域名不匹配NoDNSSECDomains则将DNSSEC专用服务器添加到列表中 // 如果启用了DNSSEC且有配置DNSSEC专用服务器并且域名不匹配NoDNSSECDomains则将DNSSEC专用服务器添加到列表中
if s.config.EnableDNSSEC && len(s.config.DNSSECUpstreamDNS) > 0 && !noDNSSEC { if s.config.EnableDNSSEC && len(s.config.DNSSECUpstreamDNS) > 0 && !noDNSSEC {
// 合并DNSSEC专用服务器到上游服务器列表避免重复 // 合并DNSSEC专用服务器到上游服务器列表避免重复,并确保包含端口号
for _, dnssecServer := range s.config.DNSSECUpstreamDNS { for _, dnssecServer := range s.config.DNSSECUpstreamDNS {
hasDuplicate := false hasDuplicate := false
// 确保DNSSEC服务器地址包含端口号
normalizedDnssecServer := normalizeDNSServerAddress(dnssecServer)
for _, upstream := range finalUpstreamDNS { for _, upstream := range finalUpstreamDNS {
if upstream == dnssecServer { if upstream == normalizedDnssecServer {
hasDuplicate = true hasDuplicate = true
break break
} }
} }
if !hasDuplicate { if !hasDuplicate {
finalUpstreamDNS = append(finalUpstreamDNS, dnssecServer) finalUpstreamDNS = append(finalUpstreamDNS, normalizedDnssecServer)
} }
} }
logger.Debug("合并DNSSEC专用服务器到上游服务器列表", "servers", finalUpstreamDNS) logger.Debug("合并DNSSEC专用服务器到上游服务器列表", "servers", finalUpstreamDNS)
@@ -843,8 +874,8 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
go func(server string) { go func(server string) {
defer wg.Done() defer wg.Done()
// 发送请求并获取响应 // 发送请求并获取响应,确保服务器地址包含端口号
response, rtt, err := s.resolver.Exchange(r, server) response, rtt, err := s.resolver.Exchange(r, normalizeDNSServerAddress(server))
select { select {
case responses <- serverResponse{response, rtt, server, err}: 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 resp.response.AuthenticatedData = false
} }
// 如果响应成功或为NXDOMAIN根据DNSSEC状态选择最佳响应 // 检查当前服务器是否是DNSSEC专用服务器
if resp.response.Rcode == dns.RcodeSuccess || resp.response.Rcode == dns.RcodeNameError { for _, dnssecServer := range dnssecServers {
// 检查当前使用的服务器是否是DNSSEC专用服务器 if dnssecServer == resp.server {
for _, dnssecServer := range dnssecServers { usedDNSSECServer = resp.server
if dnssecServer == resp.server { break
usedDNSSECServer = resp.server
break
}
} }
}
if resp.response.Rcode == dns.RcodeSuccess { // 检查当前服务器是否是用户配置的上游DNS服务器
// 处理成功响应 isUserUpstream := false
// 检查当前服务器是否是用户配置的上游DNS服务器优先使用用户配置的服务器 for _, userServer := range s.config.UpstreamDNS {
isUserUpstream := false if userServer == resp.server {
for _, userServer := range s.config.UpstreamDNS { isUserUpstream = true
if userServer == resp.server { break
isUserUpstream = true }
break }
}
}
// 优先选择用户配置的上游DNS服务器的响应 // 处理响应,优先选择用户配置的DNS服务器
if isUserUpstream { if resp.response.Rcode == dns.RcodeSuccess {
bestResponse = resp.response // 成功响应,优先使用
bestRtt = resp.rtt if isUserUpstream {
hasBestResponse = true // 用户配置的主DNS服务器响应直接设置为最佳响应
hasDNSSECResponse = containsDNSSEC bestResponse = resp.response
usedDNSServer = resp.server bestRtt = resp.rtt
logger.Debug("使用用户配置的上游服务器响应", "domain", domain, "server", resp.server, "rtt", resp.rtt) hasBestResponse = true
} else if containsDNSSEC { hasDNSSECResponse = containsDNSSEC
// 如果不是用户配置的服务器优先选择带有DNSSEC记录的响应 usedDNSServer = resp.server
logger.Debug("使用用户配置的上游服务器响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
} else if containsDNSSEC {
// 非用户配置服务器但有DNSSEC记录
if !hasBestResponse || !isUserUpstream {
// 如果还没有最佳响应,或者当前最佳响应不是用户配置的服务器,则更新
bestResponse = resp.response bestResponse = resp.response
bestRtt = resp.rtt bestRtt = resp.rtt
hasBestResponse = true hasBestResponse = true
hasDNSSECResponse = true hasDNSSECResponse = true
usedDNSServer = resp.server usedDNSServer = resp.server
logger.Debug("找到带DNSSEC的最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt) logger.Debug("找到带DNSSEC的最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
} else if !hasBestResponse { }
// 没有带DNSSEC的响应时保存第一个成功响应 } else {
// 非用户配置服务器没有DNSSEC记录
if !hasBestResponse {
// 如果还没有最佳响应,设置为最佳响应
bestResponse = resp.response bestResponse = resp.response
bestRtt = resp.rtt bestRtt = resp.rtt
hasBestResponse = true hasBestResponse = true
usedDNSServer = resp.server usedDNSServer = resp.server
logger.Debug("找到最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt) logger.Debug("找到最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt)
} }
} else if resp.response.Rcode == dns.RcodeNameError { }
// 处理NXDOMAIN响应 } else if resp.response.Rcode == dns.RcodeNameError {
// 如果还没有最佳响应或者最佳响应也是NXDOMAIN优先选择更快的NXDOMAIN响应 // NXDOMAIN响应
if !hasBestResponse || bestResponse.Rcode == dns.RcodeNameError { if !hasBestResponse || bestResponse.Rcode == dns.RcodeNameError {
// 如果还没有最佳响应,或者当前响应更快,更新最佳响应 // 如果还没有最佳响应,或者最佳响应也是NXDOMAIN
if !hasBestResponse || resp.rtt < bestRtt { if isUserUpstream {
bestResponse = resp.response // 用户配置的服务器,直接使用
bestRtt = resp.rtt bestResponse = resp.response
hasBestResponse = true bestRtt = resp.rtt
usedDNSServer = resp.server hasBestResponse = true
logger.Debug("找到NXDOMAIN最佳响应", "domain", domain, "server", resp.server, "rtt", resp.rtt) 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 { if !hasBackup {
// 第一次保存备选响应
backupResponse = resp.response backupResponse = resp.response
backupRtt = resp.rtt backupRtt = resp.rtt
hasBackup = true 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 { } else {
// 更新服务器统计信息(失败) // 更新服务器统计信息(失败)
s.updateServerStats(resp.server, false, 0) s.updateServerStats(resp.server, false, 0)
@@ -1011,7 +1073,7 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
}, 1) }, 1)
go func() { go func() {
response, rtt, err := s.resolver.Exchange(r, selectedServer) response, rtt, err := s.resolver.Exchange(r, normalizeDNSServerAddress(selectedServer))
resultChan <- struct { resultChan <- struct {
response *dns.Msg response *dns.Msg
rtt time.Duration rtt time.Duration
@@ -1137,7 +1199,7 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
}, 1) }, 1)
go func() { go func() {
resp, r, e := s.resolver.Exchange(r, fastestServer) resp, r, e := s.resolver.Exchange(r, normalizeDNSServerAddress(fastestServer))
resultChan <- struct { resultChan <- struct {
response *dns.Msg response *dns.Msg
rtt time.Duration rtt time.Duration
@@ -1259,7 +1321,7 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
defer wg.Done() defer wg.Done()
// 发送请求并获取响应 // 发送请求并获取响应
response, rtt, err := s.resolver.Exchange(r, server) response, rtt, err := s.resolver.Exchange(r, normalizeDNSServerAddress(server))
select { select {
case responses <- serverResponse{response, rtt, server, err}: case responses <- serverResponse{response, rtt, server, err}:
@@ -1400,7 +1462,7 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
}, 1) }, 1)
go func() { go func() {
response, rtt, err := s.resolver.Exchange(r, selectedDnssecServer) response, rtt, err := s.resolver.Exchange(r, normalizeDNSServerAddress(selectedDnssecServer))
resultChan <- struct { resultChan <- struct {
response *dns.Msg response *dns.Msg
rtt time.Duration rtt time.Duration
@@ -1498,7 +1560,7 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
}, 1) }, 1)
go func() { go func() {
resp, r, e := s.resolver.Exchange(r, localServer) resp, r, e := s.resolver.Exchange(r, normalizeDNSServerAddress(localServer))
resultChan <- struct { resultChan <- struct {
response *dns.Msg response *dns.Msg
rtt time.Duration rtt time.Duration
@@ -2083,7 +2145,7 @@ func (s *Server) updateStats(update func(*Stats)) {
} }
// addQueryLog 添加查询日志 // 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地理位置 // 获取IP地理位置
location := s.getIpGeolocation(clientIP) location := s.getIpGeolocation(clientIP)
@@ -2104,6 +2166,7 @@ func (s *Server) addQueryLog(clientIP, domain, queryType string, responseTime in
DNSServer: dnsServer, DNSServer: dnsServer,
DNSSECServer: dnssecServer, DNSSECServer: dnssecServer,
Answers: answers, Answers: answers,
ResponseCode: responseCode,
} }
// 添加到日志列表 // 添加到日志列表

1831
nohup.out Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -18,9 +18,67 @@
<body class="bg-gray-50 text-dark font-sans"> <body class="bg-gray-50 text-dark font-sans">
<div class="flex h-screen overflow-hidden"> <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"> <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> <i class="fa fa-times text-xl"></i>
</button> </button>
@@ -85,9 +143,9 @@
<div id="sidebar-overlay" class="fixed inset-0 bg-black bg-opacity-50 z-40 hidden md:hidden"></div> <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"> <div class="flex items-center">
<button id="toggle-sidebar" class="block md:hidden text-gray-500 hover:text-gray-700 focus:outline-none"> <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> <i class="fa fa-bars text-xl"></i>
@@ -96,55 +154,6 @@
</div> </div>
<div class="flex items-center space-x-4"> <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"> <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> <i class="fa fa-bell text-lg"></i>
@@ -170,7 +179,7 @@
</header> </header>
<!-- 页面内容 --> <!-- 页面内容 -->
<div class="p-6"> <div class="p-6 overflow-y-auto flex-1">
<!-- 仪表盘部分 --> <!-- 仪表盘部分 -->
<div id="dashboard-content" class="space-y-6"> <div id="dashboard-content" class="space-y-6">
<!-- 统计卡片 --> <!-- 统计卡片 -->
@@ -975,13 +984,12 @@
</div> </div>
</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-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>
</tr> </tr>
</thead> </thead>
<tbody id="logs-table-body"> <tbody id="logs-table-body">
<tr> <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> <i class="fa fa-file-text-o text-4xl mb-2 text-gray-300"></i>
<div>暂无查询日志</div> <div>暂无查询日志</div>
</td> </td>
@@ -1166,7 +1174,6 @@
<script src="js/main.js"></script> <script src="js/main.js"></script>
<script src="js/api.js"></script> <script src="js/api.js"></script>
<script src="js/dashboard.js"></script> <script src="js/dashboard.js"></script>
<script src="js/server-status.js"></script>
<script src="js/shield.js"></script> <script src="js/shield.js"></script>
<script src="js/hosts.js"></script> <script src="js/hosts.js"></script>
<script src="js/query.js"></script> <script src="js/query.js"></script>

View File

@@ -2964,33 +2964,10 @@ window.addEventListener('hashchange', handleHashChange);
// 初始化hash路由 - 确保在页面加载时就能被调用 // 初始化hash路由 - 确保在页面加载时就能被调用
initHashRoute(); initHashRoute();
// 侧边栏切换
function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
sidebar.classList.toggle('-translate-x-full');
}
// 响应式处理 // 响应式处理
function handleResponsive() { 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', () => { window.addEventListener('resize', () => {
updateSidebarState();
// 更新所有图表大小 // 更新所有图表大小
if (dnsRequestsChart) { if (dnsRequestsChart) {

File diff suppressed because it is too large Load Diff

View File

@@ -28,7 +28,7 @@ function setupNavigation() {
// 移动端侧边栏切换 // 移动端侧边栏切换
const toggleSidebar = document.getElementById('toggle-sidebar'); const toggleSidebar = document.getElementById('toggle-sidebar');
const closeSidebarBtn = document.getElementById('close-sidebar'); const closeSidebarBtn = document.getElementById('close-sidebar');
const sidebar = document.getElementById('sidebar'); const sidebar = document.getElementById('mobile-sidebar');
const sidebarOverlay = document.getElementById('sidebar-overlay'); const sidebarOverlay = document.getElementById('sidebar-overlay');
// 打开侧边栏函数 // 打开侧边栏函数

View File

@@ -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
}
}