You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

266 lines
6.8 KiB

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()))
}