diff --git a/config/websockets.php b/config/websockets.php index bddb3d5633..a2e29b2808 100644 --- a/config/websockets.php +++ b/config/websockets.php @@ -239,37 +239,6 @@ 'delete_statistics_older_than_days' => 60, - /* - |-------------------------------------------------------------------------- - | DNS Lookup - |-------------------------------------------------------------------------- - | - | Use an DNS resolver to make the requests to the statistics logger - | default is to resolve everything to 127.0.0.1. - | - */ - - 'perform_dns_lookup' => false, - - /* - |-------------------------------------------------------------------------- - | DNS Lookup TLS Settings - |-------------------------------------------------------------------------- - | - | You can configure the DNS Lookup Connector the TLS settings. - | Check the available options here: - | https://github.com/reactphp/socket/blob/master/src/Connector.php#L29 - | - */ - - 'tls' => [ - - 'verify_peer' => env('APP_ENV') === 'production', - - 'verify_peer_name' => env('APP_ENV') === 'production', - - ], - ], ]; diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index 9b7a20091a..1121fadb6f 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -84,9 +84,29 @@ class="rounded-full px-3 py-2 text-white focus:outline-none" v-if="connected && app.statisticsEnabled" class="w-full my-6 px-6" > -
- Live statistics -
+
+ + Live statistics + + +
+
+ + Refresh automatically +
+ + +
+
{ if (event.error.data.code === 4100) { this.connected = false; this.logs = []; + this.chart = null; throw new Error("Over capacity"); } @@ -288,12 +326,12 @@ class="rounded-full px-3 py-1 inline-block text-sm" }); this.subscribeToAllChannels(); - this.subscribeToStatistics(); }, disconnect () { this.pusher.disconnect(); this.connecting = false; + this.chart = null; }, loadChart () { @@ -333,7 +371,10 @@ class="rounded-full px-3 py-1 inline-block text-sm" autosize: true, }; - this.chart = Plotly.newPlot('statisticsChart', chartData, layout); + this.chart = this.chart + ? Plotly.react('statisticsChart', chartData, layout) + : Plotly.newPlot('statisticsChart', chartData, layout); + }); }, @@ -348,18 +389,6 @@ class="rounded-full px-3 py-1 inline-block text-sm" }); }, - subscribeToStatistics () { - this.pusher.subscribe('{{ $logPrefix }}statistics') - .bind('statistics-updated', (data) => { - var update = { - x: [[data.time], [data.time], [data.time]], - y: [[data.peak_connection_count], [data.websocket_message_count], [data.api_message_count]], - }; - - Plotly.extendTraces('statisticsChart', update, [0, 1, 2]); - }); - }, - sendEvent () { if (! this.sendingEvent) { this.sendingEvent = true; @@ -415,6 +444,17 @@ class="rounded-full px-3 py-1 inline-block text-sm" return 'bg-gray-700 text-white'; }, + + startRefreshInterval () { + this.refreshTicker = setInterval(function () { + this.loadChart(); + }.bind(this), this.refreshInterval * 1000); + }, + + stopRefreshInterval () { + clearInterval(this.refreshTicker); + this.refreshTicker = null; + }, }, }); diff --git a/src/Console/StartWebSocketServer.php b/src/Console/StartWebSocketServer.php index 70932ba798..d6c4dcb4ed 100644 --- a/src/Console/StartWebSocketServer.php +++ b/src/Console/StartWebSocketServer.php @@ -11,17 +11,12 @@ use BeyondCode\LaravelWebSockets\Server\Logger\HttpLogger; use BeyondCode\LaravelWebSockets\Server\Logger\WebsocketsLogger; use BeyondCode\LaravelWebSockets\Server\WebSocketServerFactory; -use BeyondCode\LaravelWebSockets\Statistics\DnsResolver; +use BeyondCode\LaravelWebSockets\Statistics\Drivers\StatisticsDriver; use BeyondCode\LaravelWebSockets\Statistics\Logger\StatisticsLogger as StatisticsLoggerInterface; use BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager; -use Clue\React\Buzz\Browser; use Illuminate\Console\Command; use Illuminate\Support\Facades\Cache; -use React\Dns\Config\Config as DnsConfig; -use React\Dns\Resolver\Factory as DnsFactory; -use React\Dns\Resolver\ResolverInterface; use React\EventLoop\Factory as LoopFactory; -use React\Socket\Connector; class StartWebSocketServer extends Command { @@ -103,19 +98,12 @@ public function handle() */ protected function configureStatisticsLogger() { - $connector = new Connector($this->loop, [ - 'dns' => $this->getDnsResolver(), - 'tls' => config('websockets.statistics.tls'), - ]); - - $browser = new Browser($this->loop, $connector); - - $this->laravel->singleton(StatisticsLoggerInterface::class, function () use ($browser) { + $this->laravel->singleton(StatisticsLoggerInterface::class, function () { $class = config('websockets.statistics.logger', \BeyondCode\LaravelWebSockets\Statistics\Logger\MemoryStatisticsLogger::class); return new $class( $this->laravel->make(ChannelManager::class), - $browser + $this->laravel->make(StatisticsDriver::class) ); }); @@ -273,27 +261,6 @@ protected function buildServer() ->createServer(); } - /** - * Create a DNS resolver for the stats manager. - * - * @return \React\Dns\Resolver\ResolverInterface - */ - protected function getDnsResolver(): ResolverInterface - { - if (! config('websockets.statistics.perform_dns_lookup')) { - return new DnsResolver; - } - - $dnsConfig = DnsConfig::loadSystemConfigBlocking(); - - return (new DnsFactory)->createCached( - $dnsConfig->nameservers - ? reset($dnsConfig->nameservers) - : '1.1.1.1', - $this->loop - ); - } - /** * Get the last time the server restarted. * diff --git a/src/Dashboard/Http/Controllers/DashboardApiController.php b/src/Dashboard/Http/Controllers/DashboardApiController.php index 1e63fb9417..c240905b2d 100644 --- a/src/Dashboard/Http/Controllers/DashboardApiController.php +++ b/src/Dashboard/Http/Controllers/DashboardApiController.php @@ -2,45 +2,21 @@ namespace BeyondCode\LaravelWebSockets\Dashboard\Http\Controllers; +use BeyondCode\LaravelWebSockets\Statistics\Drivers\StatisticsDriver; +use Illuminate\Http\Request; + class DashboardApiController { /** * Get statistics for an app ID. * + * @param \Illuminate\Http\Request $request + * @param \BeyondCode\LaravelWebSockets\Statistics\Drivers\StatisticsDriver $driver * @param mixed $appId * @return \Illuminate\Http\Response */ - public function getStatistics($appId) + public function getStatistics(Request $request, StatisticsDriver $driver, $appId) { - $model = config('websockets.statistics.model'); - - $statistics = $model::where('app_id', $appId) - ->latest() - ->limit(120) - ->get(); - - $statisticData = $statistics->map(function ($statistic) { - return [ - 'timestamp' => (string) $statistic->created_at, - 'peak_connection_count' => $statistic->peak_connection_count, - 'websocket_message_count' => $statistic->websocket_message_count, - 'api_message_count' => $statistic->api_message_count, - ]; - })->reverse(); - - return [ - 'peak_connections' => [ - 'x' => $statisticData->pluck('timestamp'), - 'y' => $statisticData->pluck('peak_connection_count'), - ], - 'websocket_message_count' => [ - 'x' => $statisticData->pluck('timestamp'), - 'y' => $statisticData->pluck('websocket_message_count'), - ], - 'api_message_count' => [ - 'x' => $statisticData->pluck('timestamp'), - 'y' => $statisticData->pluck('api_message_count'), - ], - ]; + return $driver::get($appId, $request); } } diff --git a/src/Dashboard/Http/Controllers/ShowDashboard.php b/src/Dashboard/Http/Controllers/ShowDashboard.php index 8ce4208e8d..f6dc6b13ac 100644 --- a/src/Dashboard/Http/Controllers/ShowDashboard.php +++ b/src/Dashboard/Http/Controllers/ShowDashboard.php @@ -22,6 +22,7 @@ public function __invoke(Request $request, AppManager $apps) 'port' => config('websockets.dashboard.port', 6001), 'channels' => DashboardLogger::$channels, 'logPrefix' => DashboardLogger::LOG_CHANNEL_PREFIX, + 'refreshInterval' => config('websockets.statistics.interval_in_seconds'), ]); } } diff --git a/src/Statistics/Drivers/DatabaseDriver.php b/src/Statistics/Drivers/DatabaseDriver.php index a8d5175a85..cb5e353743 100644 --- a/src/Statistics/Drivers/DatabaseDriver.php +++ b/src/Statistics/Drivers/DatabaseDriver.php @@ -3,6 +3,7 @@ namespace BeyondCode\LaravelWebSockets\Statistics\Drivers; use Carbon\Carbon; +use Illuminate\Http\Request; class DatabaseDriver implements StatisticsDriver { @@ -87,6 +88,46 @@ public static function create(array $data): StatisticsDriver return new static($class::create($data)); } + /** + * Get the records to show to the dashboard. + * + * @param mixed $appId + * @param \Illuminate\Http\Request $request + * @return array + */ + public static function get($appId, Request $request): array + { + $class = config('websockets.statistics.database.model'); + + $statistics = $class::whereAppId($appId) + ->latest() + ->limit(120) + ->get() + ->map(function ($statistic) { + return [ + 'timestamp' => (string) $statistic->created_at, + 'peak_connection_count' => $statistic->peak_connection_count, + 'websocket_message_count' => $statistic->websocket_message_count, + 'api_message_count' => $statistic->api_message_count, + ]; + })->reverse(); + + return [ + 'peak_connections' => [ + 'x' => $statistics->pluck('timestamp'), + 'y' => $statistics->pluck('peak_connection_count'), + ], + 'websocket_message_count' => [ + 'x' => $statistics->pluck('timestamp'), + 'y' => $statistics->pluck('websocket_message_count'), + ], + 'api_message_count' => [ + 'x' => $statistics->pluck('timestamp'), + 'y' => $statistics->pluck('api_message_count'), + ], + ]; + } + /** * Delete statistics from the store, * optionally by app id, returning diff --git a/src/Statistics/Drivers/StatisticsDriver.php b/src/Statistics/Drivers/StatisticsDriver.php index 8ed1e5ec02..9b9cfb0452 100644 --- a/src/Statistics/Drivers/StatisticsDriver.php +++ b/src/Statistics/Drivers/StatisticsDriver.php @@ -2,6 +2,8 @@ namespace BeyondCode\LaravelWebSockets\Statistics\Drivers; +use Illuminate\Http\Request; + interface StatisticsDriver { /** @@ -55,6 +57,15 @@ public function getApiMessageCount(): int; */ public static function create(array $data): StatisticsDriver; + /** + * Get the records to show to the dashboard. + * + * @param mixed $appId + * @param \Illuminate\Http\Request $request + * @return void + */ + public static function get($appId, Request $request); + /** * Delete statistics from the store, * optionally by app id, returning diff --git a/src/Statistics/Events/StatisticsUpdated.php b/src/Statistics/Events/StatisticsUpdated.php deleted file mode 100644 index b3c76b49f1..0000000000 --- a/src/Statistics/Events/StatisticsUpdated.php +++ /dev/null @@ -1,73 +0,0 @@ -driver = $driver; - } - - /** - * Format the broadcasting message. - * - * @return array - */ - public function broadcastWith() - { - return [ - 'time' => $this->driver->getTime(), - 'app_id' => $this->driver->getAppId(), - 'peak_connection_count' => $this->driver->getPeakConnectionCount(), - 'websocket_message_count' => $this->driver->getWebsocketMessageCount(), - 'api_message_count' => $this->driver->getApiMessageCount(), - ]; - } - - /** - * Specify the channel to broadcast on. - * - * @return \Illuminate\Broadcasting\Channel - */ - public function broadcastOn() - { - $channelName = Str::after(DashboardLogger::LOG_CHANNEL_PREFIX.'statistics', 'private-'); - - return new PrivateChannel( - Str::after(DashboardLogger::LOG_CHANNEL_PREFIX.'statistics', 'private-') - ); - } - - /** - * Define the broadcasted event name. - * - * @return string - */ - public function broadcastAs() - { - return 'statistics-updated'; - } -} diff --git a/src/Statistics/Http/Controllers/WebSocketStatisticsEntriesController.php b/src/Statistics/Http/Controllers/WebSocketStatisticsEntriesController.php deleted file mode 100644 index bf9453bbe3..0000000000 --- a/src/Statistics/Http/Controllers/WebSocketStatisticsEntriesController.php +++ /dev/null @@ -1,34 +0,0 @@ -validate([ - 'app_id' => ['required', new AppId()], - 'peak_connection_count' => 'required|integer', - 'websocket_message_count' => 'required|integer', - 'api_message_count' => 'required|integer', - ]); - - broadcast(new StatisticsUpdated( - $driver::create($validatedAttributes) - )); - - return 'ok'; - } -} diff --git a/src/Statistics/Http/Middleware/Authorize.php b/src/Statistics/Http/Middleware/Authorize.php deleted file mode 100644 index cadd0d6958..0000000000 --- a/src/Statistics/Http/Middleware/Authorize.php +++ /dev/null @@ -1,22 +0,0 @@ -secret)) - ? abort(403) - : $next($request); - } -} diff --git a/src/Statistics/Logger/MemoryStatisticsLogger.php b/src/Statistics/Logger/MemoryStatisticsLogger.php index 224ddfec03..fe0ac82e5d 100644 --- a/src/Statistics/Logger/MemoryStatisticsLogger.php +++ b/src/Statistics/Logger/MemoryStatisticsLogger.php @@ -3,11 +3,9 @@ namespace BeyondCode\LaravelWebSockets\Statistics\Logger; use BeyondCode\LaravelWebSockets\Apps\App; -use BeyondCode\LaravelWebSockets\Statistics\Http\Controllers\WebSocketStatisticsEntriesController; +use BeyondCode\LaravelWebSockets\Statistics\Drivers\StatisticsDriver; use BeyondCode\LaravelWebSockets\Statistics\Statistic; use BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager; -use Clue\React\Buzz\Browser; -use function GuzzleHttp\Psr7\stream_for; use Ratchet\ConnectionInterface; class MemoryStatisticsLogger implements StatisticsLogger @@ -27,23 +25,23 @@ class MemoryStatisticsLogger implements StatisticsLogger protected $channelManager; /** - * The Browser instance. + * The statistics driver instance. * - * @var \Clue\React\Buzz\Browser + * @var \BeyondCode\LaravelWebSockets\Statistics\Drivers\StatisticsDriver */ - protected $browser; + protected $driver; /** * Initialize the logger. * * @param \BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager $channelManager - * @param \Clue\React\Buzz\Browser $browser + * @param \BeyondCode\LaravelWebSockets\Statistics\Drivers\StatisticsDriver $driver * @return void */ - public function __construct(ChannelManager $channelManager, Browser $browser) + public function __construct(ChannelManager $channelManager, StatisticsDriver $driver) { $this->channelManager = $channelManager; - $this->browser = $browser; + $this->driver = $driver; } /** @@ -106,16 +104,7 @@ public function save() continue; } - $postData = array_merge($statistic->toArray(), [ - 'secret' => App::findById($appId)->secret, - ]); - - $this->browser - ->post( - action([WebSocketStatisticsEntriesController::class, 'store']), - ['Content-Type' => 'application/json'], - stream_for(json_encode($postData)) - ); + $this->driver::create($statistic->toArray()); $currentConnectionCount = $this->channelManager->getConnectionCount($appId); diff --git a/src/Statistics/Logger/NullStatisticsLogger.php b/src/Statistics/Logger/NullStatisticsLogger.php index ee8728ef98..94e35475af 100644 --- a/src/Statistics/Logger/NullStatisticsLogger.php +++ b/src/Statistics/Logger/NullStatisticsLogger.php @@ -2,8 +2,8 @@ namespace BeyondCode\LaravelWebSockets\Statistics\Logger; +use BeyondCode\LaravelWebSockets\Statistics\Drivers\StatisticsDriver; use BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager; -use Clue\React\Buzz\Browser; use Ratchet\ConnectionInterface; class NullStatisticsLogger implements StatisticsLogger @@ -16,23 +16,23 @@ class NullStatisticsLogger implements StatisticsLogger protected $channelManager; /** - * The Browser instance. + * The statistics driver instance. * - * @var \Clue\React\Buzz\Browser + * @var \BeyondCode\LaravelWebSockets\Statistics\Drivers\StatisticsDriver */ - protected $browser; + protected $driver; /** * Initialize the logger. * * @param \BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager $channelManager - * @param \Clue\React\Buzz\Browser $browser + * @param \BeyondCode\LaravelWebSockets\Statistics\Drivers\StatisticsDriver $driver * @return void */ - public function __construct(ChannelManager $channelManager, Browser $browser) + public function __construct(ChannelManager $channelManager, StatisticsDriver $driver) { $this->channelManager = $channelManager; - $this->browser = $browser; + $this->driver = $driver; } /** diff --git a/src/WebSocketsServiceProvider.php b/src/WebSocketsServiceProvider.php index 297820fab0..09db7784ac 100644 --- a/src/WebSocketsServiceProvider.php +++ b/src/WebSocketsServiceProvider.php @@ -11,8 +11,6 @@ use BeyondCode\LaravelWebSockets\PubSub\Broadcasters\RedisPusherBroadcaster; use BeyondCode\LaravelWebSockets\Server\Router; use BeyondCode\LaravelWebSockets\Statistics\Drivers\StatisticsDriver; -use BeyondCode\LaravelWebSockets\Statistics\Http\Controllers\WebSocketStatisticsEntriesController; -use BeyondCode\LaravelWebSockets\Statistics\Http\Middleware\Authorize as AuthorizeStatistics; use BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager; use BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManagers\ArrayChannelManager; use Illuminate\Broadcasting\BroadcastManager; @@ -127,10 +125,6 @@ protected function registerDashboardRoutes() Route::post('auth', AuthenticateDashboard::class); Route::post('event', SendMessage::class); }); - - Route::middleware(AuthorizeStatistics::class)->group(function () { - Route::post('statistics', [WebSocketStatisticsEntriesController::class, 'store']); - }); }); return $this; diff --git a/tests/Statistics/Controllers/WebSocketsStatisticsControllerTest.php b/tests/Statistics/Controllers/WebSocketsStatisticsControllerTest.php deleted file mode 100644 index 360518f67a..0000000000 --- a/tests/Statistics/Controllers/WebSocketsStatisticsControllerTest.php +++ /dev/null @@ -1,42 +0,0 @@ -post( - action([WebSocketStatisticsEntriesController::class, 'store']), - array_merge($this->payload(), [ - 'secret' => config('websockets.apps.0.secret'), - ]) - ); - - $entries = WebSocketsStatisticsEntry::get(); - - $this->assertCount(1, $entries); - - $actual = $entries->first()->attributesToArray(); - - foreach ($this->payload() as $key => $value) { - $this->assertArrayHasKey($key, $actual); - $this->assertSame($value, $actual[$key]); - } - } - - protected function payload(): array - { - return [ - 'app_id' => config('websockets.apps.0.id'), - 'peak_connection_count' => '1', - 'websocket_message_count' => '2', - 'api_message_count' => '3', - ]; - } -} diff --git a/tests/TestCase.php b/tests/TestCase.php index 664bf28254..b0c7b7affa 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -6,13 +6,12 @@ use BeyondCode\LaravelWebSockets\PubSub\Drivers\LocalClient; use BeyondCode\LaravelWebSockets\PubSub\Drivers\RedisClient; use BeyondCode\LaravelWebSockets\PubSub\ReplicationInterface; +use BeyondCode\LaravelWebSockets\Statistics\Drivers\StatisticsDriver; use BeyondCode\LaravelWebSockets\Tests\Mocks\Connection; use BeyondCode\LaravelWebSockets\Tests\Mocks\Message; use BeyondCode\LaravelWebSockets\Tests\Statistics\Logger\FakeStatisticsLogger; use BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager; -use Clue\React\Buzz\Browser; use GuzzleHttp\Psr7\Request; -use Mockery; use Ratchet\ConnectionInterface; use React\EventLoop\Factory as LoopFactory; @@ -45,7 +44,7 @@ public function setUp(): void StatisticsLogger::swap(new FakeStatisticsLogger( $this->channelManager, - Mockery::mock(Browser::class) + app(StatisticsDriver::class) )); $this->loadMigrationsFrom(__DIR__.'/../database/migrations'); @@ -94,8 +93,6 @@ protected function getEnvironmentSetUp($app) ], ]); - $app['config']->set('websockets.statistics.perform_dns_lookup', true); - $app['config']->set('database.redis.default', [ 'host' => env('REDIS_HOST', '127.0.0.1'), 'password' => env('REDIS_PASSWORD', null),