实现登录功能
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
"port": 53,
|
"port": 5353,
|
||||||
"upstreamDNS": [
|
"upstreamDNS": [
|
||||||
"223.5.5.5:53",
|
"223.5.5.5:53",
|
||||||
"223.6.6.6:53"
|
"223.6.6.6:53"
|
||||||
@@ -12,7 +12,9 @@
|
|||||||
"http": {
|
"http": {
|
||||||
"port": 8080,
|
"port": 8080,
|
||||||
"host": "0.0.0.0",
|
"host": "0.0.0.0",
|
||||||
"enableAPI": true
|
"enableAPI": true,
|
||||||
|
"username": "admin",
|
||||||
|
"password": "admin"
|
||||||
},
|
},
|
||||||
"shield": {
|
"shield": {
|
||||||
"localRulesFile": "data/rules.txt",
|
"localRulesFile": "data/rules.txt",
|
||||||
@@ -98,7 +100,7 @@
|
|||||||
"enabled": true
|
"enabled": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updateInterval": 30,
|
"updateInterval": 3600,
|
||||||
"hostsFile": "data/hosts.txt",
|
"hostsFile": "data/hosts.txt",
|
||||||
"blockMethod": "NXDOMAIN",
|
"blockMethod": "NXDOMAIN",
|
||||||
"customBlockIP": "",
|
"customBlockIP": "",
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ type HTTPConfig struct {
|
|||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
EnableAPI bool `json:"enableAPI"`
|
EnableAPI bool `json:"enableAPI"`
|
||||||
|
Username string `json:"username"` // 登录用户名
|
||||||
|
Password string `json:"password"` // 登录密码
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlacklistEntry 黑名单条目
|
// BlacklistEntry 黑名单条目
|
||||||
@@ -92,6 +94,13 @@ func LoadConfig(path string) (*Config, error) {
|
|||||||
if config.HTTP.Host == "" {
|
if config.HTTP.Host == "" {
|
||||||
config.HTTP.Host = "0.0.0.0"
|
config.HTTP.Host = "0.0.0.0"
|
||||||
}
|
}
|
||||||
|
// 默认用户名和密码,如果未配置则使用admin/admin
|
||||||
|
if config.HTTP.Username == "" {
|
||||||
|
config.HTTP.Username = "admin"
|
||||||
|
}
|
||||||
|
if config.HTTP.Password == "" {
|
||||||
|
config.HTTP.Password = "admin"
|
||||||
|
}
|
||||||
if config.Shield.UpdateInterval == 0 {
|
if config.Shield.UpdateInterval == 0 {
|
||||||
config.Shield.UpdateInterval = 3600
|
config.Shield.UpdateInterval = 3600
|
||||||
}
|
}
|
||||||
|
|||||||
222
http/server.go
222
http/server.go
@@ -26,6 +26,11 @@ type Server struct {
|
|||||||
shieldManager *shield.ShieldManager
|
shieldManager *shield.ShieldManager
|
||||||
server *http.Server
|
server *http.Server
|
||||||
|
|
||||||
|
// 会话管理相关字段
|
||||||
|
sessions map[string]time.Time // 会话ID到过期时间的映射
|
||||||
|
sessionsMutex sync.Mutex // 会话映射的互斥锁
|
||||||
|
sessionTTL time.Duration // 会话过期时间
|
||||||
|
|
||||||
// WebSocket相关字段
|
// WebSocket相关字段
|
||||||
upgrader websocket.Upgrader
|
upgrader websocket.Upgrader
|
||||||
clients map[*websocket.Conn]bool
|
clients map[*websocket.Conn]bool
|
||||||
@@ -50,10 +55,15 @@ func NewServer(globalConfig *config.Config, dnsServer *dns.Server, shieldManager
|
|||||||
},
|
},
|
||||||
clients: make(map[*websocket.Conn]bool),
|
clients: make(map[*websocket.Conn]bool),
|
||||||
broadcastChan: make(chan []byte, 100),
|
broadcastChan: make(chan []byte, 100),
|
||||||
|
// 会话管理初始化
|
||||||
|
sessions: make(map[string]time.Time),
|
||||||
|
sessionTTL: 24 * time.Hour, // 会话有效期24小时
|
||||||
}
|
}
|
||||||
|
|
||||||
// 启动广播协程
|
// 启动广播协程
|
||||||
go server.startBroadcastLoop()
|
go server.startBroadcastLoop()
|
||||||
|
// 启动会话清理协程
|
||||||
|
go server.cleanupSessionsLoop()
|
||||||
|
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
@@ -62,17 +72,26 @@ func NewServer(globalConfig *config.Config, dnsServer *dns.Server, shieldManager
|
|||||||
func (s *Server) Start() error {
|
func (s *Server) Start() error {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
// 登录路由,不需要认证
|
||||||
|
mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// 重定向到登录页面HTML
|
||||||
|
http.Redirect(w, r, "/login.html", http.StatusFound)
|
||||||
|
})
|
||||||
|
|
||||||
// API路由
|
// API路由
|
||||||
if s.config.EnableAPI {
|
if s.config.EnableAPI {
|
||||||
// 重定向/api到Swagger UI页面
|
// 登录API端点,不需要认证
|
||||||
mux.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/api/login", s.handleLogin)
|
||||||
http.Redirect(w, r, "/api/index.html", http.StatusMovedPermanently)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 注册所有API端点
|
// 重定向/api到Swagger UI页面
|
||||||
mux.HandleFunc("/api/stats", s.handleStats)
|
mux.HandleFunc("/api", s.loginRequired(func(w http.ResponseWriter, r *http.Request) {
|
||||||
mux.HandleFunc("/api/shield", s.handleShield)
|
http.Redirect(w, r, "/api/index.html", http.StatusMovedPermanently)
|
||||||
mux.HandleFunc("/api/shield/localrules", func(w http.ResponseWriter, r *http.Request) {
|
}))
|
||||||
|
|
||||||
|
// 注册所有API端点,应用登录中间件
|
||||||
|
mux.HandleFunc("/api/stats", s.loginRequired(s.handleStats))
|
||||||
|
mux.HandleFunc("/api/shield", s.loginRequired(s.handleShield))
|
||||||
|
mux.HandleFunc("/api/shield/localrules", s.loginRequired(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if r.Method == http.MethodGet {
|
if r.Method == http.MethodGet {
|
||||||
localRules := s.shieldManager.GetLocalRules()
|
localRules := s.shieldManager.GetLocalRules()
|
||||||
@@ -80,8 +99,8 @@ func (s *Server) Start() error {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
})
|
}))
|
||||||
mux.HandleFunc("/api/shield/remoterules", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/api/shield/remoterules", s.loginRequired(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if r.Method == http.MethodGet {
|
if r.Method == http.MethodGet {
|
||||||
remoteRules := s.shieldManager.GetRemoteRules()
|
remoteRules := s.shieldManager.GetRemoteRules()
|
||||||
@@ -89,45 +108,57 @@ func (s *Server) Start() error {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
})
|
}))
|
||||||
mux.HandleFunc("/api/shield/hosts", s.handleShieldHosts)
|
mux.HandleFunc("/api/shield/hosts", s.loginRequired(s.handleShieldHosts))
|
||||||
mux.HandleFunc("/api/shield/blacklists", s.handleShieldBlacklists)
|
mux.HandleFunc("/api/shield/blacklists", s.loginRequired(s.handleShieldBlacklists))
|
||||||
mux.HandleFunc("/api/query", s.handleQuery)
|
mux.HandleFunc("/api/query", s.loginRequired(s.handleQuery))
|
||||||
mux.HandleFunc("/api/status", s.handleStatus)
|
mux.HandleFunc("/api/status", s.loginRequired(s.handleStatus))
|
||||||
mux.HandleFunc("/api/config", s.handleConfig)
|
mux.HandleFunc("/api/config", s.loginRequired(s.handleConfig))
|
||||||
mux.HandleFunc("/api/config/restart", s.handleRestart)
|
mux.HandleFunc("/api/config/restart", s.loginRequired(s.handleRestart))
|
||||||
// 添加统计相关接口
|
// 添加统计相关接口
|
||||||
mux.HandleFunc("/api/top-blocked", s.handleTopBlockedDomains)
|
mux.HandleFunc("/api/top-blocked", s.loginRequired(s.handleTopBlockedDomains))
|
||||||
mux.HandleFunc("/api/top-resolved", s.handleTopResolvedDomains)
|
mux.HandleFunc("/api/top-resolved", s.loginRequired(s.handleTopResolvedDomains))
|
||||||
mux.HandleFunc("/api/top-clients", s.handleTopClients)
|
mux.HandleFunc("/api/top-clients", s.loginRequired(s.handleTopClients))
|
||||||
mux.HandleFunc("/api/top-domains", s.handleTopDomains)
|
mux.HandleFunc("/api/top-domains", s.loginRequired(s.handleTopDomains))
|
||||||
mux.HandleFunc("/api/recent-blocked", s.handleRecentBlockedDomains)
|
mux.HandleFunc("/api/recent-blocked", s.loginRequired(s.handleRecentBlockedDomains))
|
||||||
mux.HandleFunc("/api/hourly-stats", s.handleHourlyStats)
|
mux.HandleFunc("/api/hourly-stats", s.loginRequired(s.handleHourlyStats))
|
||||||
mux.HandleFunc("/api/daily-stats", s.handleDailyStats)
|
mux.HandleFunc("/api/daily-stats", s.loginRequired(s.handleDailyStats))
|
||||||
mux.HandleFunc("/api/monthly-stats", s.handleMonthlyStats)
|
mux.HandleFunc("/api/monthly-stats", s.loginRequired(s.handleMonthlyStats))
|
||||||
mux.HandleFunc("/api/query/type", s.handleQueryTypeStats)
|
mux.HandleFunc("/api/query/type", s.loginRequired(s.handleQueryTypeStats))
|
||||||
// 日志统计相关接口
|
// 日志统计相关接口
|
||||||
mux.HandleFunc("/api/logs/stats", s.handleLogsStats)
|
mux.HandleFunc("/api/logs/stats", s.loginRequired(s.handleLogsStats))
|
||||||
mux.HandleFunc("/api/logs/query", s.handleLogsQuery)
|
mux.HandleFunc("/api/logs/query", s.loginRequired(s.handleLogsQuery))
|
||||||
mux.HandleFunc("/api/logs/count", s.handleLogsCount)
|
mux.HandleFunc("/api/logs/count", s.loginRequired(s.handleLogsCount))
|
||||||
// WebSocket端点
|
// WebSocket端点
|
||||||
mux.HandleFunc("/ws/stats", s.handleWebSocketStats)
|
mux.HandleFunc("/ws/stats", s.loginRequired(s.handleWebSocketStats))
|
||||||
|
|
||||||
// 将/api/下的静态文件服务指向static/api目录,放在最后以避免覆盖API端点
|
// 将/api/下的静态文件服务指向static/api目录,放在最后以避免覆盖API端点
|
||||||
apiFileServer := http.FileServer(http.Dir("./static/api"))
|
apiFileServer := http.FileServer(http.Dir("./static/api"))
|
||||||
mux.Handle("/api/", http.StripPrefix("/api", apiFileServer))
|
mux.Handle("/api/", s.loginRequired(http.StripPrefix("/api", apiFileServer).ServeHTTP))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自定义静态文件服务处理器,用于禁用浏览器缓存,放在API路由之后
|
// 自定义静态文件服务处理器,用于禁用浏览器缓存,放在API路由之后
|
||||||
fileServer := http.FileServer(http.Dir("./static"))
|
fileServer := http.FileServer(http.Dir("./static"))
|
||||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
|
// 单独处理login.html,不需要登录
|
||||||
|
mux.HandleFunc("/login.html", 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")
|
||||||
|
// 直接提供login.html文件
|
||||||
|
http.ServeFile(w, r, "./static/login.html")
|
||||||
|
})
|
||||||
|
|
||||||
|
// 其他静态文件需要登录
|
||||||
|
mux.HandleFunc("/", s.loginRequired(func(w http.ResponseWriter, r *http.Request) {
|
||||||
// 添加Cache-Control头,禁用浏览器缓存
|
// 添加Cache-Control头,禁用浏览器缓存
|
||||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
w.Header().Set("Pragma", "no-cache")
|
w.Header().Set("Pragma", "no-cache")
|
||||||
w.Header().Set("Expires", "Thu, 01 Jan 1970 00:00:00 GMT")
|
w.Header().Set("Expires", "Thu, 01 Jan 1970 00:00:00 GMT")
|
||||||
// 使用StripPrefix处理路径
|
// 使用StripPrefix处理路径
|
||||||
http.StripPrefix("/", fileServer).ServeHTTP(w, r)
|
http.StripPrefix("/", fileServer).ServeHTTP(w, r)
|
||||||
})
|
}))
|
||||||
|
|
||||||
s.server = &http.Server{
|
s.server = &http.Server{
|
||||||
Addr: fmt.Sprintf("%s:%d", s.config.Host, s.config.Port),
|
Addr: fmt.Sprintf("%s:%d", s.config.Host, s.config.Port),
|
||||||
@@ -378,6 +409,78 @@ func (s *Server) startBroadcastLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cleanupSessionsLoop 定期清理过期会话
|
||||||
|
func (s *Server) cleanupSessionsLoop() {
|
||||||
|
for {
|
||||||
|
time.Sleep(1 * time.Hour) // 每小时清理一次
|
||||||
|
s.sessionsMutex.Lock()
|
||||||
|
now := time.Now()
|
||||||
|
for sessionID, expiryTime := range s.sessions {
|
||||||
|
if now.After(expiryTime) {
|
||||||
|
delete(s.sessions, sessionID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.sessionsMutex.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAuthenticated 检查用户是否已认证
|
||||||
|
func (s *Server) isAuthenticated(r *http.Request) bool {
|
||||||
|
// 从Cookie中获取会话ID
|
||||||
|
cookie, err := r.Cookie("session_id")
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionID := cookie.Value
|
||||||
|
s.sessionsMutex.Lock()
|
||||||
|
defer s.sessionsMutex.Unlock()
|
||||||
|
|
||||||
|
// 检查会话是否存在且未过期
|
||||||
|
expiryTime, exists := s.sessions[sessionID]
|
||||||
|
if !exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if time.Now().After(expiryTime) {
|
||||||
|
// 会话已过期,删除它
|
||||||
|
delete(s.sessions, sessionID)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 延长会话有效期
|
||||||
|
s.sessions[sessionID] = time.Now().Add(s.sessionTTL)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// loginRequired 登录中间件
|
||||||
|
func (s *Server) loginRequired(next http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// 检查是否为登录页面或登录API,允许直接访问
|
||||||
|
if r.URL.Path == "/login" || r.URL.Path == "/api/login" {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已认证
|
||||||
|
if !s.isAuthenticated(r) {
|
||||||
|
// 如果是API请求,返回401错误
|
||||||
|
if strings.HasPrefix(r.URL.Path, "/api/") || strings.HasPrefix(r.URL.Path, "/ws/") {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
json.NewEncoder(w).Encode(map[string]string{"error": "未授权访问"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 否则重定向到登录页面
|
||||||
|
http.Redirect(w, r, "/login", http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已认证,继续处理请求
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// handleTopBlockedDomains 处理TOP屏蔽域名请求
|
// handleTopBlockedDomains 处理TOP屏蔽域名请求
|
||||||
func (s *Server) handleTopBlockedDomains(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleTopBlockedDomains(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
@@ -1312,3 +1415,56 @@ func (s *Server) handleRestart(w http.ResponseWriter, r *http.Request) {
|
|||||||
json.NewEncoder(w).Encode(map[string]string{"status": "success", "message": "服务已重启"})
|
json.NewEncoder(w).Encode(map[string]string{"status": "success", "message": "服务已重启"})
|
||||||
logger.Info("服务重启成功")
|
logger.Info("服务重启成功")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleLogin 处理登录请求
|
||||||
|
func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析请求体
|
||||||
|
var loginData struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&loginData); err != nil {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
json.NewEncoder(w).Encode(map[string]string{"error": "无效的请求体"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证用户名和密码
|
||||||
|
if loginData.Username != s.config.Username || loginData.Password != s.config.Password {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
json.NewEncoder(w).Encode(map[string]string{"error": "用户名或密码错误"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成会话ID
|
||||||
|
sessionID := fmt.Sprintf("%d_%d", time.Now().UnixNano(), len(s.sessions))
|
||||||
|
|
||||||
|
// 保存会话
|
||||||
|
s.sessionsMutex.Lock()
|
||||||
|
s.sessions[sessionID] = time.Now().Add(s.sessionTTL)
|
||||||
|
s.sessionsMutex.Unlock()
|
||||||
|
|
||||||
|
// 设置Cookie
|
||||||
|
cookie := &http.Cookie{
|
||||||
|
Name: "session_id",
|
||||||
|
Value: sessionID,
|
||||||
|
Path: "/",
|
||||||
|
Expires: time.Now().Add(s.sessionTTL),
|
||||||
|
HttpOnly: true,
|
||||||
|
Secure: false, // 开发环境下使用false,生产环境应使用true
|
||||||
|
}
|
||||||
|
http.SetCookie(w, cookie)
|
||||||
|
|
||||||
|
// 返回成功响应
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(map[string]string{"status": "success", "message": "登录成功"})
|
||||||
|
logger.Info(fmt.Sprintf("用户 %s 登录成功", loginData.Username))
|
||||||
|
}
|
||||||
|
|||||||
6
main.go
6
main.go
@@ -42,9 +42,11 @@ func createDefaultConfig(configFile string) error {
|
|||||||
"saveInterval": 300
|
"saveInterval": 300
|
||||||
},
|
},
|
||||||
"http": {
|
"http": {
|
||||||
"port": 8081,
|
"port": 8080,
|
||||||
"host": "0.0.0.0",
|
"host": "0.0.0.0",
|
||||||
"enableAPI": true
|
"enableAPI": true,
|
||||||
|
"username": "admin",
|
||||||
|
"password": "admin"
|
||||||
},
|
},
|
||||||
"shield": {
|
"shield": {
|
||||||
"localRulesFile": "data/rules.txt",
|
"localRulesFile": "data/rules.txt",
|
||||||
|
|||||||
41
server.log
Normal file
41
server.log
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
2025/11/30 11:09:05 正在创建所需的文件和文件夹...
|
||||||
|
2025/11/30 11:09:05 所需文件和文件夹创建成功
|
||||||
|
time="2025-11-30T11:09:05+08:00" level=debug msg="尝试加载Shield统计数据" file=/root/dnsbak/data/shield_stats.json
|
||||||
|
time="2025-11-30T11:09:05+08:00" level=info msg="Shield计数数据加载成功" blocked_entries=0 resolved_entries=0
|
||||||
|
time="2025-11-30T11:09:05+08:00" level=info msg="从缓存加载远程规则" url="https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/filter.txt"
|
||||||
|
time="2025-11-30T11:09:05+08:00" level=info msg="从缓存加载远程规则" url="https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-Filters/raw/branch/main/hosts/adaway.txt"
|
||||||
|
time="2025-11-30T11:09:06+08:00" level=info msg="从缓存加载远程规则" url="https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-Filters/raw/branch/main/list/easylist.txt"
|
||||||
|
time="2025-11-30T11:09:06+08:00" level=info msg="从缓存加载远程规则" url="https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/rules/costomize.txt"
|
||||||
|
time="2025-11-30T11:09:06+08:00" level=info msg="从缓存加载远程规则" url="http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-Filters/raw/branch/main/hosts/dsjh.txt"
|
||||||
|
time="2025-11-30T11:09:06+08:00" level=info msg="从缓存加载远程规则" url="http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/hate-and-junk-extended.txt"
|
||||||
|
time="2025-11-30T11:09:06+08:00" level=info msg="从缓存加载远程规则" url="http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/hosts/costomize.txt"
|
||||||
|
time="2025-11-30T11:09:06+08:00" level=info msg="从缓存加载远程规则" url="http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/hosts/anti-remoterequests.txt"
|
||||||
|
time="2025-11-30T11:09:07+08:00" level=info msg="从缓存加载远程规则" url="http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/rules/url-based-adguard.txt"
|
||||||
|
time="2025-11-30T11:09:07+08:00" level=info msg="从缓存加载远程规则" url="http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/rules/ads-and-trackers.txt"
|
||||||
|
time="2025-11-30T11:09:08+08:00" level=info msg="从缓存加载远程规则" url="http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/rules/malware.txt"
|
||||||
|
time="2025-11-30T11:09:09+08:00" level=info msg="从缓存加载远程规则" url="http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-Filters/raw/branch/main/hosts/costomize.txt"
|
||||||
|
time="2025-11-30T11:09:09+08:00" level=info msg="从缓存加载远程规则" url="http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-Filters/raw/branch/main/rules/AWAvenue-Ads-Rule.txt"
|
||||||
|
time="2025-11-30T11:09:09+08:00" level=info msg="从缓存加载远程规则" url="https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/rules/cheat.txt"
|
||||||
|
time="2025-11-30T11:09:10+08:00" level=info msg="规则加载完成,域名规则: 189895, 排除规则: 653, 正则规则: 24094, hosts规则: 0"
|
||||||
|
time="2025-11-30T11:09:10+08:00" level=info msg="统计数据加载成功"
|
||||||
|
time="2025-11-30T11:09:10+08:00" level=info msg="查询日志加载成功" count=8608
|
||||||
|
time="2025-11-30T11:09:10+08:00" level=info msg="DNS服务器已启动,监听端口: 5353"
|
||||||
|
time="2025-11-30T11:09:10+08:00" level=info msg="HTTP控制台已启动,监听端口: 8081"
|
||||||
|
time="2025-11-30T11:09:10+08:00" level=info msg="DNS TCP服务器启动,监听端口: 5353"
|
||||||
|
time="2025-11-30T11:09:10+08:00" level=info msg="启动Shield计数数据自动保存功能" file=./data/shield_stats.json interval=60
|
||||||
|
time="2025-11-30T11:09:10+08:00" level=info msg="HTTP控制台服务器启动,监听地址: 0.0.0.0:8081"
|
||||||
|
time="2025-11-30T11:09:10+08:00" level=info msg="规则自动更新已启动" interval=3600
|
||||||
|
time="2025-11-30T11:09:10+08:00" level=info msg="DNS UDP服务器启动,监听端口: 5353"
|
||||||
|
time="2025-11-30T11:09:10+08:00" level=info msg="启动统计数据自动保存功能" file=data/stats.json interval=300
|
||||||
|
time="2025-11-30T11:09:10+08:00" level=error msg="DNS UDP服务器启动失败" error="listen udp :5353: bind: address already in use"
|
||||||
|
time="2025-11-30T11:09:10+08:00" level=info msg="Shield计数数据保存成功" blocked_entries=0 file=/root/dnsbak/data/shield_stats.json resolved_entries=0
|
||||||
|
2025/11/30 11:09:18 正在关闭服务...
|
||||||
|
time="2025-11-30T11:09:18+08:00" level=info msg="统计数据保存成功" file=/root/dnsbak/data/stats.json
|
||||||
|
time="2025-11-30T11:09:18+08:00" level=info msg="查询日志保存成功" file=/root/dnsbak/data/querylog.json
|
||||||
|
time="2025-11-30T11:09:18+08:00" level=info msg="DNS服务器已停止"
|
||||||
|
time="2025-11-30T11:09:18+08:00" level=error msg="HTTP控制台服务器启动失败" error="http: Server closed"
|
||||||
|
time="2025-11-30T11:09:18+08:00" level=info msg="HTTP控制台服务器已停止"
|
||||||
|
time="2025-11-30T11:09:18+08:00" level=info msg="Shield计数数据保存成功" blocked_entries=0 file=/root/dnsbak/data/shield_stats.json resolved_entries=0
|
||||||
|
time="2025-11-30T11:09:18+08:00" level=info msg="规则自动更新已停止"
|
||||||
|
2025/11/30 11:09:18 服务已关闭
|
||||||
|
time="2025-11-30T11:09:18+08:00" level=warning msg="日志系统已关闭"
|
||||||
@@ -38,6 +38,13 @@ async function apiRequest(endpoint, method = 'GET', data = null) {
|
|||||||
// 优化错误响应处理
|
// 优化错误响应处理
|
||||||
console.warn(`API请求失败: ${response.status}`);
|
console.warn(`API请求失败: ${response.status}`);
|
||||||
|
|
||||||
|
// 处理401未授权错误,重定向到登录页面
|
||||||
|
if (response.status === 401) {
|
||||||
|
console.warn('未授权访问,重定向到登录页面');
|
||||||
|
window.location.href = '/login';
|
||||||
|
return { error: '未授权访问' };
|
||||||
|
}
|
||||||
|
|
||||||
// 尝试解析JSON,但如果失败,直接使用原始文本作为错误信息
|
// 尝试解析JSON,但如果失败,直接使用原始文本作为错误信息
|
||||||
try {
|
try {
|
||||||
const errorData = JSON.parse(responseText);
|
const errorData = JSON.parse(responseText);
|
||||||
|
|||||||
186
static/login.html
Normal file
186
static/login.html
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>DNS服务器控制台 - 登录</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-container {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 40px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #2c3e50;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-header p {
|
||||||
|
color: #7f8c8d;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid #e1e5e9;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 16px;
|
||||||
|
transition: border-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #3498db;
|
||||||
|
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
background-color: #3498db;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
background-color: #2980b9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
background-color: #fee;
|
||||||
|
color: #c00;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="login-container">
|
||||||
|
<div class="login-header">
|
||||||
|
<h1>DNS服务器控制台</h1>
|
||||||
|
<p>请输入您的登录凭据</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="error-message" id="errorMessage"></div>
|
||||||
|
|
||||||
|
<form id="loginForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username">用户名</label>
|
||||||
|
<input type="text" id="username" name="username" placeholder="请输入用户名" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">密码</label>
|
||||||
|
<input type="password" id="password" name="password" placeholder="请输入密码" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn" id="loginBtn">登录</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('loginForm').addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const username = document.getElementById('username').value;
|
||||||
|
const password = document.getElementById('password').value;
|
||||||
|
const loginBtn = document.getElementById('loginBtn');
|
||||||
|
const errorMessage = document.getElementById('errorMessage');
|
||||||
|
|
||||||
|
// 显示加载状态
|
||||||
|
loginBtn.textContent = '登录中...';
|
||||||
|
loginBtn.classList.add('loading');
|
||||||
|
errorMessage.style.display = 'none';
|
||||||
|
|
||||||
|
// 发送登录请求
|
||||||
|
fetch('/api/login', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
username: username,
|
||||||
|
password: password
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('登录失败');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'success') {
|
||||||
|
// 登录成功,重定向到主页
|
||||||
|
window.location.href = '/';
|
||||||
|
} else {
|
||||||
|
throw new Error(data.error || '登录失败');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
// 显示错误信息
|
||||||
|
errorMessage.textContent = error.message;
|
||||||
|
errorMessage.style.display = 'block';
|
||||||
|
loginBtn.textContent = '登录';
|
||||||
|
loginBtn.classList.remove('loading');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user