Files
Go-VPN-Client/internal/cli/monitor.go
arkonsadter d88139af1b feat(admin): add platform-specific admin privilege checks
- Upgrade Go version from 1.21 to 1.25.0
- Update golang.org/x/sys dependency to v0.42.0
- Add Unix/Linux admin check using os.Geteuid() with sudo requirement
- Add Windows admin check using windows.SID and token membership validation
- Integrate admin privilege validation into main CLI entry point
- Enhance monitor.go with graceful signal handling for Ctrl+C interrupts
- Add signal channels for clean shutdown of monitoring loop
- Ensures VPN client runs with required elevated privileges on both platforms
2026-04-06 19:06:03 +06:00

210 lines
6.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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))
}