package gui import ( "encoding/json" "fmt" "net/http" "time" "vpn-client/internal/config" "vpn-client/internal/subscription" "vpn-client/internal/vless" "vpn-client/internal/vpn" "vpn-client/internal/wireguard" ) // handleIndex обрабатывает главную страницу func handleIndex(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") fmt.Fprintf(w, ` VPN Client

VPN Client GUI

GUI интерфейс в разработке. Используйте CLI режим: vpn-client-gui.exe --cli

`) } // Запуск HTTP сервера func startServer() string { addr := "127.0.0.1:8765" http.HandleFunc("/", handleIndex) http.HandleFunc("/api/status", handleStatus) http.HandleFunc("/api/wireguard/list", handleWireGuardList) http.HandleFunc("/api/wireguard/add", handleWireGuardAdd) http.HandleFunc("/api/wireguard/delete", handleWireGuardDelete) http.HandleFunc("/api/wireguard/connect", handleWireGuardConnect) http.HandleFunc("/api/vless/list", handleVLESSList) http.HandleFunc("/api/vless/add", handleVLESSAdd) http.HandleFunc("/api/vless/delete", handleVLESSDelete) http.HandleFunc("/api/vless/connect", handleVLESSConnect) http.HandleFunc("/api/vless/test", handleVLESSTest) http.HandleFunc("/api/subscriptions/list", handleSubscriptionsList) http.HandleFunc("/api/subscriptions/add", handleSubscriptionsAdd) http.HandleFunc("/api/subscriptions/delete", handleSubscriptionsDelete) http.HandleFunc("/api/subscriptions/update", handleSubscriptionsUpdate) http.HandleFunc("/api/subscriptions/show", handleSubscriptionsShow) http.HandleFunc("/api/subscriptions/test", handleSubscriptionsTest) http.HandleFunc("/api/disconnect", handleDisconnect) go http.ListenAndServe(addr, nil) return "http://" + addr } // API handlers func handleStatus(w http.ResponseWriter, r *http.Request) { state, err := vpn.GetStatus() response := map[string]interface{}{ "connected": false, "info": "", } if err == nil && state.Connected { response["connected"] = true info := fmt.Sprintf("Конфиг: %s | Тип: %s", state.ConfigName, state.ConfigType) if state.StartTime != "" { startTime, err := time.Parse(time.RFC3339, state.StartTime) if err == nil { duration := time.Since(startTime) hours := int(duration.Hours()) minutes := int(duration.Minutes()) % 60 seconds := int(duration.Seconds()) % 60 info += fmt.Sprintf(" | Время: %02d:%02d:%02d", hours, minutes, seconds) } } if state.ConfigType == "vless" { info += " | Прокси: 127.0.0.1:10808" } else if state.ConfigType == "wireguard" { stats, err := wireguard.GetStats(state.Interface) if err == nil { info += fmt.Sprintf(" | ↓%s ↑%s", stats["rx"], stats["tx"]) } } response["info"] = info } json.NewEncoder(w).Encode(response) } func handleWireGuardList(w http.ResponseWriter, r *http.Request) { configs, _ := config.LoadConfigs() json.NewEncoder(w).Encode(configs.WireGuard) } func handleWireGuardAdd(w http.ResponseWriter, r *http.Request) { var req struct { Name string `json:"name"` Config string `json:"config"` } json.NewDecoder(r.Body).Decode(&req) err := wireguard.AddConfig(req.Name, req.Config) json.NewEncoder(w).Encode(map[string]interface{}{ "success": err == nil, "message": func() string { if err != nil { return err.Error() } return "Конфиг добавлен" }(), }) } func handleWireGuardDelete(w http.ResponseWriter, r *http.Request) { var req struct { Index int `json:"index"` } json.NewDecoder(r.Body).Decode(&req) configs, _ := config.LoadConfigs() if req.Index >= 0 && req.Index < len(configs.WireGuard) { name := configs.WireGuard[req.Index].Name wireguard.DeleteConfig(name) } json.NewEncoder(w).Encode(map[string]bool{"success": true}) } func handleWireGuardConnect(w http.ResponseWriter, r *http.Request) { var req struct { Index int `json:"index"` } json.NewDecoder(r.Body).Decode(&req) configs, _ := config.LoadConfigs() if req.Index >= 0 && req.Index < len(configs.WireGuard) { name := configs.WireGuard[req.Index].Name err := wireguard.Connect(name, config.LogsDir) json.NewEncoder(w).Encode(map[string]interface{}{ "success": err == nil, "message": func() string { if err != nil { return err.Error() } return "Подключено к " + name }(), }) return } json.NewEncoder(w).Encode(map[string]interface{}{ "success": false, "message": "Конфиг не найден", }) } func handleVLESSList(w http.ResponseWriter, r *http.Request) { configs, _ := config.LoadConfigs() json.NewEncoder(w).Encode(configs.VLESS) } func handleVLESSAdd(w http.ResponseWriter, r *http.Request) { var req struct { Name string `json:"name"` URL string `json:"url"` } json.NewDecoder(r.Body).Decode(&req) configs, err := config.LoadConfigs() if err == nil { configs.VLESS = append(configs.VLESS, config.VLESSConfig{ Name: req.Name, URL: req.URL, Protocol: "VLESS", }) config.SaveConfigs(configs) } json.NewEncoder(w).Encode(map[string]bool{"success": true}) } func handleVLESSDelete(w http.ResponseWriter, r *http.Request) { var req struct { Index int `json:"index"` } json.NewDecoder(r.Body).Decode(&req) configs, _ := config.LoadConfigs() if req.Index >= 0 && req.Index < len(configs.VLESS) { configs.VLESS = append(configs.VLESS[:req.Index], configs.VLESS[req.Index+1:]...) config.SaveConfigs(configs) } json.NewEncoder(w).Encode(map[string]bool{"success": true}) } func handleVLESSConnect(w http.ResponseWriter, r *http.Request) { var req struct { Index int `json:"index"` } json.NewDecoder(r.Body).Decode(&req) configs, _ := config.LoadConfigs() if req.Index >= 0 && req.Index < len(configs.VLESS) { name := configs.VLESS[req.Index].Name err := vless.Connect(name, config.LogsDir) json.NewEncoder(w).Encode(map[string]interface{}{ "success": err == nil, "message": func() string { if err != nil { return err.Error() } return "Подключено к " + name + "\n\nSOCKS5 прокси: 127.0.0.1:10808" }(), }) return } json.NewEncoder(w).Encode(map[string]interface{}{ "success": false, "message": "Конфиг не найден", }) } func handleVLESSTest(w http.ResponseWriter, r *http.Request) { var req struct { Index int `json:"index"` } json.NewDecoder(r.Body).Decode(&req) configs, _ := config.LoadConfigs() if req.Index >= 0 && req.Index < len(configs.VLESS) { cfg := configs.VLESS[req.Index] success, ping, err := vless.PingServer(cfg.URL, 5*time.Second) if err != nil || !success { msg := "Сервер недоступен" if err != nil { msg += "\n\nОшибка: " + err.Error() } json.NewEncoder(w).Encode(map[string]string{"message": msg}) return } quality := "Плохо" if ping < 50 { quality = "Отлично" } else if ping < 100 { quality = "Хорошо" } else if ping < 200 { quality = "Средне" } msg := fmt.Sprintf("✓ Сервер доступен\n\nПинг: %.2f мс\nКачество: %s", ping, quality) json.NewEncoder(w).Encode(map[string]string{"message": msg}) return } json.NewEncoder(w).Encode(map[string]string{"message": "Конфиг не найден"}) } func handleSubscriptionsList(w http.ResponseWriter, r *http.Request) { subs, _ := config.LoadSubscriptions() json.NewEncoder(w).Encode(subs.Subscriptions) } func handleSubscriptionsAdd(w http.ResponseWriter, r *http.Request) { var req struct { Name string `json:"name"` URL string `json:"url"` } json.NewDecoder(r.Body).Decode(&req) subs, err := config.LoadSubscriptions() if err == nil { subs.Subscriptions = append(subs.Subscriptions, config.Subscription{ Name: req.Name, URL: req.URL, }) config.SaveSubscriptions(subs) } json.NewEncoder(w).Encode(map[string]bool{"success": true}) } func handleSubscriptionsDelete(w http.ResponseWriter, r *http.Request) { var req struct { Index int `json:"index"` } json.NewDecoder(r.Body).Decode(&req) subs, _ := config.LoadSubscriptions() if req.Index >= 0 && req.Index < len(subs.Subscriptions) { subs.Subscriptions = append(subs.Subscriptions[:req.Index], subs.Subscriptions[req.Index+1:]...) config.SaveSubscriptions(subs) } json.NewEncoder(w).Encode(map[string]bool{"success": true}) } func handleSubscriptionsUpdate(w http.ResponseWriter, r *http.Request) { var req struct { Index int `json:"index"` } json.NewDecoder(r.Body).Decode(&req) subs, _ := config.LoadSubscriptions() if req.Index >= 0 && req.Index < len(subs.Subscriptions) { name := subs.Subscriptions[req.Index].Name err := subscription.UpdateSubscription(name, config.LogsDir) json.NewEncoder(w).Encode(map[string]interface{}{ "success": err == nil, "message": func() string { if err != nil { return err.Error() } return "Конфиги обновлены из подписки" }(), }) return } json.NewEncoder(w).Encode(map[string]interface{}{ "success": false, "message": "Подписка не найдена", }) } func handleSubscriptionsShow(w http.ResponseWriter, r *http.Request) { var req struct { Index int `json:"index"` } json.NewDecoder(r.Body).Decode(&req) subs, _ := config.LoadSubscriptions() if req.Index >= 0 && req.Index < len(subs.Subscriptions) { subName := subs.Subscriptions[req.Index].Name configs, _ := config.LoadConfigs() var subConfigs []config.VLESSConfig for _, cfg := range configs.VLESS { if cfg.Subscription == subName { subConfigs = append(subConfigs, cfg) } } if len(subConfigs) == 0 { json.NewEncoder(w).Encode(map[string]string{ "message": "Нет конфигов из этой подписки.\n\nСначала обновите конфиги из подписки.", }) return } msg := fmt.Sprintf("Конфиги из '%s':\n\n", subName) for i, cfg := range subConfigs { protocol := cfg.Protocol if protocol == "" { protocol = "Unknown" } msg += fmt.Sprintf("%d. [%s] %s\n", i+1, protocol, cfg.Name) } json.NewEncoder(w).Encode(map[string]string{"message": msg}) return } json.NewEncoder(w).Encode(map[string]string{"message": "Подписка не найдена"}) } func handleSubscriptionsTest(w http.ResponseWriter, r *http.Request) { var req struct { Index int `json:"index"` } json.NewDecoder(r.Body).Decode(&req) subs, _ := config.LoadSubscriptions() if req.Index >= 0 && req.Index < len(subs.Subscriptions) { subName := subs.Subscriptions[req.Index].Name configs, _ := config.LoadConfigs() var subConfigs []config.VLESSConfig for _, cfg := range configs.VLESS { if cfg.Subscription == subName { subConfigs = append(subConfigs, cfg) } } if len(subConfigs) == 0 { json.NewEncoder(w).Encode(map[string]string{"message": "Нет конфигов из этой подписки"}) return } // Тестирование (упрощенная версия - первые 10) msg := fmt.Sprintf("Тестирование серверов из '%s'...\n\n", subName) tested := 0 for i, cfg := range subConfigs { if i >= 10 { break } tested++ success, ping, _ := vless.PingServer(cfg.URL, 3*time.Second) if success { msg += fmt.Sprintf("✓ %s - %.2f мс\n", cfg.Name, ping) } else { msg += fmt.Sprintf("✗ %s - недоступен\n", cfg.Name) } } if len(subConfigs) > 10 { msg += fmt.Sprintf("\n(Показано %d из %d серверов)", tested, len(subConfigs)) } json.NewEncoder(w).Encode(map[string]string{"message": msg}) return } json.NewEncoder(w).Encode(map[string]string{"message": "Подписка не найдена"}) } func handleDisconnect(w http.ResponseWriter, r *http.Request) { state, err := vpn.GetStatus() if err != nil || !state.Connected { json.NewEncoder(w).Encode(map[string]interface{}{ "success": false, "message": "VPN не подключен", }) return } err = vpn.Disconnect(config.LogsDir) json.NewEncoder(w).Encode(map[string]interface{}{ "success": err == nil, "message": func() string { if err != nil { return err.Error() } return "Отключено от VPN" }(), }) }