diff --git a/src/Console/Commands/ServerInfo.php b/src/Console/Commands/ServerInfo.php new file mode 100644 index 0000000..f4f7248 --- /dev/null +++ b/src/Console/Commands/ServerInfo.php @@ -0,0 +1,163 @@ +option('live')) { + return $this->liveLoop(); + } + + $this->renderInfo(); + + return 0; + } + + protected function renderInfo(): void + { + $this->renderConnectionDetails(); + $this->newLine(); + $this->renderFrontendSecrets(); + $this->newLine(); + $this->renderStats(); + } + + protected function renderConnectionDetails(): void + { + $app = config('websockets.apps.0', []); + $port = config('websockets.port', config('websockets.dashboard.port', 6001)); + $host = $app['host'] ?? env('PUSHER_HOST', '127.0.0.1'); + $scheme = env('PUSHER_SCHEME', 'ws'); + + $appId = $app['id'] ?? env('PUSHER_APP_ID', '—'); + $appPath = $app['path'] ?? env('PUSHER_APP_PATH', ''); + + $internalUrl = "{$scheme}://0.0.0.0:{$port}/app/{$appId}"; + $externalUrl = "{$scheme}://{$host}:{$port}/app/{$appId}"; + + if ($appPath) { + $internalUrl .= "/{$appPath}"; + $externalUrl .= "/{$appPath}"; + } + + // Detect Traefik setup via APP_TRAEFIK env + $traefikHost = env('APP_TRAEFIK'); + $traefikUrl = null; + if ($traefikHost) { + $traefikScheme = $scheme === 'ws' ? 'ws' : 'wss'; + $traefikWsHost = "ws-{$traefikHost}"; + $traefikUrl = "{$traefikScheme}://{$traefikWsHost}/app/{$appId}"; + if ($appPath) { + $traefikUrl .= "/{$appPath}"; + } + } + + $this->components->twoColumnDetail('WebSocket Server'); + $this->components->twoColumnDetail('Internal URL', "{$internalUrl}"); + $this->components->twoColumnDetail('External URL', "{$externalUrl}"); + + if ($traefikUrl) { + $this->components->twoColumnDetail('Traefik URL', "{$traefikUrl}"); + } + + $this->components->twoColumnDetail('Port', (string) $port); + $this->components->twoColumnDetail('Broadcast socket', config('websockets.broadcast_socket_enabled') ? 'enabled' : 'disabled'); + + if (config('websockets.broadcast_socket_enabled')) { + $socketPath = config('websockets.broadcast_socket', '/tmp/laravel-websockets-broadcast.sock'); + $socketAvailable = ws_available(); + $this->components->twoColumnDetail( + 'Socket path', + $socketPath . ' ' . ($socketAvailable ? '(listening)' : '(not listening)') + ); + } + } + + protected function renderFrontendSecrets(): void + { + $app = config('websockets.apps.0', []); + $port = config('websockets.port', config('websockets.dashboard.port', 6001)); + + $key = $app['key'] ?? env('PUSHER_APP_KEY', '—'); + $host = $app['host'] ?? env('PUSHER_HOST', '127.0.0.1'); + $scheme = env('PUSHER_SCHEME', 'ws'); + $cluster = env('PUSHER_APP_CLUSTER', 'mt1'); + + $this->components->twoColumnDetail('Frontend Connection Secrets'); + $this->components->twoColumnDetail('PUSHER_APP_KEY', "{$key}"); + $this->components->twoColumnDetail('PUSHER_HOST', $host); + $this->components->twoColumnDetail('PUSHER_PORT', (string) $port); + $this->components->twoColumnDetail('PUSHER_SCHEME', $scheme); + $this->components->twoColumnDetail('PUSHER_APP_CLUSTER', $cluster); + + $traefikHost = env('APP_TRAEFIK'); + if ($traefikHost) { + $this->components->twoColumnDetail('Traefik WS host', "ws-{$traefikHost}"); + } + } + + protected function renderStats(): void + { + $channels = WebsocketService::getActiveChannels(); + $authedUsers = WebsocketService::getAuthedUsers(); + + $totalConnections = 0; + $channelData = []; + + foreach ($channels as $channel) { + $connections = WebsocketService::getChannelConnections($channel); + $count = count($connections); + $totalConnections += $count; + $channelData[] = [$channel, $count]; + } + + $uniqueUsers = count(array_unique(array_values($authedUsers))); + + $this->components->twoColumnDetail('Live Stats'); + $this->components->twoColumnDetail('Total connections', "{$totalConnections}"); + $this->components->twoColumnDetail('Authenticated users', "{$uniqueUsers}"); + $this->components->twoColumnDetail('Active channels', '' . count($channels) . ''); + + if (count($channelData) > 0) { + $this->newLine(); + $this->table(['Channel', 'Connections'], $channelData); + } else { + $this->newLine(); + $this->line(' No active channels.'); + } + } + + protected function liveLoop(): int + { + $interval = max(1, (int) $this->option('interval')); + + $this->info("Live mode — refreshing every {$interval}s. Press Ctrl+C to exit."); + $this->newLine(); + + while (true) { + // Clear screen + $this->output->write("\033[2J\033[H"); + + $this->line(' WebSocket Server — Live Stats'); + $this->line(' ' . now()->format('Y-m-d H:i:s') . ' — refreshing every ' . $interval . 's (Ctrl+C to exit)'); + $this->newLine(); + + $this->renderStats(); + + sleep($interval); + } + + return 0; // @phpstan-ignore-line + } +} diff --git a/src/WebSocketsServiceProvider.php b/src/WebSocketsServiceProvider.php index 9cdc736..099f739 100644 --- a/src/WebSocketsServiceProvider.php +++ b/src/WebSocketsServiceProvider.php @@ -58,6 +58,7 @@ class WebSocketsServiceProvider extends ServiceProvider Console\Commands\StartServer::class, Console\Commands\RestartServer::class, Console\Commands\SteerServer::class, + Console\Commands\ServerInfo::class, ]); }