/* * Copyright (C) 2020-2022, IrineSistiana * * This file is part of mosdns. * * mosdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mosdns is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package coremain import ( "io" "io/fs" "net/http" "strings" "github.com/go-chi/chi/v5" "go.uber.org/zap" ) var webUIFS fs.FS // SetWebUIFS 设置 Web UI 文件系统(由 main 包调用) func SetWebUIFS(fsys fs.FS) { webUIFS = fsys } // registerWebUI 注册 Web 管理界面路由(Vue SPA) func (m *Mosdns) registerWebUI() { // 获取 Vue 构建产物 distFS, err := fs.Sub(webUIFS, "web-ui/dist") if err != nil { m.logger.Error("failed to get Vue dist files", zap.Error(err)) return } // 直接处理所有请求 m.webMux.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) { path := r.URL.Path m.logger.Debug("Web UI request", zap.String("path", path)) // API 请求交给 API 路由处理(不应该到这里,但作为保护) if strings.HasPrefix(path, "/api/") { http.NotFound(w, r) return } // 尝试打开文件 trimmedPath := strings.TrimPrefix(path, "/") if trimmedPath == "" { trimmedPath = "index.html" } m.logger.Debug("Trying to open file", zap.String("trimmedPath", trimmedPath)) file, err := distFS.Open(trimmedPath) if err == nil { // 文件存在,直接返回 defer file.Close() stat, _ := file.Stat() if !stat.IsDir() { m.logger.Debug("Serving file", zap.String("path", trimmedPath)) http.ServeContent(w, r, path, stat.ModTime(), file.(io.ReadSeeker)) return } } else { m.logger.Debug("File not found", zap.String("path", trimmedPath), zap.Error(err)) } // 文件不存在或是目录,返回 index.html (SPA 路由) m.logger.Debug("Serving index.html for SPA") m.serveVueIndex(w, r, distFS) }) } // serveVueIndex 提供 Vue SPA 的 index.html func (m *Mosdns) serveVueIndex(w http.ResponseWriter, r *http.Request, distFS fs.FS) { // 读取 index.html indexHTML, err := fs.ReadFile(distFS, "index.html") if err != nil { m.logger.Error("failed to read Vue index.html", zap.Error(err)) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } // 设置响应头 w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") w.Header().Set("Pragma", "no-cache") w.Header().Set("Expires", "0") // 返回 HTML 内容 w.Write(indexHTML) } // registerWebAPI 注册 Web 管理 API 路由(使用独立的 webMux) func (m *Mosdns) registerWebAPI() { // Web API 路由组(在 Web UI 服务器上提供 API 代理) m.webMux.Route("/api", func(r chi.Router) { // 在路由组内部添加 CORS 中间件 r.Use(corsMiddleware) // 服务器信息和状态 r.Get("/server/info", m.handleWebServerInfo) r.Get("/server/status", m.handleWebServerStatus) // 配置管理 r.Get("/config", m.handleWebGetConfig) r.Put("/config", m.handleWebUpdateConfig) r.Post("/config/reload", m.handleWebReloadConfig) r.Post("/config/validate", m.handleWebValidateConfig) r.Post("/config/backup", m.handleWebBackupConfig) // 域名文件管理 r.Get("/domain-files", m.handleWebListDomainFiles) r.Get("/domain-files/{filename}", m.handleWebGetDomainFile) r.Put("/domain-files/{filename}", m.handleWebUpdateDomainFile) r.Delete("/domain-files/{filename}", m.handleWebDeleteDomainFile) // 插件管理 r.Get("/plugins", m.handleWebListPlugins) r.Get("/plugins/{tag}/status", m.handleWebPluginStatus) // 统计信息 r.Get("/stats/detailed", m.handleWebDetailedStats) // 日志管理 r.Get("/logs", m.handleWebGetLogs) r.Post("/logs/clear", m.handleWebClearLogs) // 缓存管理 r.Post("/cache/flush", m.handleWebFlushCache) // MikroTik 管理(旧版) r.Get("/mikrotik/list", m.handleListMikroTik) r.Post("/mikrotik/add", m.handleAddMikroTik) r.Delete("/mikrotik/{tag}", m.handleDeleteMikroTik) // 域名路由规则管理(新版) r.Get("/rules", m.handleListRules) r.Get("/rules/{name}", m.handleGetRule) r.Post("/rules", m.handleAddRule) r.Put("/rules/{name}", m.handleUpdateRule) r.Delete("/rules/{name}", m.handleDeleteRule) // 系统操作 r.Post("/system/restart", m.handleSystemRestart) }) } // Web API 处理函数的简化版本,复用现有的管理 API 逻辑 func (m *Mosdns) handleWebServerInfo(w http.ResponseWriter, r *http.Request) { m.handleServerInfo(w, r) } func (m *Mosdns) handleWebServerStatus(w http.ResponseWriter, r *http.Request) { m.handleServerStatus(w, r) } func (m *Mosdns) handleWebGetConfig(w http.ResponseWriter, r *http.Request) { m.handleGetConfig(w, r) } func (m *Mosdns) handleWebUpdateConfig(w http.ResponseWriter, r *http.Request) { m.handleUpdateConfig(w, r) } func (m *Mosdns) handleWebReloadConfig(w http.ResponseWriter, r *http.Request) { m.handleReloadConfig(w, r) } func (m *Mosdns) handleWebValidateConfig(w http.ResponseWriter, r *http.Request) { m.handleValidateConfig(w, r) } func (m *Mosdns) handleWebBackupConfig(w http.ResponseWriter, r *http.Request) { m.handleBackupConfig(w, r) } func (m *Mosdns) handleWebListDomainFiles(w http.ResponseWriter, r *http.Request) { m.handleListDomainFiles(w, r) } func (m *Mosdns) handleWebGetDomainFile(w http.ResponseWriter, r *http.Request) { m.handleGetDomainFile(w, r) } func (m *Mosdns) handleWebUpdateDomainFile(w http.ResponseWriter, r *http.Request) { m.handleUpdateDomainFile(w, r) } func (m *Mosdns) handleWebDeleteDomainFile(w http.ResponseWriter, r *http.Request) { m.handleDeleteDomainFile(w, r) } func (m *Mosdns) handleWebListPlugins(w http.ResponseWriter, r *http.Request) { m.handleListPlugins(w, r) } func (m *Mosdns) handleWebPluginStatus(w http.ResponseWriter, r *http.Request) { m.handlePluginStatus(w, r) } func (m *Mosdns) handleWebDetailedStats(w http.ResponseWriter, r *http.Request) { // 返回详细统计信息 stats := map[string]interface{}{ "totalQueries": 0, "successfulQueries": 0, "failedQueries": 0, "cacheHits": 0, "cacheMisses": 0, "avgResponseTime": 0, } m.writeJSONResponse(w, APIResponse{ Success: true, Data: stats, Message: "统计信息获取成功", }) } func (m *Mosdns) handleWebGetLogs(w http.ResponseWriter, r *http.Request) { // 返回日志内容(这里可以根据实际需求实现) logs := map[string]interface{}{ "content": "MosDNS 日志内容将在这里显示...\n[INFO] MosDNS 服务已启动\n[INFO] 配置文件加载成功\n[INFO] 所有插件已加载完成", } m.writeJSONResponse(w, APIResponse{ Success: true, Data: logs, Message: "日志获取成功", }) } func (m *Mosdns) handleWebClearLogs(w http.ResponseWriter, r *http.Request) { // 清空日志(这里可以根据实际需求实现) m.writeJSONResponse(w, APIResponse{ Success: true, Message: "日志清空成功", }) } func (m *Mosdns) handleWebFlushCache(w http.ResponseWriter, r *http.Request) { // 清空缓存(这里可以根据实际需求实现) m.writeJSONResponse(w, APIResponse{ Success: true, Message: "缓存清空成功", }) } // corsMiddleware CORS 中间件,允许跨域请求 func corsMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 设置 CORS 头 w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With") w.Header().Set("Access-Control-Max-Age", "86400") // 处理 OPTIONS 预检请求 if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } // 继续处理请求 next.ServeHTTP(w, r) }) }