移除服务器代码中对于客户端IP地址数据的获取
This commit is contained in:
270
dns/server.go
270
dns/server.go
@@ -6,7 +6,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -49,12 +48,7 @@ type ClientStats struct {
|
|||||||
LastSeen time.Time
|
LastSeen time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPGeolocation IP地理位置信息
|
|
||||||
type IPGeolocation struct {
|
|
||||||
Country string `json:"country"` // 国家
|
|
||||||
City string `json:"city"` // 城市
|
|
||||||
Expiry time.Time `json:"expiry"` // 缓存过期时间
|
|
||||||
}
|
|
||||||
|
|
||||||
// DNSAnswer DNS解析记录
|
// DNSAnswer DNS解析记录
|
||||||
type DNSAnswer struct {
|
type DNSAnswer struct {
|
||||||
@@ -137,10 +131,7 @@ type Server struct {
|
|||||||
stopped bool // 服务器是否已经停止
|
stopped bool // 服务器是否已经停止
|
||||||
stoppedMutex sync.Mutex // 保护stopped标志的互斥锁
|
stoppedMutex sync.Mutex // 保护stopped标志的互斥锁
|
||||||
|
|
||||||
// IP地理位置缓存
|
|
||||||
ipGeolocationCache map[string]*IPGeolocation // IP地址到地理位置的映射
|
|
||||||
ipGeolocationCacheMutex sync.RWMutex // 保护IP地理位置缓存的互斥锁
|
|
||||||
ipGeolocationCacheTTL time.Duration // 缓存有效期
|
|
||||||
|
|
||||||
// DNS查询缓存
|
// DNS查询缓存
|
||||||
DnsCache *DNSCache // DNS响应缓存
|
DnsCache *DNSCache // DNS响应缓存
|
||||||
@@ -214,9 +205,7 @@ func NewServer(config *config.DNSConfig, shieldConfig *config.ShieldConfig, shie
|
|||||||
maxQueryLogs: 10000, // 最大保存10000条日志
|
maxQueryLogs: 10000, // 最大保存10000条日志
|
||||||
saveDone: make(chan struct{}),
|
saveDone: make(chan struct{}),
|
||||||
stopped: false, // 初始化为未停止状态
|
stopped: false, // 初始化为未停止状态
|
||||||
// IP地理位置缓存初始化
|
|
||||||
ipGeolocationCache: make(map[string]*IPGeolocation),
|
|
||||||
ipGeolocationCacheTTL: 24 * time.Hour, // 缓存有效期24小时
|
|
||||||
// DNS查询缓存初始化
|
// DNS查询缓存初始化
|
||||||
DnsCache: NewDNSCache(cacheTTL),
|
DnsCache: NewDNSCache(cacheTTL),
|
||||||
// 初始化域名DNSSEC状态映射表
|
// 初始化域名DNSSEC状态映射表
|
||||||
@@ -269,8 +258,7 @@ func (s *Server) Start() error {
|
|||||||
// 启动自动保存功能
|
// 启动自动保存功能
|
||||||
go s.startAutoSave()
|
go s.startAutoSave()
|
||||||
|
|
||||||
// 启动IP地理位置缓存清理协程
|
|
||||||
go s.startIPGeolocationCacheCleanup()
|
|
||||||
|
|
||||||
// 启动UDP服务
|
// 启动UDP服务
|
||||||
go func() {
|
go func() {
|
||||||
@@ -1136,147 +1124,7 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg
|
|||||||
logger.Debug("合并所有响应返回", "domain", domain, "responseCount", len(validResponses))
|
logger.Debug("合并所有响应返回", "domain", domain, "responseCount", len(validResponses))
|
||||||
}
|
}
|
||||||
|
|
||||||
case "loadbalance":
|
|
||||||
// 负载均衡模式 - 使用加权随机选择算法
|
|
||||||
// 1. 尝试所有可用的服务器,直到找到一个能正常工作的
|
|
||||||
var triedServers []string
|
|
||||||
for len(triedServers) < len(selectedUpstreamDNS) {
|
|
||||||
// 从剩余的服务器中选择一个加权随机服务器
|
|
||||||
var availableServers []string
|
|
||||||
for _, server := range selectedUpstreamDNS {
|
|
||||||
found := false
|
|
||||||
for _, tried := range triedServers {
|
|
||||||
if server == tried {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
availableServers = append(availableServers, server)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedServer := s.selectWeightedRandomServer(availableServers)
|
|
||||||
if selectedServer == "" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
triedServers = append(triedServers, selectedServer)
|
|
||||||
logger.Debug("在负载均衡模式下选择服务器", "domain", domain, "server", selectedServer, "triedServers", triedServers)
|
|
||||||
|
|
||||||
// 使用带超时的方式执行Exchange
|
|
||||||
resultChan := make(chan struct {
|
|
||||||
response *dns.Msg
|
|
||||||
rtt time.Duration
|
|
||||||
err error
|
|
||||||
}, 1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
response, rtt, err := s.resolver.Exchange(r, normalizeDNSServerAddress(selectedServer))
|
|
||||||
resultChan <- struct {
|
|
||||||
response *dns.Msg
|
|
||||||
rtt time.Duration
|
|
||||||
err error
|
|
||||||
}{response, rtt, err}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var response *dns.Msg
|
|
||||||
var rtt time.Duration
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// 直接获取结果,不使用上下文超时
|
|
||||||
result := <-resultChan
|
|
||||||
response, rtt, err = result.response, result.rtt, result.err
|
|
||||||
|
|
||||||
if err == nil && response != nil {
|
|
||||||
// 更新服务器统计信息
|
|
||||||
s.updateServerStats(selectedServer, true, rtt)
|
|
||||||
|
|
||||||
// 检查是否包含DNSSEC记录
|
|
||||||
containsDNSSEC := s.hasDNSSECRecords(response)
|
|
||||||
|
|
||||||
// 如果启用了DNSSEC且响应包含DNSSEC记录,验证DNSSEC签名
|
|
||||||
// 但如果域名匹配不验证DNSSEC的模式,则跳过验证
|
|
||||||
if s.config.EnableDNSSEC && containsDNSSEC && !noDNSSEC {
|
|
||||||
// 验证DNSSEC记录
|
|
||||||
signatureValid := s.verifyDNSSEC(response)
|
|
||||||
|
|
||||||
// 设置AD标志(Authenticated Data)
|
|
||||||
response.AuthenticatedData = signatureValid
|
|
||||||
|
|
||||||
if signatureValid {
|
|
||||||
// 更新DNSSEC验证成功计数
|
|
||||||
s.updateStats(func(stats *Stats) {
|
|
||||||
stats.DNSSECQueries++
|
|
||||||
stats.DNSSECSuccess++
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// 更新DNSSEC验证失败计数
|
|
||||||
s.updateStats(func(stats *Stats) {
|
|
||||||
stats.DNSSECQueries++
|
|
||||||
stats.DNSSECFailed++
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else if noDNSSEC {
|
|
||||||
// 对于不验证DNSSEC的域名,始终设置AD标志为false
|
|
||||||
response.AuthenticatedData = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果响应成功或为NXDOMAIN,根据DNSSEC状态选择最佳响应
|
|
||||||
if response.Rcode == dns.RcodeSuccess || response.Rcode == dns.RcodeNameError {
|
|
||||||
if response.Rcode == dns.RcodeSuccess {
|
|
||||||
// 优先选择带有DNSSEC记录的响应
|
|
||||||
if containsDNSSEC {
|
|
||||||
bestResponse = response
|
|
||||||
bestRtt = rtt
|
|
||||||
hasBestResponse = true
|
|
||||||
hasDNSSECResponse = true
|
|
||||||
usedDNSServer = selectedServer
|
|
||||||
// 如果当前使用的服务器是DNSSEC专用服务器,同时设置usedDNSSECServer
|
|
||||||
for _, dnssecServer := range dnssecServers {
|
|
||||||
if dnssecServer == selectedServer {
|
|
||||||
usedDNSSECServer = selectedServer
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.Debug("找到带DNSSEC的最佳响应", "domain", domain, "server", selectedServer, "rtt", rtt)
|
|
||||||
} else {
|
|
||||||
// 没有带DNSSEC的响应时,保存成功响应
|
|
||||||
bestResponse = response
|
|
||||||
bestRtt = rtt
|
|
||||||
hasBestResponse = true
|
|
||||||
usedDNSServer = selectedServer
|
|
||||||
// 如果当前使用的服务器是DNSSEC专用服务器,同时设置usedDNSSECServer
|
|
||||||
for _, dnssecServer := range dnssecServers {
|
|
||||||
if dnssecServer == selectedServer {
|
|
||||||
usedDNSSECServer = selectedServer
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.Debug("找到最佳响应", "domain", domain, "server", selectedServer, "rtt", rtt)
|
|
||||||
}
|
|
||||||
} else if response.Rcode == dns.RcodeNameError {
|
|
||||||
// 处理NXDOMAIN响应
|
|
||||||
bestResponse = response
|
|
||||||
bestRtt = rtt
|
|
||||||
hasBestResponse = true
|
|
||||||
usedDNSServer = selectedServer
|
|
||||||
logger.Debug("找到NXDOMAIN响应", "domain", domain, "server", selectedServer, "rtt", rtt)
|
|
||||||
}
|
|
||||||
// 保存为备选响应
|
|
||||||
if !hasBackup {
|
|
||||||
backupResponse = response
|
|
||||||
backupRtt = rtt
|
|
||||||
hasBackup = true
|
|
||||||
}
|
|
||||||
break // 找到有效响应,退出循环
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 更新服务器统计信息(失败)
|
|
||||||
s.updateServerStats(selectedServer, false, 0)
|
|
||||||
logger.Debug("服务器请求失败,尝试下一个", "domain", domain, "server", selectedServer, "error", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "fastest-ip":
|
case "fastest-ip":
|
||||||
// 最快的IP地址模式 - 使用TCP连接速度测量选择最快服务器
|
// 最快的IP地址模式 - 使用TCP连接速度测量选择最快服务器
|
||||||
@@ -2403,14 +2251,11 @@ 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, responseCode int) {
|
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)
|
|
||||||
|
|
||||||
// 创建日志记录
|
// 创建日志记录
|
||||||
log := QueryLog{
|
log := QueryLog{
|
||||||
Timestamp: time.Now(),
|
Timestamp: time.Now(),
|
||||||
ClientIP: clientIP,
|
ClientIP: clientIP,
|
||||||
Location: location,
|
Location: "", // 客户端IP地理位置由前端处理
|
||||||
Domain: domain,
|
Domain: domain,
|
||||||
QueryType: queryType,
|
QueryType: queryType,
|
||||||
ResponseTime: responseTime,
|
ResponseTime: responseTime,
|
||||||
@@ -2799,82 +2644,7 @@ func isPrivateIP(ip string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// getIpGeolocation 获取IP地址的地理位置信息
|
|
||||||
func (s *Server) getIpGeolocation(ip string) string {
|
|
||||||
// 检查IP是否为本地或内网地址
|
|
||||||
if isPrivateIP(ip) {
|
|
||||||
return "内网 内网"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 先检查缓存
|
|
||||||
s.ipGeolocationCacheMutex.RLock()
|
|
||||||
geo, exists := s.ipGeolocationCache[ip]
|
|
||||||
s.ipGeolocationCacheMutex.RUnlock()
|
|
||||||
|
|
||||||
// 如果缓存存在且未过期,直接返回
|
|
||||||
if exists && time.Now().Before(geo.Expiry) {
|
|
||||||
return fmt.Sprintf("%s %s", geo.Country, geo.City)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 缓存不存在或已过期,从API获取
|
|
||||||
geoInfo, err := s.fetchIpGeolocationFromAPI(ip)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("获取IP地理位置失败", "ip", ip, "error", err)
|
|
||||||
return "未知 未知"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存到缓存
|
|
||||||
s.ipGeolocationCacheMutex.Lock()
|
|
||||||
s.ipGeolocationCache[ip] = &IPGeolocation{
|
|
||||||
Country: geoInfo["country"].(string),
|
|
||||||
City: geoInfo["city"].(string),
|
|
||||||
Expiry: time.Now().Add(s.ipGeolocationCacheTTL),
|
|
||||||
}
|
|
||||||
s.ipGeolocationCacheMutex.Unlock()
|
|
||||||
|
|
||||||
// 返回格式化的地理位置
|
|
||||||
return fmt.Sprintf("%s %s", geoInfo["country"].(string), geoInfo["city"].(string))
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetchIpGeolocationFromAPI 从第三方API获取IP地理位置信息
|
|
||||||
func (s *Server) fetchIpGeolocationFromAPI(ip string) (map[string]interface{}, error) {
|
|
||||||
// 使用ip-api.com获取IP地理位置信息
|
|
||||||
url := fmt.Sprintf("http://ip-api.com/json/%s?fields=country,city", ip)
|
|
||||||
resp, err := http.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
// 读取响应内容
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析JSON响应
|
|
||||||
var result map[string]interface{}
|
|
||||||
err = json.Unmarshal(body, &result)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查API返回状态
|
|
||||||
status, ok := result["status"].(string)
|
|
||||||
if !ok || status != "success" {
|
|
||||||
return nil, fmt.Errorf("API返回错误状态: %v", result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确保国家和城市字段存在
|
|
||||||
if _, ok := result["country"]; !ok {
|
|
||||||
result["country"] = "未知"
|
|
||||||
}
|
|
||||||
if _, ok := result["city"]; !ok {
|
|
||||||
result["city"] = "未知"
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadStatsData 从文件加载统计数据
|
// loadStatsData 从文件加载统计数据
|
||||||
func (s *Server) loadStatsData() {
|
func (s *Server) loadStatsData() {
|
||||||
@@ -3138,39 +2908,9 @@ func (s *Server) startCpuUsageMonitor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// startIPGeolocationCacheCleanup 启动IP地理位置缓存清理协程
|
|
||||||
func (s *Server) startIPGeolocationCacheCleanup() {
|
|
||||||
ticker := time.NewTicker(time.Hour) // 每小时清理一次
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ticker.C:
|
|
||||||
s.cleanupExpiredIPGeolocationCache()
|
|
||||||
case <-s.ctx.Done():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanupExpiredIPGeolocationCache 清理过期的IP地理位置缓存
|
|
||||||
func (s *Server) cleanupExpiredIPGeolocationCache() {
|
|
||||||
now := time.Now()
|
|
||||||
s.ipGeolocationCacheMutex.Lock()
|
|
||||||
defer s.ipGeolocationCacheMutex.Unlock()
|
|
||||||
|
|
||||||
var deletedCount int
|
|
||||||
for ip, geo := range s.ipGeolocationCache {
|
|
||||||
if now.After(geo.Expiry) {
|
|
||||||
delete(s.ipGeolocationCache, ip)
|
|
||||||
deletedCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if deletedCount > 0 {
|
|
||||||
logger.Info("清理过期的IP地理位置缓存", "deleted", deletedCount, "remaining", len(s.ipGeolocationCache))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getSystemCpuUsage 获取系统CPU使用率
|
// getSystemCpuUsage 获取系统CPU使用率
|
||||||
func getSystemCpuUsage(prevIdle, prevTotal *uint64) (float64, error) {
|
func getSystemCpuUsage(prevIdle, prevTotal *uint64) (float64, error) {
|
||||||
|
|||||||
@@ -14,6 +14,103 @@ 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小时
|
||||||
|
|
||||||
|
// 获取IP地理位置信息
|
||||||
|
async function getIpGeolocation(ip) {
|
||||||
|
// 检查是否为内网IP
|
||||||
|
if (isPrivateIP(ip)) {
|
||||||
|
return "内网 内网";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查缓存
|
||||||
|
const now = Date.now();
|
||||||
|
if (ipGeolocationCache[ip] && (now - ipGeolocationCache[ip].timestamp) < GEOLOCATION_CACHE_EXPIRY) {
|
||||||
|
return ipGeolocationCache[ip].location;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 使用whois.pconline.com.cn API获取IP地理位置
|
||||||
|
const url = `https://whois.pconline.com.cn/ipJson.jsp?ip=${ip}&json=true`;
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析响应数据
|
||||||
|
const data = await response.json();
|
||||||
|
let location = "未知 未知";
|
||||||
|
|
||||||
|
if (data && data.country && data.city) {
|
||||||
|
location = `${data.country} ${data.city}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存到缓存
|
||||||
|
ipGeolocationCache[ip] = {
|
||||||
|
location: location,
|
||||||
|
timestamp: now
|
||||||
|
};
|
||||||
|
|
||||||
|
return location;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取IP地理位置失败:', error);
|
||||||
|
return "未知 未知";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否为内网IP
|
||||||
|
function isPrivateIP(ip) {
|
||||||
|
const parts = ip.split('.');
|
||||||
|
|
||||||
|
// 检查IPv4内网地址
|
||||||
|
if (parts.length === 4) {
|
||||||
|
const first = parseInt(parts[0]);
|
||||||
|
const second = parseInt(parts[1]);
|
||||||
|
|
||||||
|
// 10.0.0.0/8
|
||||||
|
if (first === 10) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 172.16.0.0/12
|
||||||
|
if (first === 172 && second >= 16 && second <= 31) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 192.168.0.0/16
|
||||||
|
if (first === 192 && second === 168) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 127.0.0.0/8 (localhost)
|
||||||
|
if (first === 127) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 169.254.0.0/16 (link-local)
|
||||||
|
if (first === 169 && second === 254) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查IPv6内网地址
|
||||||
|
if (ip.includes(':')) {
|
||||||
|
// ::1/128 (localhost)
|
||||||
|
if (ip === '::1' || ip.startsWith('0:0:0:0:0:0:0:1')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// fc00::/7 (unique local address)
|
||||||
|
if (ip.startsWith('fc') || ip.startsWith('fd')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// fe80::/10 (link-local)
|
||||||
|
if (ip.startsWith('fe80:')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// 跟踪器数据库缓存
|
// 跟踪器数据库缓存
|
||||||
let trackersDatabase = null;
|
let trackersDatabase = null;
|
||||||
let trackersLoaded = false;
|
let trackersLoaded = false;
|
||||||
@@ -838,35 +935,44 @@ async function updateLogsTable(logs) {
|
|||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
// 桌面设备显示完整信息
|
// 桌面设备显示完整信息
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<td class="py-3 px-4">
|
<td class="py-3 px-4">
|
||||||
<div class="text-sm font-medium">${formattedTime}</div>
|
<div class="text-sm font-medium">${formattedTime}</div>
|
||||||
<div class="text-xs text-gray-500 mt-1">${formattedDate}</div>
|
<div class="text-xs text-gray-500 mt-1">${formattedDate}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="py-3 px-4 text-sm">
|
<td class="py-3 px-4 text-sm">
|
||||||
<div class="font-medium">${log.clientIP}</div>
|
<div class="font-medium">${log.clientIP}</div>
|
||||||
<div class="text-xs text-gray-500 mt-1">${log.location || '未知 未知'}</div>
|
<div class="text-xs text-gray-500 mt-1 location-${log.clientIP.replace(/[.:]/g, '-')}">${log.location || '未知 未知'}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="py-3 px-4 text-sm">
|
<td class="py-3 px-4 text-sm">
|
||||||
<div class="font-medium flex items-center relative">
|
<div class="font-medium flex items-center relative">
|
||||||
${log.dnssec ? '<i class="fa fa-lock text-green-500 mr-1" title="DNSSEC已启用"></i>' : ''}
|
${log.dnssec ? '<i class="fa fa-lock text-green-500 mr-1" title="DNSSEC已启用"></i>' : ''}
|
||||||
<div class="tracker-icon-container relative">
|
<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>'}
|
${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}
|
${trackerTooltip}
|
||||||
</div>
|
</div>
|
||||||
${log.domain}
|
${log.domain}
|
||||||
</div>
|
</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">类型: ${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>
|
<div class="text-xs text-gray-500 mt-1">DNS 服务器: ${log.dnsServer || '无'}, DNSSEC专用: ${log.dnssecServer || '无'}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="py-3 px-4 text-sm">${log.responseTime}ms</td>
|
<td class="py-3 px-4 text-sm">${log.responseTime}ms</td>
|
||||||
<td class="py-3 px-4 text-sm text-center">
|
<td class="py-3 px-4 text-sm text-center">
|
||||||
${isBlocked ?
|
${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="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>`
|
`<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>
|
</td>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// 更新IP地理位置信息
|
||||||
|
const locationElement = row.querySelector(`.location-${log.clientIP.replace(/[.:]/g, '-')}`);
|
||||||
|
if (locationElement) {
|
||||||
|
// 调用getIpGeolocation函数获取地理位置
|
||||||
|
getIpGeolocation(log.clientIP).then(location => {
|
||||||
|
locationElement.textContent = location;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加跟踪器图标悬停事件
|
// 添加跟踪器图标悬停事件
|
||||||
@@ -1612,16 +1718,29 @@ async function showLogDetailModal(log) {
|
|||||||
clientDetailsTitle.className = 'text-sm font-medium text-gray-700 uppercase tracking-wider';
|
clientDetailsTitle.className = 'text-sm font-medium text-gray-700 uppercase tracking-wider';
|
||||||
clientDetailsTitle.textContent = '客户端详情';
|
clientDetailsTitle.textContent = '客户端详情';
|
||||||
|
|
||||||
|
// 创建客户端IP容器,为后续更新地理位置做准备
|
||||||
|
const clientIPContainer = document.createElement('div');
|
||||||
|
clientIPContainer.className = 'text-sm font-medium text-gray-900';
|
||||||
|
clientIPContainer.innerHTML = `${clientIP} <span id="modal-location-${clientIP.replace(/[.:]/g, '-')}">(${location})</span>`;
|
||||||
|
|
||||||
const clientIPDiv = document.createElement('div');
|
const clientIPDiv = document.createElement('div');
|
||||||
clientIPDiv.className = 'space-y-1';
|
clientIPDiv.className = 'space-y-1';
|
||||||
clientIPDiv.innerHTML = `
|
clientIPDiv.innerHTML = `
|
||||||
<div class="text-xs text-gray-500">IP地址</div>
|
<div class="text-xs text-gray-500">IP地址</div>
|
||||||
<div class="text-sm font-medium text-gray-900">${clientIP} (${location})</div>
|
|
||||||
`;
|
`;
|
||||||
|
clientIPDiv.appendChild(clientIPContainer);
|
||||||
|
|
||||||
clientDetails.appendChild(clientDetailsTitle);
|
clientDetails.appendChild(clientDetailsTitle);
|
||||||
clientDetails.appendChild(clientIPDiv);
|
clientDetails.appendChild(clientIPDiv);
|
||||||
|
|
||||||
|
// 动态更新地理位置信息
|
||||||
|
const locationElement = clientIPDiv.querySelector(`#modal-location-${clientIP.replace(/[.:]/g, '-')}`);
|
||||||
|
if (locationElement) {
|
||||||
|
getIpGeolocation(clientIP).then(location => {
|
||||||
|
locationElement.textContent = `(${location})`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 操作按钮区域
|
// 操作按钮区域
|
||||||
const actionButtons = document.createElement('div');
|
const actionButtons = document.createElement('div');
|
||||||
actionButtons.className = 'pt-4 border-t border-gray-200 flex justify-end space-x-2';
|
actionButtons.className = 'pt-4 border-t border-gray-200 flex justify-end space-x-2';
|
||||||
|
|||||||
Reference in New Issue
Block a user