Initial commit
This commit is contained in:
864
internal/cli/cli.go
Normal file
864
internal/cli/cli.go
Normal file
@@ -0,0 +1,864 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"vpn-client/internal/config"
|
||||
"vpn-client/internal/subscription"
|
||||
"vpn-client/internal/vless"
|
||||
"vpn-client/internal/vpn"
|
||||
"vpn-client/internal/wireguard"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
var (
|
||||
green = color.New(color.FgGreen).SprintFunc()
|
||||
red = color.New(color.FgRed).SprintFunc()
|
||||
yellow = color.New(color.FgYellow).SprintFunc()
|
||||
cyan = color.New(color.FgCyan).SprintFunc()
|
||||
bold = color.New(color.Bold).SprintFunc()
|
||||
)
|
||||
|
||||
// Run запускает CLI интерфейс
|
||||
func Run() error {
|
||||
for {
|
||||
clearScreen()
|
||||
showMainMenu()
|
||||
|
||||
choice := readInput("Выберите действие: ")
|
||||
|
||||
switch choice {
|
||||
case "1":
|
||||
wireguardMenu()
|
||||
case "2":
|
||||
vlessMenu()
|
||||
case "3":
|
||||
subscriptionsMenu()
|
||||
case "4":
|
||||
if err := vpn.ShowStatus(); err != nil {
|
||||
fmt.Printf("%s %v\n", red("✗"), err)
|
||||
}
|
||||
pause()
|
||||
case "5":
|
||||
if err := vpn.Disconnect(config.LogsDir); err != nil {
|
||||
fmt.Printf("%s %v\n", red("✗"), err)
|
||||
}
|
||||
pause()
|
||||
case "0":
|
||||
fmt.Println("До свидания!")
|
||||
return nil
|
||||
default:
|
||||
fmt.Printf("%s Неверный выбор\n", red("✗"))
|
||||
pause()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func showMainMenu() {
|
||||
state, _ := vpn.GetStatus()
|
||||
|
||||
statusIcon := "○"
|
||||
statusText := "Не подключено"
|
||||
if state != nil && state.Connected {
|
||||
statusIcon = "✓"
|
||||
statusText = fmt.Sprintf("Подключено: %s", state.ConfigName)
|
||||
}
|
||||
|
||||
fmt.Println("\n" + strings.Repeat("=", 50))
|
||||
fmt.Println(bold("VPN Клиент (Go)"))
|
||||
fmt.Println(strings.Repeat("=", 50))
|
||||
fmt.Printf("Статус: %s %s\n", statusIcon, statusText)
|
||||
fmt.Println(strings.Repeat("=", 50))
|
||||
fmt.Println("1. WireGuard")
|
||||
fmt.Println("2. VLESS")
|
||||
fmt.Println("3. Управление подписками")
|
||||
fmt.Println("4. Показать статус подключения")
|
||||
fmt.Println("5. Отключиться от VPN")
|
||||
fmt.Println("0. Выход")
|
||||
fmt.Println(strings.Repeat("=", 50))
|
||||
}
|
||||
|
||||
func vlessMenu() {
|
||||
for {
|
||||
clearScreen()
|
||||
fmt.Println("\n" + strings.Repeat("=", 50))
|
||||
fmt.Println(bold("VLESS"))
|
||||
fmt.Println(strings.Repeat("=", 50))
|
||||
fmt.Println("1. Список конфигураций")
|
||||
fmt.Println("2. Добавить конфиг")
|
||||
fmt.Println("3. Удалить конфиг")
|
||||
fmt.Println("4. Подключиться")
|
||||
fmt.Println("5. Тестировать конфиг (пинг)")
|
||||
fmt.Println("0. Назад")
|
||||
fmt.Println(strings.Repeat("=", 50))
|
||||
|
||||
choice := readInput("\nВыберите действие: ")
|
||||
|
||||
switch choice {
|
||||
case "0":
|
||||
return
|
||||
case "1":
|
||||
listVLESSConfigs()
|
||||
pause()
|
||||
case "2":
|
||||
addVLESSConfig()
|
||||
pause()
|
||||
case "3":
|
||||
deleteVLESSConfig()
|
||||
pause()
|
||||
case "4":
|
||||
connectVLESS()
|
||||
pause()
|
||||
case "5":
|
||||
testVLESSConfig()
|
||||
pause()
|
||||
default:
|
||||
fmt.Printf("%s Неверный выбор\n", red("✗"))
|
||||
pause()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func listVLESSConfigs() {
|
||||
configs, err := config.LoadConfigs()
|
||||
if err != nil {
|
||||
fmt.Printf("%s Ошибка загрузки конфигураций: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("\n=== VLESS конфиги ===")
|
||||
if len(configs.VLESS) == 0 {
|
||||
fmt.Println("Нет конфигов")
|
||||
return
|
||||
}
|
||||
|
||||
for i, cfg := range configs.VLESS {
|
||||
protocol := cfg.Protocol
|
||||
if protocol == "" {
|
||||
protocol = "VLESS"
|
||||
}
|
||||
fmt.Printf("%d. [%s] %s\n", i+1, protocol, cfg.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func addVLESSConfig() {
|
||||
name := readInput("Имя конфига: ")
|
||||
if name == "" {
|
||||
fmt.Printf("%s Имя не может быть пустым\n", red("✗"))
|
||||
return
|
||||
}
|
||||
|
||||
vlessURL := readInput("VLESS URL: ")
|
||||
if vlessURL == "" {
|
||||
fmt.Printf("%s URL не может быть пустым\n", red("✗"))
|
||||
return
|
||||
}
|
||||
|
||||
configs, err := config.LoadConfigs()
|
||||
if err != nil {
|
||||
fmt.Printf("%s Ошибка загрузки конфигураций: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
|
||||
configs.VLESS = append(configs.VLESS, config.VLESSConfig{
|
||||
Name: name,
|
||||
URL: vlessURL,
|
||||
Protocol: "VLESS",
|
||||
})
|
||||
|
||||
if err := config.SaveConfigs(configs); err != nil {
|
||||
fmt.Printf("%s Ошибка сохранения конфигураций: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%s VLESS конфиг '%s' добавлен\n", green("✓"), name)
|
||||
}
|
||||
|
||||
func deleteVLESSConfig() {
|
||||
configs, err := config.LoadConfigs()
|
||||
if err != nil {
|
||||
fmt.Printf("%s Ошибка загрузки конфигураций: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(configs.VLESS) == 0 {
|
||||
fmt.Println("Нет конфигов для удаления")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("\n=== VLESS конфиги ===")
|
||||
for i, cfg := range configs.VLESS {
|
||||
fmt.Printf("%d. %s\n", i+1, cfg.Name)
|
||||
}
|
||||
|
||||
numStr := readInput("\nНомер конфига для удаления: ")
|
||||
num, err := strconv.Atoi(numStr)
|
||||
if err != nil || num < 1 || num > len(configs.VLESS) {
|
||||
fmt.Printf("%s Неверный номер конфига\n", red("✗"))
|
||||
return
|
||||
}
|
||||
|
||||
name := configs.VLESS[num-1].Name
|
||||
configs.VLESS = append(configs.VLESS[:num-1], configs.VLESS[num:]...)
|
||||
|
||||
if err := config.SaveConfigs(configs); err != nil {
|
||||
fmt.Printf("%s Ошибка сохранения конфигураций: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%s Конфиг '%s' удален\n", green("✓"), name)
|
||||
}
|
||||
|
||||
func connectVLESS() {
|
||||
configs, err := config.LoadConfigs()
|
||||
if err != nil {
|
||||
fmt.Printf("%s Ошибка загрузки конфигураций: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(configs.VLESS) == 0 {
|
||||
fmt.Println("Нет конфигов для подключения")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("\n=== VLESS конфиги ===")
|
||||
for i, cfg := range configs.VLESS {
|
||||
protocol := cfg.Protocol
|
||||
if protocol == "" {
|
||||
protocol = "VLESS"
|
||||
}
|
||||
fmt.Printf("%d. [%s] %s\n", i+1, protocol, cfg.Name)
|
||||
}
|
||||
|
||||
numStr := readInput("\nНомер конфига для подключения: ")
|
||||
num, err := strconv.Atoi(numStr)
|
||||
if err != nil || num < 1 || num > len(configs.VLESS) {
|
||||
fmt.Printf("%s Неверный номер конфига\n", red("✗"))
|
||||
return
|
||||
}
|
||||
|
||||
configName := configs.VLESS[num-1].Name
|
||||
|
||||
fmt.Printf("\nПодключение к '%s' через Xray...\n", configName)
|
||||
|
||||
if err := vless.Connect(configName, config.LogsDir, config.XrayDir); err != nil {
|
||||
fmt.Printf("%s Ошибка подключения: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func testVLESSConfig() {
|
||||
configs, err := config.LoadConfigs()
|
||||
if err != nil {
|
||||
fmt.Printf("%s Ошибка загрузки конфигураций: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(configs.VLESS) == 0 {
|
||||
fmt.Println("Нет конфигов для тестирования")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("\n=== VLESS конфиги ===")
|
||||
for i, cfg := range configs.VLESS {
|
||||
fmt.Printf("%d. %s\n", i+1, cfg.Name)
|
||||
}
|
||||
|
||||
numStr := readInput("\nНомер конфига для тестирования: ")
|
||||
num, err := strconv.Atoi(numStr)
|
||||
if err != nil || num < 1 || num > len(configs.VLESS) {
|
||||
fmt.Printf("%s Неверный номер конфига\n", red("✗"))
|
||||
return
|
||||
}
|
||||
|
||||
cfg := configs.VLESS[num-1]
|
||||
|
||||
fmt.Printf("\nТестирование '%s'...\n", cfg.Name)
|
||||
fmt.Println("Проверка доступности сервера...")
|
||||
|
||||
success, ping, err := vless.PingServer(cfg.URL, 5000000000) // 5 секунд
|
||||
if err != nil || !success {
|
||||
fmt.Printf("%s Сервер недоступен\n", red("✗"))
|
||||
if err != nil {
|
||||
fmt.Printf(" Ошибка: %v\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%s Сервер доступен\n", green("✓"))
|
||||
fmt.Printf(" Пинг: %.2f мс\n", ping)
|
||||
|
||||
// Оценка качества
|
||||
var quality string
|
||||
if ping < 50 {
|
||||
quality = green("Отлично")
|
||||
} else if ping < 100 {
|
||||
quality = cyan("Хорошо")
|
||||
} else if ping < 200 {
|
||||
quality = yellow("Средне")
|
||||
} else {
|
||||
quality = red("Плохо")
|
||||
}
|
||||
fmt.Printf(" Качество: %s\n", quality)
|
||||
}
|
||||
|
||||
func subscriptionsMenu() {
|
||||
for {
|
||||
clearScreen()
|
||||
fmt.Println("\n" + strings.Repeat("=", 50))
|
||||
fmt.Println(bold("Управление подписками"))
|
||||
fmt.Println(strings.Repeat("=", 50))
|
||||
fmt.Println("1. Список подписок")
|
||||
fmt.Println("2. Добавить подписку")
|
||||
fmt.Println("3. Удалить подписку")
|
||||
fmt.Println("4. Обновить конфиги из подписки")
|
||||
fmt.Println("5. Показать конфиги из подписки")
|
||||
fmt.Println("6. Тестировать конфиги из подписки (пинг)")
|
||||
fmt.Println("0. Назад")
|
||||
fmt.Println(strings.Repeat("=", 50))
|
||||
|
||||
choice := readInput("\nВыберите действие: ")
|
||||
|
||||
switch choice {
|
||||
case "0":
|
||||
return
|
||||
case "1":
|
||||
listSubscriptions()
|
||||
pause()
|
||||
case "2":
|
||||
addSubscription()
|
||||
pause()
|
||||
case "3":
|
||||
deleteSubscription()
|
||||
pause()
|
||||
case "4":
|
||||
updateSubscription()
|
||||
pause()
|
||||
case "5":
|
||||
showSubscriptionConfigs()
|
||||
pause()
|
||||
case "6":
|
||||
testSubscriptionConfigs()
|
||||
pause()
|
||||
default:
|
||||
fmt.Printf("%s Неверный выбор\n", red("✗"))
|
||||
pause()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func listSubscriptions() {
|
||||
subs, err := config.LoadSubscriptions()
|
||||
if err != nil {
|
||||
fmt.Printf("%s Ошибка загрузки подписок: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("\nПодписки:")
|
||||
if len(subs.Subscriptions) == 0 {
|
||||
fmt.Println("Нет подписок")
|
||||
return
|
||||
}
|
||||
|
||||
for i, sub := range subs.Subscriptions {
|
||||
fmt.Printf("%d. %s - %s\n", i+1, sub.Name, sub.URL)
|
||||
}
|
||||
}
|
||||
|
||||
func addSubscription() {
|
||||
name := readInput("Имя подписки: ")
|
||||
if name == "" {
|
||||
fmt.Printf("%s Имя не может быть пустым\n", red("✗"))
|
||||
return
|
||||
}
|
||||
|
||||
url := readInput("URL подписки: ")
|
||||
if url == "" {
|
||||
fmt.Printf("%s URL не может быть пустым\n", red("✗"))
|
||||
return
|
||||
}
|
||||
|
||||
subs, err := config.LoadSubscriptions()
|
||||
if err != nil {
|
||||
fmt.Printf("%s Ошибка загрузки подписок: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
|
||||
subs.Subscriptions = append(subs.Subscriptions, config.Subscription{
|
||||
Name: name,
|
||||
URL: url,
|
||||
})
|
||||
|
||||
if err := config.SaveSubscriptions(subs); err != nil {
|
||||
fmt.Printf("%s Ошибка сохранения подписок: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%s Подписка '%s' добавлена\n", green("✓"), name)
|
||||
}
|
||||
|
||||
func deleteSubscription() {
|
||||
subs, err := config.LoadSubscriptions()
|
||||
if err != nil {
|
||||
fmt.Printf("%s Ошибка загрузки подписок: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(subs.Subscriptions) == 0 {
|
||||
fmt.Println("Нет подписок для удаления")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("\nПодписки:")
|
||||
for i, sub := range subs.Subscriptions {
|
||||
fmt.Printf("%d. %s\n", i+1, sub.Name)
|
||||
}
|
||||
|
||||
numStr := readInput("\nНомер подписки для удаления: ")
|
||||
num, err := strconv.Atoi(numStr)
|
||||
if err != nil || num < 1 || num > len(subs.Subscriptions) {
|
||||
fmt.Printf("%s Неверный номер подписки\n", red("✗"))
|
||||
return
|
||||
}
|
||||
|
||||
name := subs.Subscriptions[num-1].Name
|
||||
subs.Subscriptions = append(subs.Subscriptions[:num-1], subs.Subscriptions[num:]...)
|
||||
|
||||
if err := config.SaveSubscriptions(subs); err != nil {
|
||||
fmt.Printf("%s Ошибка сохранения подписок: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%s Подписка '%s' удалена\n", green("✓"), name)
|
||||
}
|
||||
|
||||
func updateSubscription() {
|
||||
subs, err := config.LoadSubscriptions()
|
||||
if err != nil {
|
||||
fmt.Printf("%s Ошибка загрузки подписок: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(subs.Subscriptions) == 0 {
|
||||
fmt.Println("Нет подписок")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("\nПодписки:")
|
||||
for i, sub := range subs.Subscriptions {
|
||||
fmt.Printf("%d. %s\n", i+1, sub.Name)
|
||||
}
|
||||
|
||||
numStr := readInput("\nНомер подписки для обновления: ")
|
||||
num, err := strconv.Atoi(numStr)
|
||||
if err != nil || num < 1 || num > len(subs.Subscriptions) {
|
||||
fmt.Printf("%s Неверный номер подписки\n", red("✗"))
|
||||
return
|
||||
}
|
||||
|
||||
name := subs.Subscriptions[num-1].Name
|
||||
|
||||
if err := subscription.UpdateSubscription(name, config.LogsDir); err != nil {
|
||||
fmt.Printf("%s Ошибка обновления: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func showSubscriptionConfigs() {
|
||||
subs, err := config.LoadSubscriptions()
|
||||
if err != nil {
|
||||
fmt.Printf("%s Ошибка загрузки подписок: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(subs.Subscriptions) == 0 {
|
||||
fmt.Println("Нет подписок")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("\nПодписки:")
|
||||
for i, sub := range subs.Subscriptions {
|
||||
fmt.Printf("%d. %s\n", i+1, sub.Name)
|
||||
}
|
||||
|
||||
numStr := readInput("\nНомер подписки: ")
|
||||
num, err := strconv.Atoi(numStr)
|
||||
if err != nil || num < 1 || num > len(subs.Subscriptions) {
|
||||
fmt.Printf("%s Неверный номер подписки\n", red("✗"))
|
||||
return
|
||||
}
|
||||
|
||||
subName := subs.Subscriptions[num-1].Name
|
||||
|
||||
configs, err := config.LoadConfigs()
|
||||
if err != nil {
|
||||
fmt.Printf("%s Ошибка загрузки конфигураций: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("\n=== Конфиги из подписки '%s' ===\n", subName)
|
||||
count := 0
|
||||
for _, cfg := range configs.VLESS {
|
||||
if cfg.Subscription == subName {
|
||||
count++
|
||||
protocol := cfg.Protocol
|
||||
if protocol == "" {
|
||||
protocol = "Unknown"
|
||||
}
|
||||
displayName := strings.TrimPrefix(cfg.Name, fmt.Sprintf("[%s] ", subName))
|
||||
fmt.Printf("%d. [%s] %s\n", count, protocol, displayName)
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
fmt.Println("Нет конфигов из этой подписки")
|
||||
fmt.Println("Сначала обновите конфиги из подписки (пункт 4)")
|
||||
}
|
||||
}
|
||||
|
||||
func testSubscriptionConfigs() {
|
||||
subs, err := config.LoadSubscriptions()
|
||||
if err != nil {
|
||||
fmt.Printf("%s Ошибка загрузки подписок: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(subs.Subscriptions) == 0 {
|
||||
fmt.Println("Нет подписок")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("\nПодписки:")
|
||||
for i, sub := range subs.Subscriptions {
|
||||
fmt.Printf("%d. %s\n", i+1, sub.Name)
|
||||
}
|
||||
|
||||
numStr := readInput("\nНомер подписки: ")
|
||||
num, err := strconv.Atoi(numStr)
|
||||
if err != nil || num < 1 || num > len(subs.Subscriptions) {
|
||||
fmt.Printf("%s Неверный номер подписки\n", red("✗"))
|
||||
return
|
||||
}
|
||||
|
||||
subName := subs.Subscriptions[num-1].Name
|
||||
|
||||
configs, err := config.LoadConfigs()
|
||||
if err != nil {
|
||||
fmt.Printf("%s Ошибка загрузки конфигураций: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
|
||||
var subConfigs []config.VLESSConfig
|
||||
for _, cfg := range configs.VLESS {
|
||||
if cfg.Subscription == subName {
|
||||
subConfigs = append(subConfigs, cfg)
|
||||
}
|
||||
}
|
||||
|
||||
if len(subConfigs) == 0 {
|
||||
fmt.Println("Нет конфигов из этой подписки")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("\n=== Тестирование конфигов из '%s' ===\n", subName)
|
||||
fmt.Printf("Найдено конфигов: %d\n\n", len(subConfigs))
|
||||
|
||||
type result struct {
|
||||
name string
|
||||
protocol string
|
||||
ping float64
|
||||
success bool
|
||||
}
|
||||
|
||||
var results []result
|
||||
|
||||
for i, cfg := range subConfigs {
|
||||
protocol := cfg.Protocol
|
||||
if protocol == "" {
|
||||
protocol = "Unknown"
|
||||
}
|
||||
displayName := strings.TrimPrefix(cfg.Name, fmt.Sprintf("[%s] ", subName))
|
||||
|
||||
fmt.Printf("%d/%d Тестирование [%s] %s...", i+1, len(subConfigs), protocol, truncate(displayName, 50))
|
||||
|
||||
success, ping, _ := vless.PingServer(cfg.URL, 3000000000) // 3 секунды
|
||||
|
||||
if success {
|
||||
fmt.Printf(" %s %.2f мс\n", green("✓"), ping)
|
||||
results = append(results, result{
|
||||
name: displayName,
|
||||
protocol: protocol,
|
||||
ping: ping,
|
||||
success: true,
|
||||
})
|
||||
} else {
|
||||
fmt.Printf(" %s Недоступен\n", red("✗"))
|
||||
results = append(results, result{
|
||||
name: displayName,
|
||||
protocol: protocol,
|
||||
success: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Сортируем по пингу
|
||||
var successful []result
|
||||
var failed []result
|
||||
for _, r := range results {
|
||||
if r.success {
|
||||
successful = append(successful, r)
|
||||
} else {
|
||||
failed = append(failed, r)
|
||||
}
|
||||
}
|
||||
|
||||
// Простая сортировка пузырьком
|
||||
for i := 0; i < len(successful); i++ {
|
||||
for j := i + 1; j < len(successful); j++ {
|
||||
if successful[i].ping > successful[j].ping {
|
||||
successful[i], successful[j] = successful[j], successful[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("\n" + strings.Repeat("=", 60))
|
||||
fmt.Println("РЕЗУЛЬТАТЫ ТЕСТИРОВАНИЯ")
|
||||
fmt.Println(strings.Repeat("=", 60))
|
||||
|
||||
if len(successful) > 0 {
|
||||
fmt.Printf("\n%s Доступно: %d\n", green("✓"), len(successful))
|
||||
fmt.Println("\nЛучшие серверы:")
|
||||
for i, r := range successful {
|
||||
if i >= 5 {
|
||||
break
|
||||
}
|
||||
fmt.Printf(" %d. [%s] %s - %.2f мс\n", i+1, r.protocol, truncate(r.name, 35), r.ping)
|
||||
}
|
||||
}
|
||||
|
||||
if len(failed) > 0 {
|
||||
fmt.Printf("\n%s Недоступно: %d\n", red("✗"), len(failed))
|
||||
}
|
||||
|
||||
fmt.Println(strings.Repeat("=", 60))
|
||||
}
|
||||
|
||||
func clearScreen() {
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd := exec.Command("cmd", "/c", "cls")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Run()
|
||||
} else {
|
||||
cmd := exec.Command("clear")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Run()
|
||||
}
|
||||
}
|
||||
|
||||
func readInput(prompt string) string {
|
||||
fmt.Print(prompt)
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
input, _ := reader.ReadString('\n')
|
||||
return strings.TrimSpace(input)
|
||||
}
|
||||
|
||||
func pause() {
|
||||
fmt.Print("\nНажмите Enter для продолжения...")
|
||||
bufio.NewReader(os.Stdin).ReadBytes('\n')
|
||||
}
|
||||
|
||||
func truncate(s string, maxLen int) string {
|
||||
if len(s) <= maxLen {
|
||||
return s
|
||||
}
|
||||
return s[:maxLen] + "..."
|
||||
}
|
||||
|
||||
|
||||
func wireguardMenu() {
|
||||
for {
|
||||
clearScreen()
|
||||
fmt.Println("\n" + strings.Repeat("=", 50))
|
||||
fmt.Println(bold("WireGuard"))
|
||||
fmt.Println(strings.Repeat("=", 50))
|
||||
fmt.Println("1. Список конфигураций")
|
||||
fmt.Println("2. Добавить конфиг (вручную)")
|
||||
fmt.Println("3. Добавить конфиг (из файла)")
|
||||
fmt.Println("4. Удалить конфиг")
|
||||
fmt.Println("5. Подключиться")
|
||||
fmt.Println("0. Назад")
|
||||
fmt.Println(strings.Repeat("=", 50))
|
||||
|
||||
choice := readInput("\nВыберите действие: ")
|
||||
|
||||
switch choice {
|
||||
case "0":
|
||||
return
|
||||
case "1":
|
||||
listWireGuardConfigs()
|
||||
pause()
|
||||
case "2":
|
||||
addWireGuardConfigManual()
|
||||
pause()
|
||||
case "3":
|
||||
addWireGuardConfigFromFile()
|
||||
pause()
|
||||
case "4":
|
||||
deleteWireGuardConfig()
|
||||
pause()
|
||||
case "5":
|
||||
connectWireGuard()
|
||||
pause()
|
||||
default:
|
||||
fmt.Printf("%s Неверный выбор\n", red("✗"))
|
||||
pause()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func listWireGuardConfigs() {
|
||||
configs, err := config.LoadConfigs()
|
||||
if err != nil {
|
||||
fmt.Printf("%s Ошибка загрузки конфигураций: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("\n=== WireGuard конфиги ===")
|
||||
if len(configs.WireGuard) == 0 {
|
||||
fmt.Println("Нет конфигов")
|
||||
return
|
||||
}
|
||||
|
||||
for i, cfg := range configs.WireGuard {
|
||||
fmt.Printf("%d. %s\n", i+1, cfg.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func addWireGuardConfigManual() {
|
||||
name := readInput("Имя конфига: ")
|
||||
if name == "" {
|
||||
fmt.Printf("%s Имя не может быть пустым\n", red("✗"))
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Вставьте конфиг WireGuard (завершите пустой строкой):")
|
||||
|
||||
var lines []string
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
line, _ := reader.ReadString('\n')
|
||||
line = strings.TrimRight(line, "\r\n")
|
||||
if line == "" {
|
||||
break
|
||||
}
|
||||
lines = append(lines, line)
|
||||
}
|
||||
|
||||
if len(lines) == 0 {
|
||||
fmt.Printf("%s Конфиг не может быть пустым\n", red("✗"))
|
||||
return
|
||||
}
|
||||
|
||||
configText := strings.Join(lines, "\n")
|
||||
|
||||
if err := wireguard.AddConfig(name, configText); err != nil {
|
||||
fmt.Printf("%s Ошибка добавления конфига: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%s WireGuard конфиг '%s' добавлен\n", green("✓"), name)
|
||||
}
|
||||
|
||||
func addWireGuardConfigFromFile() {
|
||||
name := readInput("Имя конфига: ")
|
||||
if name == "" {
|
||||
fmt.Printf("%s Имя не может быть пустым\n", red("✗"))
|
||||
return
|
||||
}
|
||||
|
||||
filePath := readInput("Путь к файлу конфига: ")
|
||||
if filePath == "" {
|
||||
fmt.Printf("%s Путь не может быть пустым\n", red("✗"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := wireguard.AddConfigFromFile(name, filePath); err != nil {
|
||||
fmt.Printf("%s Ошибка добавления конфига: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%s WireGuard конфиг '%s' добавлен из файла\n", green("✓"), name)
|
||||
}
|
||||
|
||||
func deleteWireGuardConfig() {
|
||||
configs, err := config.LoadConfigs()
|
||||
if err != nil {
|
||||
fmt.Printf("%s Ошибка загрузки конфигураций: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(configs.WireGuard) == 0 {
|
||||
fmt.Println("Нет конфигов для удаления")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("\n=== WireGuard конфиги ===")
|
||||
for i, cfg := range configs.WireGuard {
|
||||
fmt.Printf("%d. %s\n", i+1, cfg.Name)
|
||||
}
|
||||
|
||||
numStr := readInput("\nНомер конфига для удаления: ")
|
||||
num, err := strconv.Atoi(numStr)
|
||||
if err != nil || num < 1 || num > len(configs.WireGuard) {
|
||||
fmt.Printf("%s Неверный номер конфига\n", red("✗"))
|
||||
return
|
||||
}
|
||||
|
||||
name := configs.WireGuard[num-1].Name
|
||||
|
||||
if err := wireguard.DeleteConfig(name); err != nil {
|
||||
fmt.Printf("%s Ошибка удаления конфига: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%s Конфиг '%s' удален\n", green("✓"), name)
|
||||
}
|
||||
|
||||
func connectWireGuard() {
|
||||
configs, err := config.LoadConfigs()
|
||||
if err != nil {
|
||||
fmt.Printf("%s Ошибка загрузки конфигураций: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(configs.WireGuard) == 0 {
|
||||
fmt.Println("Нет конфигов для подключения")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("\n=== WireGuard конфиги ===")
|
||||
for i, cfg := range configs.WireGuard {
|
||||
fmt.Printf("%d. %s\n", i+1, cfg.Name)
|
||||
}
|
||||
|
||||
numStr := readInput("\nНомер конфига для подключения: ")
|
||||
num, err := strconv.Atoi(numStr)
|
||||
if err != nil || num < 1 || num > len(configs.WireGuard) {
|
||||
fmt.Printf("%s Неверный номер конфига\n", red("✗"))
|
||||
return
|
||||
}
|
||||
|
||||
configName := configs.WireGuard[num-1].Name
|
||||
|
||||
if err := wireguard.Connect(configName, config.LogsDir); err != nil {
|
||||
fmt.Printf("%s Ошибка подключения: %v\n", red("✗"), err)
|
||||
return
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user