package cli import ( "fmt" "os" "os/exec" "runtime" "strings" "syscall" "time" "vpn-client/internal/config" "vpn-client/internal/wireguard" ) // MonitorConnection показывает статус подключения в реальном времени func MonitorConnection() error { // Проверяем, что есть активное подключение state, err := config.LoadState() if err != nil { return fmt.Errorf("ошибка загрузки состояния: %w", err) } if !state.Connected { fmt.Println("VPN не подключен") return nil } fmt.Println("Нажмите 'q' или Ctrl+C для выхода из мониторинга\n") time.Sleep(1 * time.Second) // Создаем канал для остановки мониторинга stopChan := make(chan bool, 1) // Запускаем горутину для чтения клавиш go func() { for { var input string fmt.Scanln(&input) if strings.ToLower(input) == "q" || strings.ToLower(input) == "й" { stopChan <- true return } } }() // Запускаем мониторинг ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() for { select { case <-ticker.C: clearScreen() displayRealtimeStatus(state) fmt.Printf("\n%s Нажмите 'q' и Enter для выхода\n", cyan("ℹ")) case <-stopChan: fmt.Println("\n\nВыход из мониторинга...") time.Sleep(500 * time.Millisecond) return nil } } } func displayRealtimeStatus(state *config.ConnectionState) { // Перезагружаем состояние для актуальных данных currentState, err := config.LoadState() if err != nil || !currentState.Connected { fmt.Println("❌ Подключение потеряно") return } // Заголовок fmt.Println(strings.Repeat("═", 70)) fmt.Println(bold("📊 VPN МОНИТОРИНГ В РЕАЛЬНОМ ВРЕМЕНИ")) fmt.Println(strings.Repeat("═", 70)) // Статус подключения fmt.Printf("\n%s %s\n", green("●"), bold("ПОДКЛЮЧЕНО")) fmt.Printf("Конфигурация: %s\n", cyan(currentState.ConfigName)) fmt.Printf("Тип: %s\n", currentState.ConfigType) // Время подключения if currentState.StartTime != "" { startTime, err := time.Parse(time.RFC3339, currentState.StartTime) if err == nil { duration := time.Since(startTime) hours := int(duration.Hours()) minutes := int(duration.Minutes()) % 60 seconds := int(duration.Seconds()) % 60 fmt.Printf("Время подключения: %s\n", yellow(fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds))) } } // Текущее время fmt.Printf("Текущее время: %s\n", time.Now().Format("15:04:05")) fmt.Println(strings.Repeat("─", 70)) // Специфичная информация по типу подключения if currentState.ConfigType == "vless" { displayVLESSStats(currentState) } else if currentState.ConfigType == "wireguard" { displayWireGuardStats(currentState) } fmt.Println(strings.Repeat("═", 70)) fmt.Printf("\n%s Нажмите 'q' и Enter для выхода\n", cyan("ℹ")) } func displayVLESSStats(state *config.ConnectionState) { fmt.Println("\n" + bold("VLESS/Xray Прокси")) fmt.Printf("Адрес прокси: %s\n", green("127.0.0.1:10808")) fmt.Printf("Протокол: SOCKS5\n") fmt.Printf("PID процесса: %d\n", state.ProcessPID) if state.LogFile != "" { fmt.Printf("\n%s Логи\n", bold("📝")) fmt.Printf(" Трафик: %s\n", state.LogFile) // Показываем размер лог-файла if info, err := os.Stat(state.LogFile); err == nil { size := info.Size() sizeStr := formatBytes(size) fmt.Printf(" Размер лога: %s\n", sizeStr) } } // Проверяем, что процесс еще работает (улучшенная проверка для Windows) if state.ProcessPID > 0 { processRunning := checkProcessRunning(state.ProcessPID) if !processRunning { fmt.Printf("\n%s Процесс Xray не отвечает!\n", red("⚠")) } } } // checkProcessRunning проверяет, работает ли процесс func checkProcessRunning(pid int) bool { process, err := os.FindProcess(pid) if err != nil { return false } // На Windows FindProcess всегда успешен, нужна дополнительная проверка // Пытаемся получить информацию о процессе через tasklist if runtime.GOOS == "windows" { cmd := exec.Command("tasklist", "/FI", fmt.Sprintf("PID eq %d", pid), "/NH") output, err := cmd.Output() if err != nil { return false } // Если процесс существует, в выводе будет его PID return strings.Contains(string(output), fmt.Sprintf("%d", pid)) } // На Unix используем сигнал 0 err = process.Signal(syscall.Signal(0)) return err == nil } func displayWireGuardStats(state *config.ConnectionState) { fmt.Println("\n" + bold("WireGuard Туннель")) fmt.Printf("Интерфейс: %s\n", state.Interface) // Получаем статистику WireGuard stats, err := wireguard.GetStats(state.Interface) if err != nil { fmt.Printf("\n%s Ошибка получения статистики: %v\n", red("⚠"), err) return } fmt.Printf("\n%s Статистика трафика\n", bold("📊")) // Парсим и форматируем данные if rx, ok := stats["rx"]; ok { fmt.Printf(" %s Получено: %s\n", green("↓"), rx) } if tx, ok := stats["tx"]; ok { fmt.Printf(" %s Отправлено: %s\n", yellow("↑"), tx) } // Дополнительная информация if endpoint, ok := stats["endpoint"]; ok && endpoint != "" { fmt.Printf("\n%s Сервер\n", bold("🌐")) fmt.Printf(" Endpoint: %s\n", endpoint) } if handshake, ok := stats["latest_handshake"]; ok && handshake != "" { fmt.Printf(" Последний handshake: %s\n", handshake) } } func formatBytes(bytes int64) string { const unit = 1024 if bytes < unit { return fmt.Sprintf("%d B", bytes) } div, exp := int64(unit), 0 for n := bytes / unit; n >= unit; n /= unit { div *= unit exp++ } return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp]) } // ShowQuickStatus показывает краткий статус (для главного меню) func ShowQuickStatus() string { state, err := config.LoadState() if err != nil || !state.Connected { return fmt.Sprintf("%s Не подключено", red("○")) } // Вычисляем время подключения var timeStr string 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 timeStr = fmt.Sprintf(" [%02d:%02d:%02d]", hours, minutes, seconds) } } return fmt.Sprintf("%s Подключено: %s%s", green("●"), cyan(state.ConfigName), yellow(timeStr)) }