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.
265 lines
6.8 KiB
265 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()))
|
|
}
|
|
|