mosdns/coremain/web_ui.go
dengxiongjian 0413ee5d44
Some checks failed
Test mosdns / build (push) Has been cancelled
二次开发
2025-10-16 21:07:48 +08:00

279 lines
8.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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 <https://www.gnu.org/licenses/>.
*/
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)
})
}