package cli import ( "fmt" "os" "os/signal" "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("Нажмите Ctrl+C для выхода из мониторинга\n") time.Sleep(1 * time.Second) // Создаем канал для обработки сигналов sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) // Создаем канал для остановки мониторинга stopChan := make(chan bool, 1) // Запускаем горутину для обработки сигналов go func() { <-sigChan stopChan <- true }() // Запускаем мониторинг ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() for { select { case <-ticker.C: clearScreen() displayRealtimeStatus(state) case <-stopChan: // Восстанавливаем обработку сигналов по умолчанию signal.Reset(os.Interrupt, syscall.SIGTERM) 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 Обновление каждую секунду | Нажмите Ctrl+C для выхода\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) } } // Проверяем, что процесс еще работает if state.ProcessPID > 0 { process, err := os.FindProcess(state.ProcessPID) if err == nil { if err := process.Signal(os.Signal(nil)); err != nil { fmt.Printf("\n%s Процесс Xray не отвечает!\n", red("⚠")) } } } } 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 timeStr = fmt.Sprintf(" [%02d:%02d]", hours, minutes) } } return fmt.Sprintf("%s Подключено: %s%s", green("●"), cyan(state.ConfigName), yellow(timeStr)) }