package gui import ( "encoding/json" "fmt" "html/template" "net/http" "path/filepath" "runtime" "strings" "github.com/azoic/wormhole-client/internal/config" "github.com/azoic/wormhole-client/internal/proxy" "github.com/azoic/wormhole-client/internal/routing" "github.com/azoic/wormhole-client/internal/system" "github.com/azoic/wormhole-client/pkg/logger" ) // GUIServer Web GUI 服务器 type GUIServer struct { config *config.Config socks5Proxy *proxy.SOCKS5Proxy systemProxyMgr *system.SystemProxyManager routeMatcher *routing.RouteMatcher mode string templates *template.Template } // NewGUIServer 创建 GUI 服务器 func NewGUIServer(cfg *config.Config, socks5Proxy *proxy.SOCKS5Proxy, systemProxyMgr *system.SystemProxyManager, routeMatcher *routing.RouteMatcher, mode string) *GUIServer { gui := &GUIServer{ config: cfg, socks5Proxy: socks5Proxy, systemProxyMgr: systemProxyMgr, routeMatcher: routeMatcher, mode: mode, } // 加载模板 gui.loadTemplates() return gui } // RegisterRoutes 注册 GUI 路由 func (g *GUIServer) RegisterRoutes(mux *http.ServeMux) { // 静态文件路由 mux.HandleFunc("/static/", g.handleStatic) // Web 界面路由 mux.HandleFunc("/gui", g.handleGUI) mux.HandleFunc("/gui/", g.handleGUI) // API 路由 mux.HandleFunc("/api/status", g.handleStatus) mux.HandleFunc("/api/config", g.handleConfig) mux.HandleFunc("/api/proxy/toggle", g.handleProxyToggle) mux.HandleFunc("/api/routing/stats", g.handleRoutingStats) mux.HandleFunc("/api/system/proxy", g.handleSystemProxy) } // handleGUI 处理 GUI 主页面 func (g *GUIServer) handleGUI(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") data := g.getTemplateData() if err := g.templates.Execute(w, data); err != nil { logger.Error("Failed to execute template: %v", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } } // handleStatic 处理静态文件 func (g *GUIServer) handleStatic(w http.ResponseWriter, r *http.Request) { path := strings.TrimPrefix(r.URL.Path, "/static/") var content string var contentType string switch filepath.Ext(path) { case ".css": content = g.getCSS() contentType = "text/css" case ".js": content = g.getJS() contentType = "application/javascript" default: http.NotFound(w, r) return } w.Header().Set("Content-Type", contentType) w.Write([]byte(content)) } // handleStatus 处理状态 API func (g *GUIServer) handleStatus(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var proxyStats interface{} if g.socks5Proxy != nil { proxyStats = g.socks5Proxy.GetStats() } var routingStats interface{} if g.routeMatcher != nil { routingStats = g.routeMatcher.GetStats() } status := map[string]interface{}{ "mode": g.mode, "system_proxy": g.systemProxyMgr.IsEnabled(), "proxy_stats": proxyStats, "routing_stats": routingStats, "server_addr": g.config.GetServerAddr(), "local_port": g.config.Proxy.LocalPort, "os": runtime.GOOS, } g.writeJSON(w, status) } // handleConfig 处理配置 API func (g *GUIServer) handleConfig(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: g.writeJSON(w, g.config) case http.MethodPost: // TODO: 实现配置更新 http.Error(w, "Configuration update not implemented", http.StatusNotImplemented) default: http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) } } // handleProxyToggle 处理代理开关 func (g *GUIServer) handleProxyToggle(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req struct { Enable bool `json:"enable"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid JSON", http.StatusBadRequest) return } if req.Enable && !g.systemProxyMgr.IsEnabled() { // 启用系统代理 httpProxy := fmt.Sprintf("127.0.0.1:%d", g.config.Proxy.LocalPort) if err := g.systemProxyMgr.SetGlobalProxy(httpProxy, httpProxy, ""); err != nil { g.writeJSON(w, map[string]interface{}{ "success": false, "error": err.Error(), }) return } } else if !req.Enable && g.systemProxyMgr.IsEnabled() { // 禁用系统代理 if err := g.systemProxyMgr.RestoreProxy(); err != nil { g.writeJSON(w, map[string]interface{}{ "success": false, "error": err.Error(), }) return } } g.writeJSON(w, map[string]interface{}{ "success": true, "enabled": g.systemProxyMgr.IsEnabled(), }) } // handleRoutingStats 处理路由统计 func (g *GUIServer) handleRoutingStats(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var stats interface{} if g.routeMatcher != nil { stats = g.routeMatcher.GetStats() } g.writeJSON(w, stats) } // handleSystemProxy 处理系统代理信息 func (g *GUIServer) handleSystemProxy(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } currentProxy, err := g.systemProxyMgr.GetCurrentProxy() if err != nil { g.writeJSON(w, map[string]interface{}{ "error": err.Error(), }) return } g.writeJSON(w, map[string]interface{}{ "enabled": g.systemProxyMgr.IsEnabled(), "current_proxy": currentProxy, }) } // writeJSON 写入 JSON 响应 func (g *GUIServer) writeJSON(w http.ResponseWriter, data interface{}) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") if err := json.NewEncoder(w).Encode(data); err != nil { logger.Error("Failed to encode JSON: %v", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) } } // getTemplateData 获取模板数据 func (g *GUIServer) getTemplateData() map[string]interface{} { var proxyStats interface{} if g.socks5Proxy != nil { proxyStats = g.socks5Proxy.GetStats() } var routingStats interface{} if g.routeMatcher != nil { routingStats = g.routeMatcher.GetStats() } return map[string]interface{}{ "Title": "Wormhole SOCKS5 Client", "Mode": g.mode, "ServerAddr": g.config.GetServerAddr(), "LocalPort": g.config.Proxy.LocalPort, "SystemProxy": g.systemProxyMgr.IsEnabled(), "ProxyStats": proxyStats, "RoutingStats": routingStats, "OS": runtime.GOOS, } } // loadTemplates 加载模板 func (g *GUIServer) loadTemplates() { g.templates = template.Must(template.New("index.html").Parse(g.getHTMLTemplate())) }