diff --git a/app/Filament/Pages/M3uProxyStreamMonitor.php b/app/Filament/Pages/M3uProxyStreamMonitor.php index 9776ec35b..453a35f44 100644 --- a/app/Filament/Pages/M3uProxyStreamMonitor.php +++ b/app/Filament/Pages/M3uProxyStreamMonitor.php @@ -50,6 +50,8 @@ public static function canAccess(): bool public $systemStats = []; + public $vpnStatus = null; + public $refreshInterval = 5; // seconds public $connectionError = null; @@ -85,6 +87,10 @@ public function refreshData(): void ]; $this->systemStats = []; // populate if external API provides system metrics + + // Fetch VPN watchdog status (non-blocking, null if not enabled) + $vpnResult = $this->apiService->fetchVpnStatus(); + $this->vpnStatus = ($vpnResult['success'] ?? false) ? ($vpnResult['vpn'] ?? null) : null; } protected function getHeaderActions(): array @@ -185,6 +191,33 @@ public function stopStream(string $streamId): void $this->refreshData(); } + public function rotateVpn(): void + { + try { + $result = $this->apiService->triggerVpnRotation(); + if ($result['success'] ?? false) { + Notification::make() + ->title('VPN rotation triggered successfully.') + ->success() + ->send(); + } else { + Notification::make() + ->title('VPN rotation failed.') + ->body($result['error'] ?? 'Unknown error') + ->danger() + ->send(); + } + } catch (Exception $e) { + Notification::make() + ->title('Error rotating VPN.') + ->body($e->getMessage()) + ->danger() + ->send(); + } + + $this->refreshData(); + } + protected function getActiveStreams(): array { $apiStreams = $this->apiService->fetchActiveStreams(); diff --git a/app/Http/Controllers/Api/M3uProxyApiController.php b/app/Http/Controllers/Api/M3uProxyApiController.php index 7cad4ff53..0276a1c71 100644 --- a/app/Http/Controllers/Api/M3uProxyApiController.php +++ b/app/Http/Controllers/Api/M3uProxyApiController.php @@ -314,6 +314,16 @@ public function handleWebhook(Request $request) case 'stream_stopped': $this->invalidateStreamCaches($data); break; + + case 'vpn_health_changed': + case 'vpn_rotation_started': + case 'vpn_rotation_completed': + case 'vpn_rotation_failed': + Log::info('VPN watchdog event received', [ + 'event_type' => $eventType, + 'data' => $data, + ]); + break; } return response()->json(['status' => 'ok']); diff --git a/app/Services/M3uProxyService.php b/app/Services/M3uProxyService.php index fc8a70183..83d70952d 100644 --- a/app/Services/M3uProxyService.php +++ b/app/Services/M3uProxyService.php @@ -2449,4 +2449,93 @@ public function getWebhookUrl(): ?string // Return null if not configured, as webhooks are optional and may not be needed if the resolver URL is not set return null; } + + /** + * Fetch VPN watchdog status from m3u-proxy. + * + * @return array{success: bool, error?: string, vpn?: array} + */ + public function fetchVpnStatus(): array + { + if (empty($this->apiBaseUrl)) { + return [ + 'success' => false, + 'error' => 'M3U Proxy base URL is not configured', + ]; + } + + try { + $endpoint = $this->apiBaseUrl.'/vpn/status'; + $response = Http::timeout(5)->acceptJson() + ->withHeaders($this->apiToken ? [ + 'X-API-Token' => $this->apiToken, + ] : []) + ->get($endpoint); + + if ($response->status() === 404) { + return [ + 'success' => true, + 'vpn' => null, + ]; + } + + if ($response->successful()) { + return [ + 'success' => true, + 'vpn' => $response->json() ?: [], + ]; + } + + return [ + 'success' => false, + 'error' => 'M3U Proxy returned status '.$response->status(), + ]; + } catch (Exception $e) { + return [ + 'success' => false, + 'error' => 'Unable to connect to m3u-proxy: '.$e->getMessage(), + ]; + } + } + + /** + * Trigger a manual VPN rotation via m3u-proxy. + * + * @return array{success: bool, error?: string} + */ + public function triggerVpnRotation(): array + { + if (empty($this->apiBaseUrl)) { + return [ + 'success' => false, + 'error' => 'M3U Proxy base URL is not configured', + ]; + } + + try { + $endpoint = $this->apiBaseUrl.'/vpn/rotate'; + $response = Http::timeout(30)->acceptJson() + ->withHeaders($this->apiToken ? [ + 'X-API-Token' => $this->apiToken, + ] : []) + ->post($endpoint); + + if ($response->successful()) { + return [ + 'success' => true, + 'data' => $response->json() ?: [], + ]; + } + + return [ + 'success' => false, + 'error' => $response->json()['detail'] ?? 'VPN rotation failed', + ]; + } catch (Exception $e) { + return [ + 'success' => false, + 'error' => 'Unable to connect to m3u-proxy: '.$e->getMessage(), + ]; + } + } } diff --git a/resources/views/filament/pages/m3u-proxy-stream-monitor.blade.php b/resources/views/filament/pages/m3u-proxy-stream-monitor.blade.php index 7eb3c866d..2610a189c 100644 --- a/resources/views/filament/pages/m3u-proxy-stream-monitor.blade.php +++ b/resources/views/filament/pages/m3u-proxy-stream-monitor.blade.php @@ -67,6 +67,65 @@ + {{-- VPN Watchdog Status Card --}} + @if($vpnStatus) +
VPN Status
+Latency
++ {{ round($vpnStatus['latency_ms']) }} ms +
+Rotations
+{{ $vpnStatus['total_rotations'] }}
+