From feb3071d4e5bfbcd280a71a5ccb524209a1b4781 Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Wed, 1 Jan 2025 20:15:14 +0100 Subject: [PATCH 01/31] Add Apprise --- .../SendAppriseTestNotification.php | 63 +++++++ .../Pages/Settings/NotificationPage.php | 73 ++++++++ .../SendSpeedtestCompletedNotification.php | 97 ++++++++++ .../SendSpeedtestThresholdNotification.php | 175 ++++++++++++++++++ app/Listeners/SpeedtestEventSubscriber.php | 21 ++- app/Settings/NotificationSettings.php | 8 + ..._31_164343_create_apprise_notification.php | 14 ++ .../apprise/speedtest-completed.blade.php | 13 ++ .../apprise/speedtest-threshold.blade.php | 9 + 9 files changed, 472 insertions(+), 1 deletion(-) create mode 100644 app/Actions/Notifications/SendAppriseTestNotification.php create mode 100644 app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php create mode 100644 app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php create mode 100644 database/settings/2024_12_31_164343_create_apprise_notification.php create mode 100644 resources/views/apprise/speedtest-completed.blade.php create mode 100644 resources/views/apprise/speedtest-threshold.blade.php diff --git a/app/Actions/Notifications/SendAppriseTestNotification.php b/app/Actions/Notifications/SendAppriseTestNotification.php new file mode 100644 index 000000000..d07fe9bb9 --- /dev/null +++ b/app/Actions/Notifications/SendAppriseTestNotification.php @@ -0,0 +1,63 @@ +title('You need to add Apprise webhooks!')->warning()->send(); + + return; + } + + $client = new Client; + + foreach ($webhooks as $webhook) { + $payload = [ + 'body' => 'πŸ‘‹ Testing the Apprise notification channel.', + ]; + + if ($webhook['notification_type'] === 'tags' && ! empty($webhook['tags'])) { + $tags = is_string($webhook['tags']) ? explode(',', $webhook['tags']) : $webhook['tags']; + $payload['tags'] = implode(',', array_map('trim', $tags)); + } elseif (! empty($webhook['service_url'])) { + $payload['urls'] = $webhook['service_url']; + } else { + Notification::make()->title('Webhook is missing either tags or service URL!')->warning()->send(); + + continue; + } + + try { + $response = $client->post(rtrim($webhook['url'], '/'), [ + 'form_params' => $payload, + ]); + + if ($response->getStatusCode() === 200) { + Notification::make()->title('Apprise notification sent successfully.')->success()->send(); + } else { + Notification::make() + ->title('Failed to send Apprise notification.') + ->warning() + ->body('HTTP Status: '.$response->getStatusCode()) + ->send(); + } + } catch (RequestException $e) { + Notification::make() + ->title('Failed to send Apprise notification.') + ->warning() + ->body($e->getMessage()) + ->send(); + } + } + } +} diff --git a/app/Filament/Pages/Settings/NotificationPage.php b/app/Filament/Pages/Settings/NotificationPage.php index bd7df5902..c7f6da566 100755 --- a/app/Filament/Pages/Settings/NotificationPage.php +++ b/app/Filament/Pages/Settings/NotificationPage.php @@ -2,6 +2,7 @@ namespace App\Filament\Pages\Settings; +use App\Actions\Notifications\SendAppriseTestNotification; use App\Actions\Notifications\SendDatabaseTestNotification; use App\Actions\Notifications\SendDiscordTestNotification; use App\Actions\Notifications\SendGotifyTestNotification; @@ -17,6 +18,7 @@ use Filament\Forms\Form; use Filament\Pages\SettingsPage; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\HtmlString; class NotificationPage extends SettingsPage { @@ -89,6 +91,77 @@ public function form(Form $form): Form 'md' => 2, ]), + Forms\Components\Section::make('Apprise') + ->description('The Apprise Notification Library enables sending notifications to a wide range of services.') + ->schema([ + Forms\Components\Toggle::make('apprise_enabled') + ->label('Enable Apprise Notifications') + ->reactive() + ->columnSpanFull(), + Forms\Components\Grid::make([ + 'default' => 1, + ]) + ->hidden(fn (Forms\Get $get) => $get('apprise_enabled') !== true) + ->schema([ + Forms\Components\Fieldset::make('Triggers') + ->schema([ + Forms\Components\Toggle::make('apprise_on_speedtest_run') + ->label('Notify on every speedtest run') + ->columnSpanFull(), + Forms\Components\Toggle::make('apprise_on_threshold_failure') + ->label('Notify on threshold failures') + ->columnSpanFull(), + ]), + + Forms\Components\Repeater::make('apprise_webhooks') + ->label('apprise Webhooks') + ->hint(new HtmlString('Apprise Documentation')) + ->schema([ + Forms\Components\TextInput::make('url') + ->label('URL') + ->placeholder('http://apprise:8000/notify/apprise') + ->maxLength(2000) + ->required() + ->url(), + Forms\Components\Radio::make('notification_type') + ->label('Notification Type') + ->options([ + 'service_url' => 'Service URL', + 'tags' => 'Tags', + ]) + ->default('service_url') + ->reactive() + ->required(), + Forms\Components\TextInput::make('service_url') + ->label('Service URL') + ->placeholder('discord://WebhookID/WebhookToken') + ->maxLength(200) + ->required() + ->visible(fn (callable $get) => $get('notification_type') === 'service_url'), + Forms\Components\TextInput::make('tags') + ->label('Tags') + ->placeholder('Homelab') + ->maxLength(200) + ->required() + ->visible(fn (callable $get) => $get('notification_type') === 'tags'), + ]) + ->columnSpanFull(), + Forms\Components\Actions::make([ + Forms\Components\Actions\Action::make('test apprise') + ->label('Test Apprise') + ->action(fn (Forms\Get $get) => SendAppriseTestNotification::run( + webhooks: $get('apprise_webhooks') + )) + ->hidden(fn (Forms\Get $get) => ! count($get('apprise_webhooks'))), + ]), + ]), + ]) + ->compact() + ->columns([ + 'default' => 1, + 'md' => 2, + ]), + Forms\Components\Section::make('Pushover') ->schema([ Forms\Components\Toggle::make('pushover_enabled') diff --git a/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php b/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php new file mode 100644 index 000000000..f72f0be25 --- /dev/null +++ b/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php @@ -0,0 +1,97 @@ +result = $result; + } + + /** + * Handle the event. + */ + public function handle(): void + { + // Resolve NotificationSettings from the service container + $notificationSettings = app(NotificationSettings::class); + + // Ensure we have at least one Apprise webhook URL + if (! count($notificationSettings->apprise_webhooks)) { + Log::warning('Apprise URLs not found, check Apprise notification channel settings.'); + + return; + } + + // Prepare the payload using the view + $payload = view('apprise.speedtest-completed', [ + 'id' => $this->result->id, + 'service' => Str::title($this->result->service->getLabel()), + 'serverName' => $this->result->server_name, + 'serverId' => $this->result->server_id, + 'isp' => $this->result->isp, + 'ping' => round($this->result->ping).' ms', + 'download' => Number::toBitRate(bits: $this->result->download_bits, precision: 2), + 'upload' => Number::toBitRate(bits: $this->result->upload_bits, precision: 2), + 'packetLoss' => $this->result->packet_loss, + 'speedtest_url' => $this->result->result_url, + 'url' => url('/admin/results'), + ])->render(); + + // Loop through the webhooks and send the notifications + foreach ($notificationSettings->apprise_webhooks as $webhook) { + // Build the payload for each webhook + $webhookPayload = [ + 'body' => $payload, + 'title' => 'Speedtest Completed', + 'type' => 'info', + ]; + + // Add tags if applicable + if ($webhook['notification_type'] === 'tags' && ! empty($webhook['tags'])) { + $tags = is_string($webhook['tags']) ? explode(',', $webhook['tags']) : $webhook['tags']; + $webhookPayload['tag'] = implode(',', array_map('trim', $tags)); + } + + // Add the service URL + if (! empty($webhook['service_url'])) { + $webhookPayload['urls'] = $webhook['service_url']; + } + + // Send the notification + try { + $client = new Client; + $response = $client->post($webhook['url'], [ + 'json' => $webhookPayload, + 'headers' => [ + 'Content-Type' => 'application/json', + ], + ]); + + // Optionally, log the response status for debugging + Log::info('Apprise notification sent successfully to '.$webhook['url']); + } catch (RequestException $e) { + Log::error('Apprise notification failed: '.$e->getMessage()); + } + } + } +} diff --git a/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php b/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php new file mode 100644 index 000000000..4fed40a66 --- /dev/null +++ b/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php @@ -0,0 +1,175 @@ +result = $result; + } + + /** + * Handle the event. + */ + public function handle(): void + { + // Resolve NotificationSettings from the service container + $notificationSettings = app(NotificationSettings::class); + + // Ensure we have at least one Apprise webhook URL + if (! count($notificationSettings->apprise_webhooks)) { + Log::warning('Apprise URLs not found, check Apprise notification channel settings.'); + + return; + } + + $thresholdSettings = app(ThresholdSettings::class); + + // Check if threshold notifications are enabled + if (! $thresholdSettings->absolute_enabled) { + + return; + } + + $failed = []; + + // Check for threshold breaches + if ($thresholdSettings->absolute_download > 0) { + array_push($failed, $this->absoluteDownloadThreshold($thresholdSettings)); + } + + if ($thresholdSettings->absolute_upload > 0) { + array_push($failed, $this->absoluteUploadThreshold($thresholdSettings)); + } + + if ($thresholdSettings->absolute_ping > 0) { + array_push($failed, $this->absolutePingThreshold($thresholdSettings)); + } + + $failed = array_filter($failed); + + // If no thresholds are breached, return early + if (! count($failed)) { + Log::warning('Failed apprise thresholds not found, won\'t send notification.'); + + return; + } + + // Prepare the payload using the view + $payload = view('apprise.speedtest-threshold', [ + 'id' => $this->result->id, + 'service' => Str::title($this->result->service->getLabel()), + 'serverName' => $this->result->server_name, + 'serverId' => $this->result->server_id, + 'isp' => $this->result->isp, + 'metrics' => $failed, + 'speedtest_url' => $this->result->result_url, + 'url' => url('/admin/results'), + ])->render(); + + // Loop through the webhooks and send the notifications + foreach ($notificationSettings->apprise_webhooks as $webhook) { + // Build the payload for each webhook + $webhookPayload = [ + 'body' => $payload, + 'title' => 'Speedtest Threshold Breach', + 'type' => 'info', + ]; + + // Add tags if applicable + if ($webhook['notification_type'] === 'tags' && ! empty($webhook['tags'])) { + $tags = is_string($webhook['tags']) ? explode(',', $webhook['tags']) : $webhook['tags']; + $webhookPayload['tag'] = implode(',', array_map('trim', $tags)); + } + + // Add the service URL + if (! empty($webhook['service_url'])) { + $webhookPayload['urls'] = $webhook['service_url']; + } + + // Send the notification + try { + $client = new Client; + $response = $client->post($webhook['url'], [ + 'json' => $webhookPayload, + 'headers' => [ + 'Content-Type' => 'application/json', + ], + ]); + + // Optionally, log the response status for debugging + Log::info('Apprise notification sent successfully to '.$webhook['url']); + } catch (RequestException $e) { + Log::error('Apprise notification failed: '.$e->getMessage()); + } + } + } + + /** + * Build apprise notification if absolute download threshold is breached. + */ + protected function absoluteDownloadThreshold(ThresholdSettings $thresholdSettings): bool|array + { + if (! absoluteDownloadThresholdFailed($thresholdSettings->absolute_download, $this->result->download)) { + return false; + } + + return [ + 'name' => 'Download', + 'threshold' => $thresholdSettings->absolute_download.' Mbps', + 'value' => Number::toBitRate(bits: $this->result->download_bits, precision: 2), + ]; + } + + /** + * Build apprise notification if absolute upload threshold is breached. + */ + protected function absoluteUploadThreshold(ThresholdSettings $thresholdSettings): bool|array + { + if (! absoluteUploadThresholdFailed($thresholdSettings->absolute_upload, $this->result->upload)) { + return false; + } + + return [ + 'name' => 'Upload', + 'threshold' => $thresholdSettings->absolute_upload.' Mbps', + 'value' => Number::toBitRate(bits: $this->result->upload_bits, precision: 2), + ]; + } + + /** + * Build apprise notification if absolute ping threshold is breached. + */ + protected function absolutePingThreshold(ThresholdSettings $thresholdSettings): bool|array + { + if (! absolutePingThresholdFailed($thresholdSettings->absolute_ping, $this->result->ping)) { + return false; + } + + return [ + 'name' => 'Ping', + 'threshold' => $thresholdSettings->absolute_ping.' ms', + 'value' => round($this->result->ping, 2).' ms', + ]; + } +} diff --git a/app/Listeners/SpeedtestEventSubscriber.php b/app/Listeners/SpeedtestEventSubscriber.php index 3588209c6..23b804554 100644 --- a/app/Listeners/SpeedtestEventSubscriber.php +++ b/app/Listeners/SpeedtestEventSubscriber.php @@ -5,7 +5,10 @@ use App\Events\SpeedtestCompleted; use App\Events\SpeedtestFailed; use App\Jobs\Influxdb\v2\WriteResult; +use App\Jobs\Notifications\Apprise\SendSpeedtestCompletedNotification as AppriseCompleted; +use App\Jobs\Notifications\Apprise\SendSpeedtestThresholdNotification as AppriseThresholds; use App\Settings\DataIntegrationSettings; +use App\Settings\NotificationSettings; use Illuminate\Events\Dispatcher; class SpeedtestEventSubscriber @@ -13,7 +16,10 @@ class SpeedtestEventSubscriber /** * Handle speedtest failed events. */ - public function handleSpeedtestFailed(SpeedtestFailed $event): void {} + public function handleSpeedtestFailed(SpeedtestFailed $event): void + { + // Handle failed event if necessary + } /** * Handle speedtest completed events. @@ -22,9 +28,22 @@ public function handleSpeedtestCompleted(SpeedtestCompleted $event): void { $settings = app(DataIntegrationSettings::class); + // Write to InfluxDB if enabled if ($settings->influxdb_v2_enabled) { WriteResult::dispatch($event->result); } + + $notificationSettings = app(NotificationSettings::class); + + // Send Apprise notification if the setting is enabled + if ($notificationSettings->apprise_on_speedtest_run) { + AppriseCompleted::dispatch($event->result); + } + + // Send threshold failure notification if the setting is enabled + if ($notificationSettings->apprise_on_threshold_failure) { + AppriseThresholds::dispatch($event->result); + } } /** diff --git a/app/Settings/NotificationSettings.php b/app/Settings/NotificationSettings.php index 0796be61a..c889aceeb 100644 --- a/app/Settings/NotificationSettings.php +++ b/app/Settings/NotificationSettings.php @@ -86,6 +86,14 @@ class NotificationSettings extends Settings public ?array $gotify_webhooks; + public bool $apprise_enabled; + + public bool $apprise_on_speedtest_run; + + public bool $apprise_on_threshold_failure; + + public ?array $apprise_webhooks; + public static function group(): string { return 'notification'; diff --git a/database/settings/2024_12_31_164343_create_apprise_notification.php b/database/settings/2024_12_31_164343_create_apprise_notification.php new file mode 100644 index 000000000..e4b2ccdc1 --- /dev/null +++ b/database/settings/2024_12_31_164343_create_apprise_notification.php @@ -0,0 +1,14 @@ +migrator->add('notification.apprise_enabled', false); + $this->migrator->add('notification.apprise_on_speedtest_run', false); + $this->migrator->add('notification.apprise_on_threshold_failure', false); + $this->migrator->add('notification.apprise_webhooks', null); + } +}; diff --git a/resources/views/apprise/speedtest-completed.blade.php b/resources/views/apprise/speedtest-completed.blade.php new file mode 100644 index 000000000..24c67a908 --- /dev/null +++ b/resources/views/apprise/speedtest-completed.blade.php @@ -0,0 +1,13 @@ +Speedtest Completed - #{{ $id }} + +A new speedtest on {{ config('app.name') }} was completed using {{ $service }}. + +Server name: {{ $serverName }} +Server ID: {{ $serverId }} +ISP: {{ $isp }} +Ping: {{ $ping }} +Download: {{ $download }} +Upload: {{ $upload }} +Packet Loss: {{ $packetLoss }} % +Ookla Speedtest: {{ $speedtest_url }} +URL: {{ $url }} diff --git a/resources/views/apprise/speedtest-threshold.blade.php b/resources/views/apprise/speedtest-threshold.blade.php new file mode 100644 index 000000000..8ad956c03 --- /dev/null +++ b/resources/views/apprise/speedtest-threshold.blade.php @@ -0,0 +1,9 @@ +Speedtest Threshold Breached - #{{ $id }} + +A new speedtest on **{{ config('app.name') }}** was completed using **{{ $service }}** on **{{ $isp }}** but a threshold was breached. + +@foreach ($metrics as $item) +- {{ $item['name'] }} {{ $item['threshold'] }}: {{ $item['value'] }} +@endforeach +- Ookla Speedtest: {{ $speedtest_url }} +- URL: {{ $url }} From 910229b8906933bc27a1fafa24c602c754d1a5c9 Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Sun, 27 Apr 2025 16:37:31 +0200 Subject: [PATCH 02/31] first step refactor --- .../SendAppriseTestNotification.php | 18 ++- .../SendWebhookTestNotification.php | 18 ++- .../Pages/Settings/NotificationPage.php | 25 +--- .../SendSpeedtestCompletedNotification.php | 25 ++-- .../SendSpeedtestThresholdNotification.php | 43 ++----- .../SendSpeedtestCompletedNotification.php | 16 ++- .../SendSpeedtestThresholdNotification.php | 99 +++++++++++++++ .../SendSpeedtestCompletedNotification.php | 46 +++++++ .../SendSpeedtestThresholdNotification.php | 119 ++++++++++++++++++ .../SendSpeedtestCompletedNotification.php | 58 +++++++++ .../SendSpeedtestThresholdNotification.php | 80 ++++++------ .../SendSpeedtestThresholdNotification.php | 101 --------------- .../SendSpeedtestCompletedNotification.php | 39 ------ .../SendSpeedtestThresholdNotification.php | 117 ----------------- app/Listeners/SpeedtestEventSubscriber.php | 42 ++++++- .../SendSpeedtestCompletedNotification.php | 51 -------- .../forms/notifications-deprecation.blade.php | 7 ++ 17 files changed, 468 insertions(+), 436 deletions(-) rename app/{Listeners => Jobs/Notifications}/Database/SendSpeedtestCompletedNotification.php (64%) create mode 100644 app/Jobs/Notifications/Database/SendSpeedtestThresholdNotification.php create mode 100644 app/Jobs/Notifications/Mail/SendSpeedtestCompletedNotification.php create mode 100644 app/Jobs/Notifications/Mail/SendSpeedtestThresholdNotification.php create mode 100644 app/Jobs/Notifications/Webhook/SendSpeedtestCompletedNotification.php rename app/{Listeners => Jobs/Notifications}/Webhook/SendSpeedtestThresholdNotification.php (50%) delete mode 100644 app/Listeners/Database/SendSpeedtestThresholdNotification.php delete mode 100644 app/Listeners/Mail/SendSpeedtestCompletedNotification.php delete mode 100644 app/Listeners/Mail/SendSpeedtestThresholdNotification.php delete mode 100644 app/Listeners/Webhook/SendSpeedtestCompletedNotification.php create mode 100644 resources/views/filament/forms/notifications-deprecation.blade.php diff --git a/app/Actions/Notifications/SendAppriseTestNotification.php b/app/Actions/Notifications/SendAppriseTestNotification.php index d07fe9bb9..54c935183 100644 --- a/app/Actions/Notifications/SendAppriseTestNotification.php +++ b/app/Actions/Notifications/SendAppriseTestNotification.php @@ -22,21 +22,17 @@ public function handle(array $webhooks) $client = new Client; foreach ($webhooks as $webhook) { - $payload = [ - 'body' => 'πŸ‘‹ Testing the Apprise notification channel.', - ]; - - if ($webhook['notification_type'] === 'tags' && ! empty($webhook['tags'])) { - $tags = is_string($webhook['tags']) ? explode(',', $webhook['tags']) : $webhook['tags']; - $payload['tags'] = implode(',', array_map('trim', $tags)); - } elseif (! empty($webhook['service_url'])) { - $payload['urls'] = $webhook['service_url']; - } else { - Notification::make()->title('Webhook is missing either tags or service URL!')->warning()->send(); + if (empty($webhook['service_url'])) { + Notification::make()->title('Webhook is missing service URL!')->warning()->send(); continue; } + $payload = [ + 'body' => 'πŸ‘‹ Testing the Apprise notification channel.', + 'urls' => $webhook['service_url'], + ]; + try { $response = $client->post(rtrim($webhook['url'], '/'), [ 'form_params' => $payload, diff --git a/app/Actions/Notifications/SendWebhookTestNotification.php b/app/Actions/Notifications/SendWebhookTestNotification.php index f5ada9a09..6b7ada378 100644 --- a/app/Actions/Notifications/SendWebhookTestNotification.php +++ b/app/Actions/Notifications/SendWebhookTestNotification.php @@ -2,6 +2,7 @@ namespace App\Actions\Notifications; +use App\Models\Result; use Filament\Notifications\Notification; use Lorisleiva\Actions\Concerns\AsAction; use Spatie\WebhookServer\WebhookCall; @@ -14,17 +15,30 @@ public function handle(array $webhooks) { if (! count($webhooks)) { Notification::make() - ->title('You need to add webhook urls!') + ->title('You need to add webhook URLs!') ->warning() ->send(); return; } + // Generate a fake Result (NOT saved to database) + $fakeResult = Result::factory()->make(); + foreach ($webhooks as $webhook) { WebhookCall::create() ->url($webhook['url']) - ->payload(['message' => 'πŸ‘‹ Testing the Webhook notification channel.']) + ->payload([ + 'result_id' => fake()->uuid(), + 'site_name' => config('app.name'), + 'isp' => $fakeResult->data['isp'], + 'ping' => $fakeResult->ping, + 'download' => $fakeResult->download, + 'upload' => $fakeResult->upload, + 'packetLoss' => $fakeResult->data['packetLoss'], + 'speedtest_url' => $fakeResult->data['result']['url'], + 'url' => url('/admin/results'), + ]) ->doNotSign() ->dispatch(); } diff --git a/app/Filament/Pages/Settings/NotificationPage.php b/app/Filament/Pages/Settings/NotificationPage.php index c7f6da566..2a8724b17 100755 --- a/app/Filament/Pages/Settings/NotificationPage.php +++ b/app/Filament/Pages/Settings/NotificationPage.php @@ -57,6 +57,8 @@ public function form(Form $form): Form 'default' => 1, ]) ->schema([ + Forms\Components\View::make('filament.forms.notifications-deprecation') + ->columnSpanFull(), Forms\Components\Section::make('Database') ->description('Notifications sent to this channel will show up under the πŸ”” icon in the header.') ->schema([ @@ -112,38 +114,23 @@ public function form(Form $form): Form ->label('Notify on threshold failures') ->columnSpanFull(), ]), - Forms\Components\Repeater::make('apprise_webhooks') - ->label('apprise Webhooks') + ->label('Apprise Webhooks') ->hint(new HtmlString('Apprise Documentation')) ->schema([ Forms\Components\TextInput::make('url') ->label('URL') ->placeholder('http://apprise:8000/notify/apprise') + ->helperText('The URL to your Apprise instance.') ->maxLength(2000) ->required() ->url(), - Forms\Components\Radio::make('notification_type') - ->label('Notification Type') - ->options([ - 'service_url' => 'Service URL', - 'tags' => 'Tags', - ]) - ->default('service_url') - ->reactive() - ->required(), Forms\Components\TextInput::make('service_url') ->label('Service URL') ->placeholder('discord://WebhookID/WebhookToken') + ->helperText('The service URL where the notification will be sent.') ->maxLength(200) - ->required() - ->visible(fn (callable $get) => $get('notification_type') === 'service_url'), - Forms\Components\TextInput::make('tags') - ->label('Tags') - ->placeholder('Homelab') - ->maxLength(200) - ->required() - ->visible(fn (callable $get) => $get('notification_type') === 'tags'), + ->required(), ]) ->columnSpanFull(), Forms\Components\Actions::make([ diff --git a/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php b/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php index f72f0be25..d37095b67 100644 --- a/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php +++ b/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php @@ -32,17 +32,14 @@ public function __construct(Result $result) */ public function handle(): void { - // Resolve NotificationSettings from the service container $notificationSettings = app(NotificationSettings::class); - // Ensure we have at least one Apprise webhook URL if (! count($notificationSettings->apprise_webhooks)) { Log::warning('Apprise URLs not found, check Apprise notification channel settings.'); return; } - // Prepare the payload using the view $payload = view('apprise.speedtest-completed', [ 'id' => $this->result->id, 'service' => Str::title($this->result->service->getLabel()), @@ -57,27 +54,20 @@ public function handle(): void 'url' => url('/admin/results'), ])->render(); - // Loop through the webhooks and send the notifications foreach ($notificationSettings->apprise_webhooks as $webhook) { - // Build the payload for each webhook + if (empty($webhook['service_url']) || empty($webhook['url'])) { + Log::warning('Webhook is missing service URL or URL, skipping.'); + + continue; + } + $webhookPayload = [ 'body' => $payload, 'title' => 'Speedtest Completed', 'type' => 'info', + 'urls' => $webhook['service_url'], ]; - // Add tags if applicable - if ($webhook['notification_type'] === 'tags' && ! empty($webhook['tags'])) { - $tags = is_string($webhook['tags']) ? explode(',', $webhook['tags']) : $webhook['tags']; - $webhookPayload['tag'] = implode(',', array_map('trim', $tags)); - } - - // Add the service URL - if (! empty($webhook['service_url'])) { - $webhookPayload['urls'] = $webhook['service_url']; - } - - // Send the notification try { $client = new Client; $response = $client->post($webhook['url'], [ @@ -87,7 +77,6 @@ public function handle(): void ], ]); - // Optionally, log the response status for debugging Log::info('Apprise notification sent successfully to '.$webhook['url']); } catch (RequestException $e) { Log::error('Apprise notification failed: '.$e->getMessage()); diff --git a/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php b/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php index 4fed40a66..9727d5b1a 100644 --- a/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php +++ b/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php @@ -33,10 +33,8 @@ public function __construct(Result $result) */ public function handle(): void { - // Resolve NotificationSettings from the service container $notificationSettings = app(NotificationSettings::class); - // Ensure we have at least one Apprise webhook URL if (! count($notificationSettings->apprise_webhooks)) { Log::warning('Apprise URLs not found, check Apprise notification channel settings.'); @@ -45,7 +43,6 @@ public function handle(): void $thresholdSettings = app(ThresholdSettings::class); - // Check if threshold notifications are enabled if (! $thresholdSettings->absolute_enabled) { return; @@ -53,7 +50,6 @@ public function handle(): void $failed = []; - // Check for threshold breaches if ($thresholdSettings->absolute_download > 0) { array_push($failed, $this->absoluteDownloadThreshold($thresholdSettings)); } @@ -68,14 +64,12 @@ public function handle(): void $failed = array_filter($failed); - // If no thresholds are breached, return early if (! count($failed)) { Log::warning('Failed apprise thresholds not found, won\'t send notification.'); return; } - // Prepare the payload using the view $payload = view('apprise.speedtest-threshold', [ 'id' => $this->result->id, 'service' => Str::title($this->result->service->getLabel()), @@ -87,29 +81,23 @@ public function handle(): void 'url' => url('/admin/results'), ])->render(); - // Loop through the webhooks and send the notifications + $client = new Client; + foreach ($notificationSettings->apprise_webhooks as $webhook) { - // Build the payload for each webhook + if (empty($webhook['service_url']) || empty($webhook['url'])) { + Log::warning('Webhook is missing service URL or URL, skipping.'); + + continue; + } + $webhookPayload = [ 'body' => $payload, 'title' => 'Speedtest Threshold Breach', 'type' => 'info', + 'urls' => $webhook['service_url'], ]; - // Add tags if applicable - if ($webhook['notification_type'] === 'tags' && ! empty($webhook['tags'])) { - $tags = is_string($webhook['tags']) ? explode(',', $webhook['tags']) : $webhook['tags']; - $webhookPayload['tag'] = implode(',', array_map('trim', $tags)); - } - - // Add the service URL - if (! empty($webhook['service_url'])) { - $webhookPayload['urls'] = $webhook['service_url']; - } - - // Send the notification try { - $client = new Client; $response = $client->post($webhook['url'], [ 'json' => $webhookPayload, 'headers' => [ @@ -117,7 +105,6 @@ public function handle(): void ], ]); - // Optionally, log the response status for debugging Log::info('Apprise notification sent successfully to '.$webhook['url']); } catch (RequestException $e) { Log::error('Apprise notification failed: '.$e->getMessage()); @@ -125,12 +112,10 @@ public function handle(): void } } - /** - * Build apprise notification if absolute download threshold is breached. - */ protected function absoluteDownloadThreshold(ThresholdSettings $thresholdSettings): bool|array { if (! absoluteDownloadThresholdFailed($thresholdSettings->absolute_download, $this->result->download)) { + return false; } @@ -141,12 +126,10 @@ protected function absoluteDownloadThreshold(ThresholdSettings $thresholdSetting ]; } - /** - * Build apprise notification if absolute upload threshold is breached. - */ protected function absoluteUploadThreshold(ThresholdSettings $thresholdSettings): bool|array { if (! absoluteUploadThresholdFailed($thresholdSettings->absolute_upload, $this->result->upload)) { + return false; } @@ -157,12 +140,10 @@ protected function absoluteUploadThreshold(ThresholdSettings $thresholdSettings) ]; } - /** - * Build apprise notification if absolute ping threshold is breached. - */ protected function absolutePingThreshold(ThresholdSettings $thresholdSettings): bool|array { if (! absolutePingThresholdFailed($thresholdSettings->absolute_ping, $this->result->ping)) { + return false; } diff --git a/app/Listeners/Database/SendSpeedtestCompletedNotification.php b/app/Jobs/Notifications/Database/SendSpeedtestCompletedNotification.php similarity index 64% rename from app/Listeners/Database/SendSpeedtestCompletedNotification.php rename to app/Jobs/Notifications/Database/SendSpeedtestCompletedNotification.php index 14ea66605..7506a1f14 100644 --- a/app/Listeners/Database/SendSpeedtestCompletedNotification.php +++ b/app/Jobs/Notifications/Database/SendSpeedtestCompletedNotification.php @@ -1,26 +1,32 @@ database_enabled) { + return; } if (! $notificationSettings->database_on_speedtest_run) { + return; } diff --git a/app/Jobs/Notifications/Database/SendSpeedtestThresholdNotification.php b/app/Jobs/Notifications/Database/SendSpeedtestThresholdNotification.php new file mode 100644 index 000000000..1747b5c79 --- /dev/null +++ b/app/Jobs/Notifications/Database/SendSpeedtestThresholdNotification.php @@ -0,0 +1,99 @@ +result = $result; + } + + /** + * Handle the job. + */ + public function handle(): void + { + $thresholdSettings = new ThresholdSettings; + + if (! $thresholdSettings->absolute_enabled) { + return; + } + + if ($thresholdSettings->absolute_download > 0) { + $this->absoluteDownloadThreshold($thresholdSettings); + } + + if ($thresholdSettings->absolute_upload > 0) { + $this->absoluteUploadThreshold($thresholdSettings); + } + + if ($thresholdSettings->absolute_ping > 0) { + $this->absolutePingThreshold($thresholdSettings); + } + } + + protected function absoluteDownloadThreshold(ThresholdSettings $thresholdSettings): void + { + if (! absoluteDownloadThresholdFailed($thresholdSettings->absolute_download, $this->result->download)) { + + return; + } + + foreach (User::all() as $user) { + Notification::make() + ->title('Download threshold breached!') + ->body('Speedtest #'.$this->result->id.' breached the download threshold of '.$thresholdSettings->absolute_download.' Mbps at '.Number::toBitRate($this->result->download_bits).'.') + ->warning() + ->sendToDatabase($user); + } + } + + protected function absoluteUploadThreshold(ThresholdSettings $thresholdSettings): void + { + if (! absoluteUploadThresholdFailed($thresholdSettings->absolute_upload, $this->result->upload)) { + + return; + } + + foreach (User::all() as $user) { + Notification::make() + ->title('Upload threshold breached!') + ->body('Speedtest #'.$this->result->id.' breached the upload threshold of '.$thresholdSettings->absolute_upload.' Mbps at '.Number::toBitRate($this->result->upload_bits).'.') + ->warning() + ->sendToDatabase($user); + } + } + + protected function absolutePingThreshold(ThresholdSettings $thresholdSettings): void + { + if (! absolutePingThresholdFailed($thresholdSettings->absolute_ping, $this->result->ping)) { + + return; + } + + foreach (User::all() as $user) { + Notification::make() + ->title('Ping threshold breached!') + ->body('Speedtest #'.$this->result->id.' breached the ping threshold of '.$thresholdSettings->absolute_ping.'ms at '.$this->result->ping.'ms.') + ->warning() + ->sendToDatabase($user); + } + } +} diff --git a/app/Jobs/Notifications/Mail/SendSpeedtestCompletedNotification.php b/app/Jobs/Notifications/Mail/SendSpeedtestCompletedNotification.php new file mode 100644 index 000000000..2292571eb --- /dev/null +++ b/app/Jobs/Notifications/Mail/SendSpeedtestCompletedNotification.php @@ -0,0 +1,46 @@ +result = $result; + } + + /** + * Handle the job. + */ + public function handle(): void + { + $notificationSettings = new NotificationSettings; + + if (! count($notificationSettings->mail_recipients)) { + Log::warning('Mail recipients not found, check mail notification channel settings.'); + + return; + } + + foreach ($notificationSettings->mail_recipients as $recipient) { + Mail::to($recipient) + ->send(new SpeedtestCompletedMail($this->result)); + } + } +} diff --git a/app/Jobs/Notifications/Mail/SendSpeedtestThresholdNotification.php b/app/Jobs/Notifications/Mail/SendSpeedtestThresholdNotification.php new file mode 100644 index 000000000..364795c55 --- /dev/null +++ b/app/Jobs/Notifications/Mail/SendSpeedtestThresholdNotification.php @@ -0,0 +1,119 @@ +result = $result; + } + + /** + * Handle the job. + */ + public function handle(): void + { + $notificationSettings = new NotificationSettings; + + if (! count($notificationSettings->mail_recipients)) { + Log::warning('Mail recipients not found, check mail notification channel settings.'); + + return; + } + + $thresholdSettings = new ThresholdSettings; + + if (! $thresholdSettings->absolute_enabled) { + + return; + } + + $failed = []; + + if ($thresholdSettings->absolute_download > 0) { + array_push($failed, $this->absoluteDownloadThreshold($thresholdSettings)); + } + + if ($thresholdSettings->absolute_upload > 0) { + array_push($failed, $this->absoluteUploadThreshold($thresholdSettings)); + } + + if ($thresholdSettings->absolute_ping > 0) { + array_push($failed, $this->absolutePingThreshold($thresholdSettings)); + } + + $failed = array_filter($failed); + + if (! count($failed)) { + Log::warning('No threshold breaches found, skipping mail notification.'); + + return; + } + + foreach ($notificationSettings->mail_recipients as $recipient) { + Mail::to($recipient) + ->send(new SpeedtestThresholdMail($this->result, $failed)); + } + } + + protected function absoluteDownloadThreshold(ThresholdSettings $thresholdSettings): bool|array + { + if (! absoluteDownloadThresholdFailed($thresholdSettings->absolute_download, $this->result->download)) { + + return false; + } + + return [ + 'name' => 'Download', + 'threshold' => $thresholdSettings->absolute_download.' Mbps', + 'value' => Number::toBitRate(bits: $this->result->download_bits, precision: 2), + ]; + } + + protected function absoluteUploadThreshold(ThresholdSettings $thresholdSettings): bool|array + { + if (! absoluteUploadThresholdFailed($thresholdSettings->absolute_upload, $this->result->upload)) { + + return false; + } + + return [ + 'name' => 'Upload', + 'threshold' => $thresholdSettings->absolute_upload.' Mbps', + 'value' => Number::toBitRate(bits: $this->result->upload_bits, precision: 2), + ]; + } + + protected function absolutePingThreshold(ThresholdSettings $thresholdSettings): bool|array + { + if (! absolutePingThresholdFailed($thresholdSettings->absolute_ping, $this->result->ping)) { + + return false; + } + + return [ + 'name' => 'Ping', + 'threshold' => $thresholdSettings->absolute_ping.' ms', + 'value' => round($this->result->ping, 2).' ms', + ]; + } +} diff --git a/app/Jobs/Notifications/Webhook/SendSpeedtestCompletedNotification.php b/app/Jobs/Notifications/Webhook/SendSpeedtestCompletedNotification.php new file mode 100644 index 000000000..8ba65ab7e --- /dev/null +++ b/app/Jobs/Notifications/Webhook/SendSpeedtestCompletedNotification.php @@ -0,0 +1,58 @@ +result = $result; + } + + /** + * Handle the job. + */ + public function handle(): void + { + $notificationSettings = new NotificationSettings; + + if (! count($notificationSettings->webhook_urls)) { + Log::warning('Webhook URLs not found, check webhook notification channel settings.'); + + return; + } + + foreach ($notificationSettings->webhook_urls as $url) { + WebhookCall::create() + ->url($url['url']) + ->payload([ + 'result_id' => $this->result->id, + 'site_name' => config('app.name'), + 'isp' => $this->result->isp, + 'ping' => $this->result->ping, + 'download' => $this->result->downloadBits, + 'upload' => $this->result->uploadBits, + 'packetLoss' => $this->result->packet_loss, + 'speedtest_url' => $this->result->result_url, + 'url' => url('/admin/results'), + ]) + ->doNotSign() + ->dispatch(); + } + } +} diff --git a/app/Listeners/Webhook/SendSpeedtestThresholdNotification.php b/app/Jobs/Notifications/Webhook/SendSpeedtestThresholdNotification.php similarity index 50% rename from app/Listeners/Webhook/SendSpeedtestThresholdNotification.php rename to app/Jobs/Notifications/Webhook/SendSpeedtestThresholdNotification.php index bb64866b4..fb35970f6 100644 --- a/app/Listeners/Webhook/SendSpeedtestThresholdNotification.php +++ b/app/Jobs/Notifications/Webhook/SendSpeedtestThresholdNotification.php @@ -1,33 +1,40 @@ webhook_enabled) { - return; - } + $this->result = $result; + } - if (! $notificationSettings->webhook_on_threshold_failure) { - return; - } + /** + * Handle the job. + */ + public function handle(): void + { + $notificationSettings = new NotificationSettings; if (! count($notificationSettings->webhook_urls)) { - Log::warning('Webhook urls not found, check webhook notification channel settings.'); + Log::warning('Webhook URLs not found, check webhook notification channel settings.'); return; } @@ -35,27 +42,28 @@ public function handle(SpeedtestCompleted $event): void $thresholdSettings = new ThresholdSettings; if (! $thresholdSettings->absolute_enabled) { + return; } $failed = []; if ($thresholdSettings->absolute_download > 0) { - array_push($failed, $this->absoluteDownloadThreshold(event: $event, thresholdSettings: $thresholdSettings)); + array_push($failed, $this->absoluteDownloadThreshold($thresholdSettings)); } if ($thresholdSettings->absolute_upload > 0) { - array_push($failed, $this->absoluteUploadThreshold(event: $event, thresholdSettings: $thresholdSettings)); + array_push($failed, $this->absoluteUploadThreshold($thresholdSettings)); } if ($thresholdSettings->absolute_ping > 0) { - array_push($failed, $this->absolutePingThreshold(event: $event, thresholdSettings: $thresholdSettings)); + array_push($failed, $this->absolutePingThreshold($thresholdSettings)); } $failed = array_filter($failed); if (! count($failed)) { - Log::warning('Failed webhook thresholds not found, won\'t send notification.'); + Log::warning('No threshold breaches found, skipping webhook notification.'); return; } @@ -64,11 +72,11 @@ public function handle(SpeedtestCompleted $event): void WebhookCall::create() ->url($url['url']) ->payload([ - 'result_id' => $event->result->id, + 'result_id' => $this->result->id, 'site_name' => config('app.name'), - 'isp' => $event->result->isp, + 'isp' => $this->result->isp, 'metrics' => $failed, - 'speedtest_url' => $event->result->result_url, + 'speedtest_url' => $this->result->result_url, 'url' => url('/admin/results'), ]) ->doNotSign() @@ -76,51 +84,45 @@ public function handle(SpeedtestCompleted $event): void } } - /** - * Build webhook notification if absolute download threshold is breached. - */ - protected function absoluteDownloadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array + protected function absoluteDownloadThreshold(ThresholdSettings $thresholdSettings): bool|array { - if (! absoluteDownloadThresholdFailed($thresholdSettings->absolute_download, $event->result->download)) { + if (! absoluteDownloadThresholdFailed($thresholdSettings->absolute_download, $this->result->download)) { + return false; } return [ 'name' => 'Download', 'threshold' => $thresholdSettings->absolute_download.' Mbps', - 'value' => Number::toBitRate(bits: $event->result->download_bits, precision: 2), + 'value' => Number::toBitRate(bits: $this->result->download_bits, precision: 2), ]; } - /** - * Build webhook notification if absolute upload threshold is breached. - */ - protected function absoluteUploadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array + protected function absoluteUploadThreshold(ThresholdSettings $thresholdSettings): bool|array { - if (! absoluteUploadThresholdFailed($thresholdSettings->absolute_upload, $event->result->upload)) { + if (! absoluteUploadThresholdFailed($thresholdSettings->absolute_upload, $this->result->upload)) { + return false; } return [ 'name' => 'Upload', 'threshold' => $thresholdSettings->absolute_upload.' Mbps', - 'value' => Number::toBitRate(bits: $event->result->upload_bits, precision: 2), + 'value' => Number::toBitRate(bits: $this->result->upload_bits, precision: 2), ]; } - /** - * Build webhook notification if absolute ping threshold is breached. - */ - protected function absolutePingThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array + protected function absolutePingThreshold(ThresholdSettings $thresholdSettings): bool|array { - if (! absolutePingThresholdFailed($thresholdSettings->absolute_ping, $event->result->ping)) { + if (! absolutePingThresholdFailed($thresholdSettings->absolute_ping, $this->result->ping)) { + return false; } return [ 'name' => 'Ping', 'threshold' => $thresholdSettings->absolute_ping.' ms', - 'value' => round($event->result->ping, 2).' ms', + 'value' => round($this->result->ping, 2).' ms', ]; } } diff --git a/app/Listeners/Database/SendSpeedtestThresholdNotification.php b/app/Listeners/Database/SendSpeedtestThresholdNotification.php deleted file mode 100644 index 7439b52f1..000000000 --- a/app/Listeners/Database/SendSpeedtestThresholdNotification.php +++ /dev/null @@ -1,101 +0,0 @@ -database_enabled) { - return; - } - - if (! $notificationSettings->database_on_threshold_failure) { - return; - } - - $thresholdSettings = new ThresholdSettings; - - if (! $thresholdSettings->absolute_enabled) { - return; - } - - if ($thresholdSettings->absolute_download > 0) { - $this->absoluteDownloadThreshold(event: $event, thresholdSettings: $thresholdSettings); - } - - if ($thresholdSettings->absolute_upload > 0) { - $this->absoluteUploadThreshold(event: $event, thresholdSettings: $thresholdSettings); - } - - if ($thresholdSettings->absolute_ping > 0) { - $this->absolutePingThreshold(event: $event, thresholdSettings: $thresholdSettings); - } - } - - /** - * Send database notification if absolute download threshold is breached. - */ - protected function absoluteDownloadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): void - { - if (! absoluteDownloadThresholdFailed($thresholdSettings->absolute_download, $event->result->download)) { - return; - } - - foreach (User::all() as $user) { - Notification::make() - ->title('Download threshold breached!') - ->body('Speedtest #'.$event->result->id.' breached the download threshold of '.$thresholdSettings->absolute_download.' Mbps at '.Number::toBitRate($event->result->download_bits).'.') - ->warning() - ->sendToDatabase($user); - } - } - - /** - * Send database notification if absolute upload threshold is breached. - */ - protected function absoluteUploadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): void - { - if (! absoluteUploadThresholdFailed($thresholdSettings->absolute_upload, $event->result->upload)) { - return; - } - - foreach (User::all() as $user) { - Notification::make() - ->title('Upload threshold breached!') - ->body('Speedtest #'.$event->result->id.' breached the upload threshold of '.$thresholdSettings->absolute_upload.' Mbps at '.Number::toBitRate($event->result->upload_bits).'.') - ->warning() - ->sendToDatabase($user); - } - } - - /** - * Send database notification if absolute upload threshold is breached. - */ - protected function absolutePingThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): void - { - if (! absolutePingThresholdFailed($thresholdSettings->absolute_ping, $event->result->ping)) { - return; - } - - foreach (User::all() as $user) { - Notification::make() - ->title('Ping threshold breached!') - ->body('Speedtest #'.$event->result->id.' breached the ping threshold of '.$thresholdSettings->absolute_ping.'ms at '.$event->result->ping.'ms.') - ->warning() - ->sendToDatabase($user); - } - } -} diff --git a/app/Listeners/Mail/SendSpeedtestCompletedNotification.php b/app/Listeners/Mail/SendSpeedtestCompletedNotification.php deleted file mode 100644 index 2e731cd99..000000000 --- a/app/Listeners/Mail/SendSpeedtestCompletedNotification.php +++ /dev/null @@ -1,39 +0,0 @@ -mail_enabled) { - return; - } - - if (! $notificationSettings->mail_on_speedtest_run) { - return; - } - - if (! count($notificationSettings->mail_recipients)) { - Log::warning('Mail recipients not found, check mail notification channel settings.'); - - return; - } - - foreach ($notificationSettings->mail_recipients as $recipient) { - Mail::to($recipient) - ->send(new SpeedtestCompletedMail($event->result)); - } - } -} diff --git a/app/Listeners/Mail/SendSpeedtestThresholdNotification.php b/app/Listeners/Mail/SendSpeedtestThresholdNotification.php deleted file mode 100644 index 774851df5..000000000 --- a/app/Listeners/Mail/SendSpeedtestThresholdNotification.php +++ /dev/null @@ -1,117 +0,0 @@ -mail_enabled) { - return; - } - - if (! $notificationSettings->mail_on_threshold_failure) { - return; - } - - if (! count($notificationSettings->mail_recipients) > 0) { - Log::warning('Mail recipients not found, check mail notification channel settings.'); - - return; - } - - $thresholdSettings = new ThresholdSettings; - - if (! $thresholdSettings->absolute_enabled) { - return; - } - - $failed = []; - - if ($thresholdSettings->absolute_download > 0) { - array_push($failed, $this->absoluteDownloadThreshold(event: $event, thresholdSettings: $thresholdSettings)); - } - - if ($thresholdSettings->absolute_upload > 0) { - array_push($failed, $this->absoluteUploadThreshold(event: $event, thresholdSettings: $thresholdSettings)); - } - - if ($thresholdSettings->absolute_ping > 0) { - array_push($failed, $this->absolutePingThreshold(event: $event, thresholdSettings: $thresholdSettings)); - } - - $failed = array_filter($failed); - - if (! count($failed)) { - Log::warning('Failed mail thresholds not found, won\'t send notification.'); - - return; - } - - foreach ($notificationSettings->mail_recipients as $recipient) { - Mail::to($recipient) - ->send(new SpeedtestThresholdMail($event->result, $failed)); - } - } - - /** - * Build mail notification if absolute download threshold is breached. - */ - protected function absoluteDownloadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array - { - if (! absoluteDownloadThresholdFailed($thresholdSettings->absolute_download, $event->result->download)) { - return false; - } - - return [ - 'name' => 'Download', - 'threshold' => $thresholdSettings->absolute_download.' Mbps', - 'value' => Number::toBitRate(bits: $event->result->download_bits, precision: 2), - ]; - } - - /** - * Build mail notification if absolute upload threshold is breached. - */ - protected function absoluteUploadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array - { - if (! absoluteUploadThresholdFailed($thresholdSettings->absolute_upload, $event->result->upload)) { - return false; - } - - return [ - 'name' => 'Upload', - 'threshold' => $thresholdSettings->absolute_upload.' Mbps', - 'value' => Number::toBitRate(bits: $event->result->upload_bits, precision: 2), - ]; - } - - /** - * Build mail notification if absolute ping threshold is breached. - */ - protected function absolutePingThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array - { - if (! absolutePingThresholdFailed($thresholdSettings->absolute_ping, $event->result->ping)) { - return false; - } - - return [ - 'name' => 'Ping', - 'threshold' => $thresholdSettings->absolute_ping.' ms', - 'value' => round($event->result->ping, 2).' ms', - ]; - } -} diff --git a/app/Listeners/SpeedtestEventSubscriber.php b/app/Listeners/SpeedtestEventSubscriber.php index 23b804554..0a28a7a28 100644 --- a/app/Listeners/SpeedtestEventSubscriber.php +++ b/app/Listeners/SpeedtestEventSubscriber.php @@ -7,6 +7,12 @@ use App\Jobs\Influxdb\v2\WriteResult; use App\Jobs\Notifications\Apprise\SendSpeedtestCompletedNotification as AppriseCompleted; use App\Jobs\Notifications\Apprise\SendSpeedtestThresholdNotification as AppriseThresholds; +use App\Jobs\Notifications\Database\SendSpeedtestCompletedNotification as DatabaseCompleted; +use App\Jobs\Notifications\Database\SendSpeedtestThresholdNotification as DatabaseThresholds; +use App\Jobs\Notifications\Mail\SendSpeedtestCompletedNotification as MailCompleted; +use App\Jobs\Notifications\Mail\SendSpeedtestThresholdNotification as MailThresholds; +use App\Jobs\Notifications\Webhook\SendSpeedtestCompletedNotification as WebhookCompleted; +use App\Jobs\Notifications\Webhook\SendSpeedtestThresholdNotification as WebhookThresholds; use App\Settings\DataIntegrationSettings; use App\Settings\NotificationSettings; use Illuminate\Events\Dispatcher; @@ -36,14 +42,44 @@ public function handleSpeedtestCompleted(SpeedtestCompleted $event): void $notificationSettings = app(NotificationSettings::class); // Send Apprise notification if the setting is enabled - if ($notificationSettings->apprise_on_speedtest_run) { + if ($notificationSettings->apprise_enabled && $notificationSettings->apprise_on_speedtest_run) { AppriseCompleted::dispatch($event->result); } - // Send threshold failure notification if the setting is enabled - if ($notificationSettings->apprise_on_threshold_failure) { + // Send Databse notification if the setting is enabled + if ($notificationSettings->database_enabled && $notificationSettings->database_on_speedtest_run) { + DatabaseCompleted::dispatch($event->result); + } + + // Send Webhook notification if the setting is enabled + if ($notificationSettings->webhook_enabled && $notificationSettings->webhook_on_speedtest_run) { + WebhookCompleted::dispatch($event->result); + } + + // Send Mail notification if the setting is enabled + if ($notificationSettings->mail_enabled && $notificationSettings->mail_on_speedtest_run) { + MailCompleted::dispatch($event->result); + } + + // Send Apprise threshold failure notification if the setting is enabled + if ($notificationSettings->apprise_enabled && $notificationSettings->apprise_on_threshold_failure) { AppriseThresholds::dispatch($event->result); } + + // Send Database threshold failure notification if the setting is enabled + if ($notificationSettings->database_enabled && $notificationSettings->database_on_threshold_failure) { + DatabaseThresholds::dispatch($event->result); + } + + // Send Webhook threshold failure notification if the setting is enabled + if ($notificationSettings->webhook_enabled && $notificationSettings->webhook_on_threshold_failure) { + WebhookThresholds::dispatch($event->result); + } + + // Send Mail threshold failure notification if the setting is enabled + if ($notificationSettings->mail_enabled && $notificationSettings->mail_on_threshold_failure) { + MailThresholds::dispatch($event->result); + } } /** diff --git a/app/Listeners/Webhook/SendSpeedtestCompletedNotification.php b/app/Listeners/Webhook/SendSpeedtestCompletedNotification.php deleted file mode 100644 index 85d42d2b9..000000000 --- a/app/Listeners/Webhook/SendSpeedtestCompletedNotification.php +++ /dev/null @@ -1,51 +0,0 @@ -webhook_enabled) { - return; - } - - if (! $notificationSettings->webhook_on_speedtest_run) { - return; - } - - if (! count($notificationSettings->webhook_urls)) { - Log::warning('Webhook urls not found, check webhook notification channel settings.'); - - return; - } - - foreach ($notificationSettings->webhook_urls as $url) { - WebhookCall::create() - ->url($url['url']) - ->payload([ - 'result_id' => $event->result->id, - 'site_name' => config('app.name'), - 'isp' => $event->result->isp, - 'ping' => $event->result->ping, - 'download' => $event->result->downloadBits, - 'upload' => $event->result->uploadBits, - 'packetLoss' => $event->result->packet_loss, - 'speedtest_url' => $event->result->result_url, - 'url' => url('/admin/results'), - ]) - ->doNotSign() - ->dispatch(); - } - } -} diff --git a/resources/views/filament/forms/notifications-deprecation.blade.php b/resources/views/filament/forms/notifications-deprecation.blade.php new file mode 100644 index 000000000..f41f2dd5b --- /dev/null +++ b/resources/views/filament/forms/notifications-deprecation.blade.php @@ -0,0 +1,7 @@ +
+

+ Deprecation Notice:
+ Support for Pushover, Discord, Ntfy, Gotify, Healthchecks, Slack, and Telegram will be removed soon.
+ We recommend switching to Apprise for future notifications. +

+
From f64823f3e877ad0253398c74e9ddf99d0b442acd Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Mon, 28 Apr 2025 15:11:34 +0200 Subject: [PATCH 03/31] Add SpeedtestBenchmarkFailed --- app/Listeners/SpeedtestEventSubscriber.php | 75 +++++++++++++++------- 1 file changed, 51 insertions(+), 24 deletions(-) diff --git a/app/Listeners/SpeedtestEventSubscriber.php b/app/Listeners/SpeedtestEventSubscriber.php index 0a28a7a28..de4fb6994 100644 --- a/app/Listeners/SpeedtestEventSubscriber.php +++ b/app/Listeners/SpeedtestEventSubscriber.php @@ -4,6 +4,7 @@ use App\Events\SpeedtestCompleted; use App\Events\SpeedtestFailed; +use App\Events\SpeedtestBenchmarkFailed; use App\Jobs\Influxdb\v2\WriteResult; use App\Jobs\Notifications\Apprise\SendSpeedtestCompletedNotification as AppriseCompleted; use App\Jobs\Notifications\Apprise\SendSpeedtestThresholdNotification as AppriseThresholds; @@ -41,44 +42,65 @@ public function handleSpeedtestCompleted(SpeedtestCompleted $event): void $notificationSettings = app(NotificationSettings::class); - // Send Apprise notification if the setting is enabled - if ($notificationSettings->apprise_enabled && $notificationSettings->apprise_on_speedtest_run) { - AppriseCompleted::dispatch($event->result); + // Apprise notifications + if ($notificationSettings->apprise_enabled) { + if ($notificationSettings->apprise_on_speedtest_run) { + AppriseCompleted::dispatch($event->result); + } } - // Send Databse notification if the setting is enabled - if ($notificationSettings->database_enabled && $notificationSettings->database_on_speedtest_run) { - DatabaseCompleted::dispatch($event->result); + // Database notifications + if ($notificationSettings->database_enabled) { + if ($notificationSettings->database_on_speedtest_run) { + DatabaseCompleted::dispatch($event->result); + } } - // Send Webhook notification if the setting is enabled - if ($notificationSettings->webhook_enabled && $notificationSettings->webhook_on_speedtest_run) { - WebhookCompleted::dispatch($event->result); + // Webhook notifications + if ($notificationSettings->webhook_enabled) { + if ($notificationSettings->webhook_on_speedtest_run) { + WebhookCompleted::dispatch($event->result); + } } - // Send Mail notification if the setting is enabled - if ($notificationSettings->mail_enabled && $notificationSettings->mail_on_speedtest_run) { - MailCompleted::dispatch($event->result); + // Mail notifications + if ($notificationSettings->mail_enabled) { + if ($notificationSettings->mail_on_speedtest_run) { + MailCompleted::dispatch($event->result); + } } + } + + public function handleSpeedtestBenchmarkFailed(SpeedtestBenchmarkFailed $event): void + { + $notificationSettings = app(NotificationSettings::class); - // Send Apprise threshold failure notification if the setting is enabled - if ($notificationSettings->apprise_enabled && $notificationSettings->apprise_on_threshold_failure) { - AppriseThresholds::dispatch($event->result); + // Apprise notifications + if ($notificationSettings->apprise_enabled) { + if ($notificationSettings->apprise_on_threshold_failure) { + AppriseThresholds::dispatch($event->result); + } } - // Send Database threshold failure notification if the setting is enabled - if ($notificationSettings->database_enabled && $notificationSettings->database_on_threshold_failure) { - DatabaseThresholds::dispatch($event->result); + // Database notifications + if ($notificationSettings->database_enabled) { + if ($notificationSettings->database_on_threshold_failure) { + DatabaseThresholds::dispatch($event->result); + } } - // Send Webhook threshold failure notification if the setting is enabled - if ($notificationSettings->webhook_enabled && $notificationSettings->webhook_on_threshold_failure) { - WebhookThresholds::dispatch($event->result); + // Webhook notifications + if ($notificationSettings->webhook_enabled) { + if ($notificationSettings->webhook_on_threshold_failure) { + WebhookThresholds::dispatch($event->result); + } } - // Send Mail threshold failure notification if the setting is enabled - if ($notificationSettings->mail_enabled && $notificationSettings->mail_on_threshold_failure) { - MailThresholds::dispatch($event->result); + // Mail notifications + if ($notificationSettings->mail_enabled) { + if ($notificationSettings->mail_on_threshold_failure) { + MailThresholds::dispatch($event->result); + } } } @@ -96,5 +118,10 @@ public function subscribe(Dispatcher $events): void SpeedtestCompleted::class, [SpeedtestEventSubscriber::class, 'handleSpeedtestCompleted'] ); + + $events->listen( + SpeedtestBenchmarkFailed::class, + [SpeedtestEventSubscriber::class, 'handleSpeedtestBenchmarkFailed'] + ); } } From 388ea10b2bd69415f94776ce9c599bb913539696 Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Mon, 28 Apr 2025 15:15:54 +0200 Subject: [PATCH 04/31] lint --- app/Listeners/SpeedtestEventSubscriber.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Listeners/SpeedtestEventSubscriber.php b/app/Listeners/SpeedtestEventSubscriber.php index de4fb6994..4431f94a9 100644 --- a/app/Listeners/SpeedtestEventSubscriber.php +++ b/app/Listeners/SpeedtestEventSubscriber.php @@ -2,9 +2,9 @@ namespace App\Listeners; +use App\Events\SpeedtestBenchmarkFailed; use App\Events\SpeedtestCompleted; use App\Events\SpeedtestFailed; -use App\Events\SpeedtestBenchmarkFailed; use App\Jobs\Influxdb\v2\WriteResult; use App\Jobs\Notifications\Apprise\SendSpeedtestCompletedNotification as AppriseCompleted; use App\Jobs\Notifications\Apprise\SendSpeedtestThresholdNotification as AppriseThresholds; From 8e725923b7cd38e118e82eb00dd97d64dae277cd Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Mon, 28 Apr 2025 22:32:40 +0200 Subject: [PATCH 05/31] add failed notifications --- .../SendAppriseTestNotification.php | 5 +- .../Pages/Settings/NotificationPage.php | 188 ++++++++++-------- .../SendSpeedtestCompletedNotification.php | 2 +- .../SendSpeedtestFailedNotification.php | 81 ++++++++ .../SendSpeedtestCompletedNotification.php | 13 -- .../SendSpeedtestFailedNotification.php | 35 ++++ .../Mail/SendSpeedtestFailedNotification.php | 46 +++++ .../SendSpeedtestCompletedNotification.php | 4 + .../SendSpeedtestFailedNotification.php | 57 ++++++ .../SendSpeedtestThresholdNotification.php | 4 + app/Listeners/SpeedtestEventSubscriber.php | 34 +++- app/Mail/SpeedtestFailedMail.php | 54 +++++ app/Settings/NotificationSettings.php | 8 + ...ailed_speedtest_notifications_settings.php | 14 ++ .../apprise/speedtest-completed.blade.php | 2 - .../views/apprise/speedtest-failed.blade.php | 8 + .../views/emails/speedtest-failed.blade.php | 18 ++ 17 files changed, 467 insertions(+), 106 deletions(-) create mode 100644 app/Jobs/Notifications/Apprise/SendSpeedtestFailedNotification.php create mode 100644 app/Jobs/Notifications/Database/SendSpeedtestFailedNotification.php create mode 100644 app/Jobs/Notifications/Mail/SendSpeedtestFailedNotification.php create mode 100644 app/Jobs/Notifications/Webhook/SendSpeedtestFailedNotification.php create mode 100644 app/Mail/SpeedtestFailedMail.php create mode 100644 database/settings/2025_04_28_162755_add_failed_speedtest_notifications_settings.php create mode 100644 resources/views/apprise/speedtest-failed.blade.php create mode 100644 resources/views/emails/speedtest-failed.blade.php diff --git a/app/Actions/Notifications/SendAppriseTestNotification.php b/app/Actions/Notifications/SendAppriseTestNotification.php index 54c935183..15d4ac062 100644 --- a/app/Actions/Notifications/SendAppriseTestNotification.php +++ b/app/Actions/Notifications/SendAppriseTestNotification.php @@ -35,7 +35,10 @@ public function handle(array $webhooks) try { $response = $client->post(rtrim($webhook['url'], '/'), [ - 'form_params' => $payload, + 'json' => $payload, + 'headers' => [ + 'Content-Type' => 'application/json', + ], ]); if ($response->getStatusCode() === 200) { diff --git a/app/Filament/Pages/Settings/NotificationPage.php b/app/Filament/Pages/Settings/NotificationPage.php index 2a8724b17..6080839e6 100755 --- a/app/Filament/Pages/Settings/NotificationPage.php +++ b/app/Filament/Pages/Settings/NotificationPage.php @@ -63,7 +63,7 @@ public function form(Form $form): Form ->description('Notifications sent to this channel will show up under the πŸ”” icon in the header.') ->schema([ Forms\Components\Toggle::make('database_enabled') - ->label('Enable database notifications') + ->label('Enable Database Notifications') ->reactive() ->columnSpanFull(), Forms\Components\Grid::make([ @@ -79,6 +79,9 @@ public function form(Form $form): Form Forms\Components\Toggle::make('database_on_threshold_failure') ->label('Notify on threshold failures') ->columnSpanFull(), + Forms\Components\Toggle::make('database_on_speedtest_failed') + ->label('Notify on speedtest failures') + ->columnSpanFull(), ]), Forms\Components\Actions::make([ Forms\Components\Actions\Action::make('test database') @@ -113,6 +116,9 @@ public function form(Form $form): Form Forms\Components\Toggle::make('apprise_on_threshold_failure') ->label('Notify on threshold failures') ->columnSpanFull(), + Forms\Components\Toggle::make('apprise_on_speedtest_failed') + ->label('Notify on speedtest failures') + ->columnSpanFull(), ]), Forms\Components\Repeater::make('apprise_webhooks') ->label('Apprise Webhooks') @@ -149,6 +155,99 @@ public function form(Form $form): Form 'md' => 2, ]), + Forms\Components\Section::make('Mail') + ->schema([ + Forms\Components\Toggle::make('mail_enabled') + ->label('Enable Mail Notifications') + ->reactive() + ->columnSpanFull(), + Forms\Components\Grid::make([ + 'default' => 1, + ]) + ->hidden(fn (Forms\Get $get) => $get('mail_enabled') !== true) + ->schema([ + Forms\Components\Fieldset::make('Triggers') + ->schema([ + Forms\Components\Toggle::make('mail_on_speedtest_run') + ->label('Notify on every speedtest run') + ->columnSpanFull(), + Forms\Components\Toggle::make('mail_on_threshold_failure') + ->label('Notify on threshold failures') + ->columnSpanFull(), + Forms\Components\Toggle::make('mail_on_speedtest_failed') + ->label('Notify on every speedtest failures') + ->columnSpanFull(), + ]), + Forms\Components\Repeater::make('mail_recipients') + ->label('Recipients') + ->schema([ + Forms\Components\TextInput::make('email_address') + ->placeholder('your@email.com') + ->email() + ->required(), + ]) + ->columnSpanFull(), + Forms\Components\Actions::make([ + Forms\Components\Actions\Action::make('test mail') + ->label('Test mail channel') + ->action(fn (Forms\Get $get) => SendMailTestNotification::run(recipients: $get('mail_recipients'))) + ->hidden(fn (Forms\Get $get) => ! count($get('mail_recipients'))), + ]), + ]), + ]) + ->compact() + ->columns([ + 'default' => 1, + 'md' => 2, + ]), + + Forms\Components\Section::make('Webhook') + ->schema([ + Forms\Components\Toggle::make('webhook_enabled') + ->label('Enable Webhook Notifications') + ->reactive() + ->columnSpanFull(), + Forms\Components\Grid::make([ + 'default' => 1, + ]) + ->hidden(fn (Forms\Get $get) => $get('webhook_enabled') !== true) + ->schema([ + Forms\Components\Fieldset::make('Triggers') + ->schema([ + Forms\Components\Toggle::make('webhook_on_speedtest_run') + ->label('Notify on every speedtest run') + ->columnSpan(2), + Forms\Components\Toggle::make('webhook_on_threshold_failure') + ->label('Notify on threshold failures') + ->columnSpan(2), + Forms\Components\Toggle::make('webhook_on_speedtest_failed') + ->label('Notify on every speedtest failure') + ->columnSpan(2), + ]), + Forms\Components\Repeater::make('webhook_urls') + ->label('Recipients') + ->schema([ + Forms\Components\TextInput::make('url') + ->placeholder('https://webhook.site/longstringofcharacters') + ->maxLength(2000) + ->required() + ->url(), + ]) + ->columnSpanFull(), + Forms\Components\Actions::make([ + Forms\Components\Actions\Action::make('test webhook') + ->label('Test webhook channel') + ->action(fn (Forms\Get $get) => SendWebhookTestNotification::run(webhooks: $get('webhook_urls'))) + ->hidden(fn (Forms\Get $get) => ! count($get('webhook_urls'))), + ]), + ]), + ]) + ->compact() + ->columns([ + 'default' => 1, + 'md' => 2, + ]), + Forms\Components\Section::make('Pushover') ->schema([ Forms\Components\Toggle::make('pushover_enabled') @@ -396,49 +495,6 @@ public function form(Form $form): Form 'md' => 2, ]), - Forms\Components\Section::make('Mail') - ->schema([ - Forms\Components\Toggle::make('mail_enabled') - ->label('Enable mail notifications') - ->reactive() - ->columnSpanFull(), - Forms\Components\Grid::make([ - 'default' => 1, - ]) - ->hidden(fn (Forms\Get $get) => $get('mail_enabled') !== true) - ->schema([ - Forms\Components\Fieldset::make('Triggers') - ->schema([ - Forms\Components\Toggle::make('mail_on_speedtest_run') - ->label('Notify on every speedtest run') - ->columnSpanFull(), - Forms\Components\Toggle::make('mail_on_threshold_failure') - ->label('Notify on threshold failures') - ->columnSpanFull(), - ]), - Forms\Components\Repeater::make('mail_recipients') - ->label('Recipients') - ->schema([ - Forms\Components\TextInput::make('email_address') - ->placeholder('your@email.com') - ->email() - ->required(), - ]) - ->columnSpanFull(), - Forms\Components\Actions::make([ - Forms\Components\Actions\Action::make('test mail') - ->label('Test mail channel') - ->action(fn (Forms\Get $get) => SendMailTestNotification::run(recipients: $get('mail_recipients'))) - ->hidden(fn (Forms\Get $get) => ! count($get('mail_recipients'))), - ]), - ]), - ]) - ->compact() - ->columns([ - 'default' => 1, - 'md' => 2, - ]), - Forms\Components\Section::make('Healthcheck.io') ->schema([ Forms\Components\Toggle::make('healthcheck_enabled') @@ -533,50 +589,6 @@ public function form(Form $form): Form 'default' => 1, 'md' => 2, ]), - - Forms\Components\Section::make('Webhook') - ->schema([ - Forms\Components\Toggle::make('webhook_enabled') - ->label('Enable webhook notifications') - ->reactive() - ->columnSpanFull(), - Forms\Components\Grid::make([ - 'default' => 1, - ]) - ->hidden(fn (Forms\Get $get) => $get('webhook_enabled') !== true) - ->schema([ - Forms\Components\Fieldset::make('Triggers') - ->schema([ - Forms\Components\Toggle::make('webhook_on_speedtest_run') - ->label('Notify on every speedtest run') - ->columnSpan(2), - Forms\Components\Toggle::make('webhook_on_threshold_failure') - ->label('Notify on threshold failures') - ->columnSpan(2), - ]), - Forms\Components\Repeater::make('webhook_urls') - ->label('Recipients') - ->schema([ - Forms\Components\TextInput::make('url') - ->placeholder('https://webhook.site/longstringofcharacters') - ->maxLength(2000) - ->required() - ->url(), - ]) - ->columnSpanFull(), - Forms\Components\Actions::make([ - Forms\Components\Actions\Action::make('test webhook') - ->label('Test webhook channel') - ->action(fn (Forms\Get $get) => SendWebhookTestNotification::run(webhooks: $get('webhook_urls'))) - ->hidden(fn (Forms\Get $get) => ! count($get('webhook_urls'))), - ]), - ]), - ]) - ->compact() - ->columns([ - 'default' => 1, - 'md' => 2, - ]), ]) ->columnSpan([ 'md' => 2, diff --git a/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php b/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php index d37095b67..77c39c48a 100644 --- a/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php +++ b/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php @@ -63,7 +63,7 @@ public function handle(): void $webhookPayload = [ 'body' => $payload, - 'title' => 'Speedtest Completed', + 'title' => 'Speedtest Completed - #{$this->result->id}', 'type' => 'info', 'urls' => $webhook['service_url'], ]; diff --git a/app/Jobs/Notifications/Apprise/SendSpeedtestFailedNotification.php b/app/Jobs/Notifications/Apprise/SendSpeedtestFailedNotification.php new file mode 100644 index 000000000..dfb1fb0fa --- /dev/null +++ b/app/Jobs/Notifications/Apprise/SendSpeedtestFailedNotification.php @@ -0,0 +1,81 @@ +result = $result; + } + + /** + * Handle the event. + */ + public function handle(): void + { + $notificationSettings = app(NotificationSettings::class); + + if (! count($notificationSettings->apprise_webhooks)) { + Log::warning('Apprise URLs not found, check Apprise notification channel settings.'); + + return; + } + + $payload = view('apprise.speedtest-failed', [ + 'id' => $this->result->id, + 'service' => Str::title($this->result->service->getLabel()), + 'serverName' => $this->result->server_name ?? 'Unknown', + 'serverId' => $this->result->server_id ?? 'Unknown', + 'isp' => $this->result->isp ?? 'Unknown', + 'errorMessage' => $this->result->data['message'] ?? 'Unknown error during speedtest.', + 'url' => url('/admin/results'), + ])->render(); + + foreach ($notificationSettings->apprise_webhooks as $webhook) { + if (empty($webhook['service_url']) || empty($webhook['url'])) { + Log::warning('Webhook is missing service URL or URL, skipping.'); + + continue; + } + + $webhookPayload = [ + 'body' => $payload, + 'title' => "Speedtest Failed - #{$this->result->id}", + 'type' => 'info', + 'urls' => [$webhook['service_url']], + ]; + + try { + $client = new Client; + $response = $client->post($webhook['url'], [ + 'json' => $webhookPayload, + 'headers' => [ + 'Content-Type' => 'application/json', + ], + ]); + + Log::info('Apprise failed notification sent successfully to '.$webhook['url']); + } catch (RequestException $e) { + Log::error('Apprise failed notification failed: '.$e->getMessage()); + } + } + } +} diff --git a/app/Jobs/Notifications/Database/SendSpeedtestCompletedNotification.php b/app/Jobs/Notifications/Database/SendSpeedtestCompletedNotification.php index 7506a1f14..fa16be537 100644 --- a/app/Jobs/Notifications/Database/SendSpeedtestCompletedNotification.php +++ b/app/Jobs/Notifications/Database/SendSpeedtestCompletedNotification.php @@ -3,7 +3,6 @@ namespace App\Jobs\Notifications\Database; use App\Models\User; -use App\Settings\NotificationSettings; use Filament\Notifications\Notification; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -18,18 +17,6 @@ class SendSpeedtestCompletedNotification implements ShouldQueue */ public function handle(): void { - $notificationSettings = new NotificationSettings; - - if (! $notificationSettings->database_enabled) { - - return; - } - - if (! $notificationSettings->database_on_speedtest_run) { - - return; - } - foreach (User::all() as $user) { Notification::make() ->title('Speedtest completed') diff --git a/app/Jobs/Notifications/Database/SendSpeedtestFailedNotification.php b/app/Jobs/Notifications/Database/SendSpeedtestFailedNotification.php new file mode 100644 index 000000000..c2b26a45d --- /dev/null +++ b/app/Jobs/Notifications/Database/SendSpeedtestFailedNotification.php @@ -0,0 +1,35 @@ +result->data['message'] ?? 'Unknown error during speedtest.'; + + foreach (User::all() as $user) { + Notification::make() + ->title('Speedtest failed') + ->body("Failure reason: {$errorMessage}") + ->danger() + ->sendToDatabase($user); + } + } +} diff --git a/app/Jobs/Notifications/Mail/SendSpeedtestFailedNotification.php b/app/Jobs/Notifications/Mail/SendSpeedtestFailedNotification.php new file mode 100644 index 000000000..de510f67f --- /dev/null +++ b/app/Jobs/Notifications/Mail/SendSpeedtestFailedNotification.php @@ -0,0 +1,46 @@ +result = $result; + } + + /** + * Handle the job. + */ + public function handle(): void + { + $notificationSettings = new NotificationSettings; + + if (! count($notificationSettings->mail_recipients)) { + Log::warning('Mail recipients not found, check mail notification channel settings.'); + + return; + } + + foreach ($notificationSettings->mail_recipients as $recipient) { + Mail::to($recipient) + ->send(new SpeedtestFailedMail($this->result)); + } + } +} diff --git a/app/Jobs/Notifications/Webhook/SendSpeedtestCompletedNotification.php b/app/Jobs/Notifications/Webhook/SendSpeedtestCompletedNotification.php index 8ba65ab7e..ba9887459 100644 --- a/app/Jobs/Notifications/Webhook/SendSpeedtestCompletedNotification.php +++ b/app/Jobs/Notifications/Webhook/SendSpeedtestCompletedNotification.php @@ -8,6 +8,7 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Str; use Spatie\WebhookServer\WebhookCall; class SendSpeedtestCompletedNotification implements ShouldQueue @@ -43,6 +44,9 @@ public function handle(): void ->payload([ 'result_id' => $this->result->id, 'site_name' => config('app.name'), + 'service' => Str::title($this->result->service->getLabel()), + 'serverName' => $this->result->server_name, + 'serverId' => $this->result->server_id, 'isp' => $this->result->isp, 'ping' => $this->result->ping, 'download' => $this->result->downloadBits, diff --git a/app/Jobs/Notifications/Webhook/SendSpeedtestFailedNotification.php b/app/Jobs/Notifications/Webhook/SendSpeedtestFailedNotification.php new file mode 100644 index 000000000..9bf6d818e --- /dev/null +++ b/app/Jobs/Notifications/Webhook/SendSpeedtestFailedNotification.php @@ -0,0 +1,57 @@ +result = $result; + } + + /** + * Handle the job. + */ + public function handle(): void + { + $notificationSettings = new NotificationSettings; + + if (! count($notificationSettings->webhook_urls)) { + Log::warning('Webhook URLs not found, check webhook notification channel settings.'); + + return; + } + + foreach ($notificationSettings->webhook_urls as $url) { + WebhookCall::create() + ->url($url['url']) + ->payload([ + 'result_id' => $this->result->id, + 'site_name' => config('app.name'), + 'service' => Str::title($this->result->service->getLabel()), + 'serverName' => $this->result->server_name ?? 'Unknown', + 'serverId' => $this->result->server_id ?? 'Unknown', + 'errorMessage' => $this->result->data['message'] ?? 'Unknown error during speedtest.', + 'url' => url('/admin/results'), + ]) + ->doNotSign() + ->dispatch(); + } + } +} diff --git a/app/Jobs/Notifications/Webhook/SendSpeedtestThresholdNotification.php b/app/Jobs/Notifications/Webhook/SendSpeedtestThresholdNotification.php index fb35970f6..f63a56bd6 100644 --- a/app/Jobs/Notifications/Webhook/SendSpeedtestThresholdNotification.php +++ b/app/Jobs/Notifications/Webhook/SendSpeedtestThresholdNotification.php @@ -10,6 +10,7 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Str; use Spatie\WebhookServer\WebhookCall; class SendSpeedtestThresholdNotification implements ShouldQueue @@ -74,6 +75,9 @@ public function handle(): void ->payload([ 'result_id' => $this->result->id, 'site_name' => config('app.name'), + 'service' => Str::title($this->result->service->getLabel()), + 'serverName' => $this->result->server_name, + 'serverId' => $this->result->server_id, 'isp' => $this->result->isp, 'metrics' => $failed, 'speedtest_url' => $this->result->result_url, diff --git a/app/Listeners/SpeedtestEventSubscriber.php b/app/Listeners/SpeedtestEventSubscriber.php index 4431f94a9..0ef1aacf7 100644 --- a/app/Listeners/SpeedtestEventSubscriber.php +++ b/app/Listeners/SpeedtestEventSubscriber.php @@ -7,12 +7,16 @@ use App\Events\SpeedtestFailed; use App\Jobs\Influxdb\v2\WriteResult; use App\Jobs\Notifications\Apprise\SendSpeedtestCompletedNotification as AppriseCompleted; +use App\Jobs\Notifications\Apprise\SendSpeedtestFailedNotification as AppriseFailed; use App\Jobs\Notifications\Apprise\SendSpeedtestThresholdNotification as AppriseThresholds; use App\Jobs\Notifications\Database\SendSpeedtestCompletedNotification as DatabaseCompleted; +use App\Jobs\Notifications\Database\SendSpeedtestFailedNotification as DatabaseFailed; use App\Jobs\Notifications\Database\SendSpeedtestThresholdNotification as DatabaseThresholds; use App\Jobs\Notifications\Mail\SendSpeedtestCompletedNotification as MailCompleted; +use App\Jobs\Notifications\Mail\SendSpeedtestFailedNotification as MailFailed; use App\Jobs\Notifications\Mail\SendSpeedtestThresholdNotification as MailThresholds; use App\Jobs\Notifications\Webhook\SendSpeedtestCompletedNotification as WebhookCompleted; +use App\Jobs\Notifications\Webhook\SendSpeedtestFailedNotification as WebhookFailed; use App\Jobs\Notifications\Webhook\SendSpeedtestThresholdNotification as WebhookThresholds; use App\Settings\DataIntegrationSettings; use App\Settings\NotificationSettings; @@ -25,7 +29,35 @@ class SpeedtestEventSubscriber */ public function handleSpeedtestFailed(SpeedtestFailed $event): void { - // Handle failed event if necessary + $notificationSettings = app(NotificationSettings::class); + + // Database notifications + if ($notificationSettings->database_enabled) { + if ($notificationSettings->database_on_speedtest_failed) { + DatabaseFailed::dispatch($event->result); + } + } + + // Apprise notifications + if ($notificationSettings->apprise_enabled) { + if ($notificationSettings->apprise_on_speedtest_failed) { + AppriseFailed::dispatch($event->result); + } + } + + // Webhook notifications + if ($notificationSettings->webhook_enabled) { + if ($notificationSettings->webhook_on_speedtest_failed) { + WebhookFailed::dispatch($event->result); + } + } + + // Mail notifications + if ($notificationSettings->mail_enabled) { + if ($notificationSettings->mail_on_speedtest_failed) { + MailFailed::dispatch($event->result); + } + } } /** diff --git a/app/Mail/SpeedtestFailedMail.php b/app/Mail/SpeedtestFailedMail.php new file mode 100644 index 000000000..55c4ad8ec --- /dev/null +++ b/app/Mail/SpeedtestFailedMail.php @@ -0,0 +1,54 @@ +result->id, + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + markdown: 'emails.speedtest-failed', + with: [ + 'id' => $this->result->id, + 'service' => Str::title($this->result->service->getLabel()), + 'serverName' => $this->result->server_name, + 'serverId' => $this->result->server_id, + 'errorMessage' => $this->result->data['message'] ?? 'Unknown error during speedtest.', + 'url' => url('/admin/results'), + ], + ); + } +} diff --git a/app/Settings/NotificationSettings.php b/app/Settings/NotificationSettings.php index c889aceeb..41ac7fcb7 100644 --- a/app/Settings/NotificationSettings.php +++ b/app/Settings/NotificationSettings.php @@ -12,12 +12,16 @@ class NotificationSettings extends Settings public bool $database_on_threshold_failure; + public bool $database_on_speedtest_failed; + public bool $mail_enabled; public bool $mail_on_speedtest_run; public bool $mail_on_threshold_failure; + public bool $mail_on_speedtest_failed; + public ?array $mail_recipients; public bool $telegram_enabled; @@ -34,6 +38,8 @@ class NotificationSettings extends Settings public bool $webhook_on_speedtest_run; + public bool $webhook_on_speedtest_failed; + public bool $webhook_on_threshold_failure; public ?array $webhook_urls; @@ -92,6 +98,8 @@ class NotificationSettings extends Settings public bool $apprise_on_threshold_failure; + public bool $apprise_on_speedtest_failed; + public ?array $apprise_webhooks; public static function group(): string diff --git a/database/settings/2025_04_28_162755_add_failed_speedtest_notifications_settings.php b/database/settings/2025_04_28_162755_add_failed_speedtest_notifications_settings.php new file mode 100644 index 000000000..1e5a6f19d --- /dev/null +++ b/database/settings/2025_04_28_162755_add_failed_speedtest_notifications_settings.php @@ -0,0 +1,14 @@ +migrator->add('notification.database_on_speedtest_failed', false); + $this->migrator->add('notification.apprise_on_speedtest_failed', false); + $this->migrator->add('notification.webhook_on_speedtest_failed', false); + $this->migrator->add('notification.mail_on_speedtest_failed', false); + } +}; diff --git a/resources/views/apprise/speedtest-completed.blade.php b/resources/views/apprise/speedtest-completed.blade.php index 24c67a908..6363ee642 100644 --- a/resources/views/apprise/speedtest-completed.blade.php +++ b/resources/views/apprise/speedtest-completed.blade.php @@ -1,5 +1,3 @@ -Speedtest Completed - #{{ $id }} - A new speedtest on {{ config('app.name') }} was completed using {{ $service }}. Server name: {{ $serverName }} diff --git a/resources/views/apprise/speedtest-failed.blade.php b/resources/views/apprise/speedtest-failed.blade.php new file mode 100644 index 000000000..abd6cc395 --- /dev/null +++ b/resources/views/apprise/speedtest-failed.blade.php @@ -0,0 +1,8 @@ +A new speedtest on {{ config('app.name') }} has failed using {{ $service }}. + +Server Name: {{ $serverName }} +Server ID: {{ $serverId }} + +Failure Reason: {{ $errorMessage }} + +View results: {{ $url }} \ No newline at end of file diff --git a/resources/views/emails/speedtest-failed.blade.php b/resources/views/emails/speedtest-failed.blade.php new file mode 100644 index 000000000..cff32f20f --- /dev/null +++ b/resources/views/emails/speedtest-failed.blade.php @@ -0,0 +1,18 @@ + +# Speedtest Failed - #{{ $id }} + +A speedtest attempt on **{{ $service }}** has failed. + + +| **Metric** | **Value** | +|:------------|---------------------------:| +| Server Name | {{ $serverName ?? 'Unknown' }} | +| Server ID | {{ $serverId ?? 'Unknown' }} | +| ISP | {{ $isp ?? 'Unknown' }} | +| Failure Reason | {{ $errorMessage ?? 'Unknown error' }} | + + + +Thanks,
+{{ config('app.name') }} +
\ No newline at end of file From 36e0afd1ad279d45cb39d482f5d48f16ca7238d9 Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Tue, 29 Apr 2025 10:54:07 +0200 Subject: [PATCH 06/31] remove failed tests --- .../Pages/Settings/NotificationPage.php | 12 --- .../SendSpeedtestCompletedNotification.php | 18 +---- .../SendSpeedtestFailedNotification.php | 81 ------------------- .../SendSpeedtestFailedNotification.php | 35 -------- .../Mail/SendSpeedtestFailedNotification.php | 46 ----------- .../SendSpeedtestFailedNotification.php | 57 ------------- app/Mail/SpeedtestFailedMail.php | 54 ------------- .../SpeedtestNotificationData.php | 27 +++++++ app/Settings/NotificationSettings.php | 8 -- ...ailed_speedtest_notifications_settings.php | 14 ---- .../views/apprise/speedtest-failed.blade.php | 8 -- .../views/emails/speedtest-failed.blade.php | 18 ----- 12 files changed, 31 insertions(+), 347 deletions(-) delete mode 100644 app/Jobs/Notifications/Apprise/SendSpeedtestFailedNotification.php delete mode 100644 app/Jobs/Notifications/Database/SendSpeedtestFailedNotification.php delete mode 100644 app/Jobs/Notifications/Mail/SendSpeedtestFailedNotification.php delete mode 100644 app/Jobs/Notifications/Webhook/SendSpeedtestFailedNotification.php delete mode 100644 app/Mail/SpeedtestFailedMail.php create mode 100644 app/Notifications/SpeedtestNotificationData.php delete mode 100644 database/settings/2025_04_28_162755_add_failed_speedtest_notifications_settings.php delete mode 100644 resources/views/apprise/speedtest-failed.blade.php delete mode 100644 resources/views/emails/speedtest-failed.blade.php diff --git a/app/Filament/Pages/Settings/NotificationPage.php b/app/Filament/Pages/Settings/NotificationPage.php index 6080839e6..d58afdce4 100755 --- a/app/Filament/Pages/Settings/NotificationPage.php +++ b/app/Filament/Pages/Settings/NotificationPage.php @@ -79,9 +79,6 @@ public function form(Form $form): Form Forms\Components\Toggle::make('database_on_threshold_failure') ->label('Notify on threshold failures') ->columnSpanFull(), - Forms\Components\Toggle::make('database_on_speedtest_failed') - ->label('Notify on speedtest failures') - ->columnSpanFull(), ]), Forms\Components\Actions::make([ Forms\Components\Actions\Action::make('test database') @@ -116,9 +113,6 @@ public function form(Form $form): Form Forms\Components\Toggle::make('apprise_on_threshold_failure') ->label('Notify on threshold failures') ->columnSpanFull(), - Forms\Components\Toggle::make('apprise_on_speedtest_failed') - ->label('Notify on speedtest failures') - ->columnSpanFull(), ]), Forms\Components\Repeater::make('apprise_webhooks') ->label('Apprise Webhooks') @@ -174,9 +168,6 @@ public function form(Form $form): Form Forms\Components\Toggle::make('mail_on_threshold_failure') ->label('Notify on threshold failures') ->columnSpanFull(), - Forms\Components\Toggle::make('mail_on_speedtest_failed') - ->label('Notify on every speedtest failures') - ->columnSpanFull(), ]), Forms\Components\Repeater::make('mail_recipients') ->label('Recipients') @@ -220,9 +211,6 @@ public function form(Form $form): Form Forms\Components\Toggle::make('webhook_on_threshold_failure') ->label('Notify on threshold failures') ->columnSpan(2), - Forms\Components\Toggle::make('webhook_on_speedtest_failed') - ->label('Notify on every speedtest failure') - ->columnSpan(2), ]), Forms\Components\Repeater::make('webhook_urls') ->label('Recipients') diff --git a/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php b/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php index 77c39c48a..febb7a51d 100644 --- a/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php +++ b/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php @@ -3,6 +3,7 @@ namespace App\Jobs\Notifications\Apprise; use App\Helpers\Number; +use App\Notifications\SpeedtestNotificationData; use App\Models\Result; use App\Settings\NotificationSettings; use GuzzleHttp\Client; @@ -40,19 +41,8 @@ public function handle(): void return; } - $payload = view('apprise.speedtest-completed', [ - 'id' => $this->result->id, - 'service' => Str::title($this->result->service->getLabel()), - 'serverName' => $this->result->server_name, - 'serverId' => $this->result->server_id, - 'isp' => $this->result->isp, - 'ping' => round($this->result->ping).' ms', - 'download' => Number::toBitRate(bits: $this->result->download_bits, precision: 2), - 'upload' => Number::toBitRate(bits: $this->result->upload_bits, precision: 2), - 'packetLoss' => $this->result->packet_loss, - 'speedtest_url' => $this->result->result_url, - 'url' => url('/admin/results'), - ])->render(); + $data = SpeedtestNotificationData::make($this->result); + $payload = view('apprise.speedtest-completed', $data)->render(); foreach ($notificationSettings->apprise_webhooks as $webhook) { if (empty($webhook['service_url']) || empty($webhook['url'])) { @@ -63,7 +53,7 @@ public function handle(): void $webhookPayload = [ 'body' => $payload, - 'title' => 'Speedtest Completed - #{$this->result->id}', + 'title' => "Speedtest Completed - #{$data['id']}", 'type' => 'info', 'urls' => $webhook['service_url'], ]; diff --git a/app/Jobs/Notifications/Apprise/SendSpeedtestFailedNotification.php b/app/Jobs/Notifications/Apprise/SendSpeedtestFailedNotification.php deleted file mode 100644 index dfb1fb0fa..000000000 --- a/app/Jobs/Notifications/Apprise/SendSpeedtestFailedNotification.php +++ /dev/null @@ -1,81 +0,0 @@ -result = $result; - } - - /** - * Handle the event. - */ - public function handle(): void - { - $notificationSettings = app(NotificationSettings::class); - - if (! count($notificationSettings->apprise_webhooks)) { - Log::warning('Apprise URLs not found, check Apprise notification channel settings.'); - - return; - } - - $payload = view('apprise.speedtest-failed', [ - 'id' => $this->result->id, - 'service' => Str::title($this->result->service->getLabel()), - 'serverName' => $this->result->server_name ?? 'Unknown', - 'serverId' => $this->result->server_id ?? 'Unknown', - 'isp' => $this->result->isp ?? 'Unknown', - 'errorMessage' => $this->result->data['message'] ?? 'Unknown error during speedtest.', - 'url' => url('/admin/results'), - ])->render(); - - foreach ($notificationSettings->apprise_webhooks as $webhook) { - if (empty($webhook['service_url']) || empty($webhook['url'])) { - Log::warning('Webhook is missing service URL or URL, skipping.'); - - continue; - } - - $webhookPayload = [ - 'body' => $payload, - 'title' => "Speedtest Failed - #{$this->result->id}", - 'type' => 'info', - 'urls' => [$webhook['service_url']], - ]; - - try { - $client = new Client; - $response = $client->post($webhook['url'], [ - 'json' => $webhookPayload, - 'headers' => [ - 'Content-Type' => 'application/json', - ], - ]); - - Log::info('Apprise failed notification sent successfully to '.$webhook['url']); - } catch (RequestException $e) { - Log::error('Apprise failed notification failed: '.$e->getMessage()); - } - } - } -} diff --git a/app/Jobs/Notifications/Database/SendSpeedtestFailedNotification.php b/app/Jobs/Notifications/Database/SendSpeedtestFailedNotification.php deleted file mode 100644 index c2b26a45d..000000000 --- a/app/Jobs/Notifications/Database/SendSpeedtestFailedNotification.php +++ /dev/null @@ -1,35 +0,0 @@ -result->data['message'] ?? 'Unknown error during speedtest.'; - - foreach (User::all() as $user) { - Notification::make() - ->title('Speedtest failed') - ->body("Failure reason: {$errorMessage}") - ->danger() - ->sendToDatabase($user); - } - } -} diff --git a/app/Jobs/Notifications/Mail/SendSpeedtestFailedNotification.php b/app/Jobs/Notifications/Mail/SendSpeedtestFailedNotification.php deleted file mode 100644 index de510f67f..000000000 --- a/app/Jobs/Notifications/Mail/SendSpeedtestFailedNotification.php +++ /dev/null @@ -1,46 +0,0 @@ -result = $result; - } - - /** - * Handle the job. - */ - public function handle(): void - { - $notificationSettings = new NotificationSettings; - - if (! count($notificationSettings->mail_recipients)) { - Log::warning('Mail recipients not found, check mail notification channel settings.'); - - return; - } - - foreach ($notificationSettings->mail_recipients as $recipient) { - Mail::to($recipient) - ->send(new SpeedtestFailedMail($this->result)); - } - } -} diff --git a/app/Jobs/Notifications/Webhook/SendSpeedtestFailedNotification.php b/app/Jobs/Notifications/Webhook/SendSpeedtestFailedNotification.php deleted file mode 100644 index 9bf6d818e..000000000 --- a/app/Jobs/Notifications/Webhook/SendSpeedtestFailedNotification.php +++ /dev/null @@ -1,57 +0,0 @@ -result = $result; - } - - /** - * Handle the job. - */ - public function handle(): void - { - $notificationSettings = new NotificationSettings; - - if (! count($notificationSettings->webhook_urls)) { - Log::warning('Webhook URLs not found, check webhook notification channel settings.'); - - return; - } - - foreach ($notificationSettings->webhook_urls as $url) { - WebhookCall::create() - ->url($url['url']) - ->payload([ - 'result_id' => $this->result->id, - 'site_name' => config('app.name'), - 'service' => Str::title($this->result->service->getLabel()), - 'serverName' => $this->result->server_name ?? 'Unknown', - 'serverId' => $this->result->server_id ?? 'Unknown', - 'errorMessage' => $this->result->data['message'] ?? 'Unknown error during speedtest.', - 'url' => url('/admin/results'), - ]) - ->doNotSign() - ->dispatch(); - } - } -} diff --git a/app/Mail/SpeedtestFailedMail.php b/app/Mail/SpeedtestFailedMail.php deleted file mode 100644 index 55c4ad8ec..000000000 --- a/app/Mail/SpeedtestFailedMail.php +++ /dev/null @@ -1,54 +0,0 @@ -result->id, - ); - } - - /** - * Get the message content definition. - */ - public function content(): Content - { - return new Content( - markdown: 'emails.speedtest-failed', - with: [ - 'id' => $this->result->id, - 'service' => Str::title($this->result->service->getLabel()), - 'serverName' => $this->result->server_name, - 'serverId' => $this->result->server_id, - 'errorMessage' => $this->result->data['message'] ?? 'Unknown error during speedtest.', - 'url' => url('/admin/results'), - ], - ); - } -} diff --git a/app/Notifications/SpeedtestNotificationData.php b/app/Notifications/SpeedtestNotificationData.php new file mode 100644 index 000000000..3c8e7502c --- /dev/null +++ b/app/Notifications/SpeedtestNotificationData.php @@ -0,0 +1,27 @@ + $result->id, + 'service' => Str::title($result->service->getLabel()), + 'serverName' => $result->server_name, + 'serverId' => $result->server_id, + 'isp' => $result->isp, + 'ping' => round($result->ping, 2).' ms', + 'download' => Number::toBitRate(bits: $result->download_bits, precision: 2), + 'upload' => Number::toBitRate(bits: $result->upload_bits, precision: 2), + 'packetLoss' => is_numeric($result->packet_loss) ? $result->packet_loss : 'n/a', + 'speedtest_url' => $result->result_url, + 'url' => url('/admin/results'), + ]; + } +} diff --git a/app/Settings/NotificationSettings.php b/app/Settings/NotificationSettings.php index 41ac7fcb7..c889aceeb 100644 --- a/app/Settings/NotificationSettings.php +++ b/app/Settings/NotificationSettings.php @@ -12,16 +12,12 @@ class NotificationSettings extends Settings public bool $database_on_threshold_failure; - public bool $database_on_speedtest_failed; - public bool $mail_enabled; public bool $mail_on_speedtest_run; public bool $mail_on_threshold_failure; - public bool $mail_on_speedtest_failed; - public ?array $mail_recipients; public bool $telegram_enabled; @@ -38,8 +34,6 @@ class NotificationSettings extends Settings public bool $webhook_on_speedtest_run; - public bool $webhook_on_speedtest_failed; - public bool $webhook_on_threshold_failure; public ?array $webhook_urls; @@ -98,8 +92,6 @@ class NotificationSettings extends Settings public bool $apprise_on_threshold_failure; - public bool $apprise_on_speedtest_failed; - public ?array $apprise_webhooks; public static function group(): string diff --git a/database/settings/2025_04_28_162755_add_failed_speedtest_notifications_settings.php b/database/settings/2025_04_28_162755_add_failed_speedtest_notifications_settings.php deleted file mode 100644 index 1e5a6f19d..000000000 --- a/database/settings/2025_04_28_162755_add_failed_speedtest_notifications_settings.php +++ /dev/null @@ -1,14 +0,0 @@ -migrator->add('notification.database_on_speedtest_failed', false); - $this->migrator->add('notification.apprise_on_speedtest_failed', false); - $this->migrator->add('notification.webhook_on_speedtest_failed', false); - $this->migrator->add('notification.mail_on_speedtest_failed', false); - } -}; diff --git a/resources/views/apprise/speedtest-failed.blade.php b/resources/views/apprise/speedtest-failed.blade.php deleted file mode 100644 index abd6cc395..000000000 --- a/resources/views/apprise/speedtest-failed.blade.php +++ /dev/null @@ -1,8 +0,0 @@ -A new speedtest on {{ config('app.name') }} has failed using {{ $service }}. - -Server Name: {{ $serverName }} -Server ID: {{ $serverId }} - -Failure Reason: {{ $errorMessage }} - -View results: {{ $url }} \ No newline at end of file diff --git a/resources/views/emails/speedtest-failed.blade.php b/resources/views/emails/speedtest-failed.blade.php deleted file mode 100644 index cff32f20f..000000000 --- a/resources/views/emails/speedtest-failed.blade.php +++ /dev/null @@ -1,18 +0,0 @@ - -# Speedtest Failed - #{{ $id }} - -A speedtest attempt on **{{ $service }}** has failed. - - -| **Metric** | **Value** | -|:------------|---------------------------:| -| Server Name | {{ $serverName ?? 'Unknown' }} | -| Server ID | {{ $serverId ?? 'Unknown' }} | -| ISP | {{ $isp ?? 'Unknown' }} | -| Failure Reason | {{ $errorMessage ?? 'Unknown error' }} | - - - -Thanks,
-{{ config('app.name') }} -
\ No newline at end of file From ebebb0f4cddfff93fa2388b13364d7a5b3474b1e Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Tue, 29 Apr 2025 10:55:33 +0200 Subject: [PATCH 07/31] update subscriber --- app/Listeners/SpeedtestEventSubscriber.php | 38 ---------------------- 1 file changed, 38 deletions(-) diff --git a/app/Listeners/SpeedtestEventSubscriber.php b/app/Listeners/SpeedtestEventSubscriber.php index 0ef1aacf7..56f846b8d 100644 --- a/app/Listeners/SpeedtestEventSubscriber.php +++ b/app/Listeners/SpeedtestEventSubscriber.php @@ -2,21 +2,16 @@ namespace App\Listeners; -use App\Events\SpeedtestBenchmarkFailed; use App\Events\SpeedtestCompleted; use App\Events\SpeedtestFailed; use App\Jobs\Influxdb\v2\WriteResult; use App\Jobs\Notifications\Apprise\SendSpeedtestCompletedNotification as AppriseCompleted; -use App\Jobs\Notifications\Apprise\SendSpeedtestFailedNotification as AppriseFailed; use App\Jobs\Notifications\Apprise\SendSpeedtestThresholdNotification as AppriseThresholds; use App\Jobs\Notifications\Database\SendSpeedtestCompletedNotification as DatabaseCompleted; -use App\Jobs\Notifications\Database\SendSpeedtestFailedNotification as DatabaseFailed; use App\Jobs\Notifications\Database\SendSpeedtestThresholdNotification as DatabaseThresholds; use App\Jobs\Notifications\Mail\SendSpeedtestCompletedNotification as MailCompleted; -use App\Jobs\Notifications\Mail\SendSpeedtestFailedNotification as MailFailed; use App\Jobs\Notifications\Mail\SendSpeedtestThresholdNotification as MailThresholds; use App\Jobs\Notifications\Webhook\SendSpeedtestCompletedNotification as WebhookCompleted; -use App\Jobs\Notifications\Webhook\SendSpeedtestFailedNotification as WebhookFailed; use App\Jobs\Notifications\Webhook\SendSpeedtestThresholdNotification as WebhookThresholds; use App\Settings\DataIntegrationSettings; use App\Settings\NotificationSettings; @@ -29,35 +24,7 @@ class SpeedtestEventSubscriber */ public function handleSpeedtestFailed(SpeedtestFailed $event): void { - $notificationSettings = app(NotificationSettings::class); - - // Database notifications - if ($notificationSettings->database_enabled) { - if ($notificationSettings->database_on_speedtest_failed) { - DatabaseFailed::dispatch($event->result); - } - } - // Apprise notifications - if ($notificationSettings->apprise_enabled) { - if ($notificationSettings->apprise_on_speedtest_failed) { - AppriseFailed::dispatch($event->result); - } - } - - // Webhook notifications - if ($notificationSettings->webhook_enabled) { - if ($notificationSettings->webhook_on_speedtest_failed) { - WebhookFailed::dispatch($event->result); - } - } - - // Mail notifications - if ($notificationSettings->mail_enabled) { - if ($notificationSettings->mail_on_speedtest_failed) { - MailFailed::dispatch($event->result); - } - } } /** @@ -150,10 +117,5 @@ public function subscribe(Dispatcher $events): void SpeedtestCompleted::class, [SpeedtestEventSubscriber::class, 'handleSpeedtestCompleted'] ); - - $events->listen( - SpeedtestBenchmarkFailed::class, - [SpeedtestEventSubscriber::class, 'handleSpeedtestBenchmarkFailed'] - ); } } From b3ebdacedd7d3a3c3529f085be6653a70196dc36 Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Tue, 29 Apr 2025 10:59:14 +0200 Subject: [PATCH 08/31] lint and remove data collection --- .../SendSpeedtestCompletedNotification.php | 18 ++++++++++++++---- app/Listeners/SpeedtestEventSubscriber.php | 5 +---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php b/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php index febb7a51d..77c39c48a 100644 --- a/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php +++ b/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php @@ -3,7 +3,6 @@ namespace App\Jobs\Notifications\Apprise; use App\Helpers\Number; -use App\Notifications\SpeedtestNotificationData; use App\Models\Result; use App\Settings\NotificationSettings; use GuzzleHttp\Client; @@ -41,8 +40,19 @@ public function handle(): void return; } - $data = SpeedtestNotificationData::make($this->result); - $payload = view('apprise.speedtest-completed', $data)->render(); + $payload = view('apprise.speedtest-completed', [ + 'id' => $this->result->id, + 'service' => Str::title($this->result->service->getLabel()), + 'serverName' => $this->result->server_name, + 'serverId' => $this->result->server_id, + 'isp' => $this->result->isp, + 'ping' => round($this->result->ping).' ms', + 'download' => Number::toBitRate(bits: $this->result->download_bits, precision: 2), + 'upload' => Number::toBitRate(bits: $this->result->upload_bits, precision: 2), + 'packetLoss' => $this->result->packet_loss, + 'speedtest_url' => $this->result->result_url, + 'url' => url('/admin/results'), + ])->render(); foreach ($notificationSettings->apprise_webhooks as $webhook) { if (empty($webhook['service_url']) || empty($webhook['url'])) { @@ -53,7 +63,7 @@ public function handle(): void $webhookPayload = [ 'body' => $payload, - 'title' => "Speedtest Completed - #{$data['id']}", + 'title' => 'Speedtest Completed - #{$this->result->id}', 'type' => 'info', 'urls' => $webhook['service_url'], ]; diff --git a/app/Listeners/SpeedtestEventSubscriber.php b/app/Listeners/SpeedtestEventSubscriber.php index 56f846b8d..01811f046 100644 --- a/app/Listeners/SpeedtestEventSubscriber.php +++ b/app/Listeners/SpeedtestEventSubscriber.php @@ -22,10 +22,7 @@ class SpeedtestEventSubscriber /** * Handle speedtest failed events. */ - public function handleSpeedtestFailed(SpeedtestFailed $event): void - { - - } + public function handleSpeedtestFailed(SpeedtestFailed $event): void {} /** * Handle speedtest completed events. From d44166082232138b4fa97889dd0a0d95fde09151 Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Tue, 29 Apr 2025 11:30:17 +0200 Subject: [PATCH 09/31] remove faker data --- .../SendWebhookTestNotification.php | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/app/Actions/Notifications/SendWebhookTestNotification.php b/app/Actions/Notifications/SendWebhookTestNotification.php index 6b7ada378..3176f92a0 100644 --- a/app/Actions/Notifications/SendWebhookTestNotification.php +++ b/app/Actions/Notifications/SendWebhookTestNotification.php @@ -2,7 +2,6 @@ namespace App\Actions\Notifications; -use App\Models\Result; use Filament\Notifications\Notification; use Lorisleiva\Actions\Concerns\AsAction; use Spatie\WebhookServer\WebhookCall; @@ -22,23 +21,10 @@ public function handle(array $webhooks) return; } - // Generate a fake Result (NOT saved to database) - $fakeResult = Result::factory()->make(); - foreach ($webhooks as $webhook) { WebhookCall::create() ->url($webhook['url']) - ->payload([ - 'result_id' => fake()->uuid(), - 'site_name' => config('app.name'), - 'isp' => $fakeResult->data['isp'], - 'ping' => $fakeResult->ping, - 'download' => $fakeResult->download, - 'upload' => $fakeResult->upload, - 'packetLoss' => $fakeResult->data['packetLoss'], - 'speedtest_url' => $fakeResult->data['result']['url'], - 'url' => url('/admin/results'), - ]) + ->payload(['message' => 'πŸ‘‹ Testing the Webhook notification channel.']) ->doNotSign() ->dispatch(); } From a96a531cef07304c8e5dfa5270be195af27b62ec Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Tue, 29 Apr 2025 20:43:20 +0200 Subject: [PATCH 10/31] fix benchmarkfaild --- app/Listeners/SpeedtestEventSubscriber.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/Listeners/SpeedtestEventSubscriber.php b/app/Listeners/SpeedtestEventSubscriber.php index 01811f046..1bbde58d9 100644 --- a/app/Listeners/SpeedtestEventSubscriber.php +++ b/app/Listeners/SpeedtestEventSubscriber.php @@ -2,6 +2,7 @@ namespace App\Listeners; +use App\Events\SpeedtestBenchmarkFailed; use App\Events\SpeedtestCompleted; use App\Events\SpeedtestFailed; use App\Jobs\Influxdb\v2\WriteResult; @@ -114,5 +115,10 @@ public function subscribe(Dispatcher $events): void SpeedtestCompleted::class, [SpeedtestEventSubscriber::class, 'handleSpeedtestCompleted'] ); + + $events->listen( + SpeedtestBenchmarkFailed::class, + [SpeedtestEventSubscriber::class, 'handleSpeedtestBenchmarkFailed'] + ); } } From f096a116b4d7461f7fbdb3277e3fde55554978ae Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Tue, 29 Apr 2025 21:01:48 +0200 Subject: [PATCH 11/31] placeholder change --- app/Filament/Pages/Settings/NotificationPage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Filament/Pages/Settings/NotificationPage.php b/app/Filament/Pages/Settings/NotificationPage.php index d58afdce4..1ea0bf7f8 100755 --- a/app/Filament/Pages/Settings/NotificationPage.php +++ b/app/Filament/Pages/Settings/NotificationPage.php @@ -120,7 +120,7 @@ public function form(Form $form): Form ->schema([ Forms\Components\TextInput::make('url') ->label('URL') - ->placeholder('http://apprise:8000/notify/apprise') + ->placeholder('http://apprise:8000/notify') ->helperText('The URL to your Apprise instance.') ->maxLength(2000) ->required() From 0d7be504319831de81389c50ada4c5fcd29ebfc3 Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Wed, 30 Apr 2025 15:04:46 +0200 Subject: [PATCH 12/31] Disbale repeaters --- app/Filament/Pages/Settings/NotificationPage.php | 8 ++++++++ .../filament/forms/notifications-deprecation.blade.php | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/Filament/Pages/Settings/NotificationPage.php b/app/Filament/Pages/Settings/NotificationPage.php index 1ea0bf7f8..ac66135b4 100755 --- a/app/Filament/Pages/Settings/NotificationPage.php +++ b/app/Filament/Pages/Settings/NotificationPage.php @@ -15,6 +15,7 @@ use App\Actions\Notifications\SendWebhookTestNotification; use App\Settings\NotificationSettings; use Filament\Forms; +use Filament\Forms\Get; use Filament\Forms\Form; use Filament\Pages\SettingsPage; use Illuminate\Support\Facades\Auth; @@ -258,6 +259,7 @@ public function form(Form $form): Form ]), Forms\Components\Repeater::make('pushover_webhooks') ->label('Pushover Webhooks') + ->addable(false) ->schema([ Forms\Components\TextInput::make('url') ->label('URL') @@ -315,6 +317,7 @@ public function form(Form $form): Form ]), Forms\Components\Repeater::make('discord_webhooks') ->label('Webhooks') + ->addable(false) ->schema([ Forms\Components\TextInput::make('url') ->placeholder('https://discord.com/api/webhooks/longstringofcharacters') @@ -359,6 +362,7 @@ public function form(Form $form): Form ]), Forms\Components\Repeater::make('gotify_webhooks') ->label('Webhooks') + ->addable(false) ->schema([ Forms\Components\TextInput::make('url') ->placeholder('https://example.com/message?token=') @@ -403,6 +407,7 @@ public function form(Form $form): Form ]), Forms\Components\Repeater::make('slack_webhooks') ->label('Webhooks') + ->addable(false) ->schema([ Forms\Components\TextInput::make('url') ->placeholder('https://hooks.slack.com/services/abc/xyz') @@ -447,6 +452,7 @@ public function form(Form $form): Form ]), Forms\Components\Repeater::make('ntfy_webhooks') ->label('Webhooks') + ->addable(false) ->schema([ Forms\Components\TextInput::make('url') ->maxLength(2000) @@ -506,6 +512,7 @@ public function form(Form $form): Form ]), Forms\Components\Repeater::make('healthcheck_webhooks') ->label('webhooks') + ->addable(false) ->schema([ Forms\Components\TextInput::make('url') ->placeholder('https://hc-ping.com/your-uuid-here') @@ -556,6 +563,7 @@ public function form(Form $form): Form ]), Forms\Components\Repeater::make('telegram_recipients') ->label('Recipients') + ->addable(false) ->schema([ Forms\Components\TextInput::make('telegram_chat_id') ->placeholder('12345678910') diff --git a/resources/views/filament/forms/notifications-deprecation.blade.php b/resources/views/filament/forms/notifications-deprecation.blade.php index f41f2dd5b..d8df59959 100644 --- a/resources/views/filament/forms/notifications-deprecation.blade.php +++ b/resources/views/filament/forms/notifications-deprecation.blade.php @@ -1,7 +1,7 @@

- Deprecation Notice:
- Support for Pushover, Discord, Ntfy, Gotify, Healthchecks, Slack, and Telegram will be removed soon.
+ Deprecation Notice:
+ Support for Pushover, Discord, Ntfy, Gotify, Healthchecks, Slack, and Telegram will be removed soon and can no longer be added.
We recommend switching to Apprise for future notifications.

From 512c25c0566e7e7f8073c6b5e45868258662d781 Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Wed, 30 Apr 2025 15:05:50 +0200 Subject: [PATCH 13/31] Lint --- app/Filament/Pages/Settings/NotificationPage.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/Filament/Pages/Settings/NotificationPage.php b/app/Filament/Pages/Settings/NotificationPage.php index ac66135b4..b8b3ea5d5 100755 --- a/app/Filament/Pages/Settings/NotificationPage.php +++ b/app/Filament/Pages/Settings/NotificationPage.php @@ -15,7 +15,6 @@ use App\Actions\Notifications\SendWebhookTestNotification; use App\Settings\NotificationSettings; use Filament\Forms; -use Filament\Forms\Get; use Filament\Forms\Form; use Filament\Pages\SettingsPage; use Illuminate\Support\Facades\Auth; From cff30bc82309cea4ae1c893244c12eee0665421d Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Wed, 30 Apr 2025 19:50:37 +0200 Subject: [PATCH 14/31] Add test command for notifications --- app/Console/Commands/TestNotification.php | 52 +++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 app/Console/Commands/TestNotification.php diff --git a/app/Console/Commands/TestNotification.php b/app/Console/Commands/TestNotification.php new file mode 100644 index 000000000..2bf5d2026 --- /dev/null +++ b/app/Console/Commands/TestNotification.php @@ -0,0 +1,52 @@ +argument('type'); + $channel = $this->option('channel'); + + $this->info("Creating fake result for type: {$type}"); + + $result = Result::factory()->create([ + 'status' => 'completed', + ]); + + $this->info("Dispatching {$channel} notification..."); + + match ("{$channel}-{$type}") { + 'apprise-completed' => AppriseCompleted::dispatch($result), + 'apprise-threshold' => AppriseThreshold::dispatch($result), + 'mail-completed' => MailCompleted::dispatch($result), + 'mail-threshold' => MailThreshold::dispatch($result), + 'database-completed' => DatabaseCompleted::dispatch($result), + 'database-threshold' => DatabaseThreshold::dispatch($result), + 'webhook-completed' => WebhookCompleted::dispatch($result), + 'webhook-threshold' => WebhookThreshold::dispatch($result), + }; + + $this->info('βœ… Notification dispatched!'); + + return self::SUCCESS; + } +} From 1fbad1fc7c03388367f5cabd6d8588c512d1e6d6 Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Sun, 4 May 2025 21:25:52 +0200 Subject: [PATCH 15/31] add SpeedtestNotificationData --- .../SendSpeedtestCompletedNotification.php | 19 ++--- .../SendSpeedtestThresholdNotification.php | 15 ++-- .../SendSpeedtestCompletedNotification.php | 19 ++--- app/Mail/SpeedtestCompletedMail.php | 17 +---- app/Mail/SpeedtestThresholdMail.php | 13 +--- app/Models/Traits/ResultDataAttributes.php | 72 ++++++++++++++++++- .../SpeedtestNotificationData.php | 27 ------- .../SpeedtestNotificationData.php | 48 +++++++++++++ 8 files changed, 135 insertions(+), 95 deletions(-) delete mode 100644 app/Notifications/SpeedtestNotificationData.php create mode 100644 app/Services/Notifications/SpeedtestNotificationData.php diff --git a/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php b/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php index 77c39c48a..caa4f3eb3 100644 --- a/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php +++ b/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php @@ -2,8 +2,8 @@ namespace App\Jobs\Notifications\Apprise; -use App\Helpers\Number; use App\Models\Result; +use App\Services\Notifications\SpeedtestNotificationData; use App\Settings\NotificationSettings; use GuzzleHttp\Client; use GuzzleHttp\Exception\RequestException; @@ -11,7 +11,6 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Queue\Queueable; use Illuminate\Support\Facades\Log; -use Illuminate\Support\Str; class SendSpeedtestCompletedNotification implements ShouldQueue { @@ -40,19 +39,9 @@ public function handle(): void return; } - $payload = view('apprise.speedtest-completed', [ - 'id' => $this->result->id, - 'service' => Str::title($this->result->service->getLabel()), - 'serverName' => $this->result->server_name, - 'serverId' => $this->result->server_id, - 'isp' => $this->result->isp, - 'ping' => round($this->result->ping).' ms', - 'download' => Number::toBitRate(bits: $this->result->download_bits, precision: 2), - 'upload' => Number::toBitRate(bits: $this->result->upload_bits, precision: 2), - 'packetLoss' => $this->result->packet_loss, - 'speedtest_url' => $this->result->result_url, - 'url' => url('/admin/results'), - ])->render(); + $data = SpeedtestNotificationData::make($this->result); + + $payload = view('apprise.speedtest-completed', $data)->render(); foreach ($notificationSettings->apprise_webhooks as $webhook) { if (empty($webhook['service_url']) || empty($webhook['url'])) { diff --git a/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php b/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php index 9727d5b1a..ff6487ff9 100644 --- a/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php +++ b/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php @@ -4,6 +4,7 @@ use App\Helpers\Number; use App\Models\Result; +use App\Services\Notifications\SpeedtestNotificationData; use App\Settings\NotificationSettings; use App\Settings\ThresholdSettings; use GuzzleHttp\Client; @@ -12,7 +13,6 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Queue\Queueable; use Illuminate\Support\Facades\Log; -use Illuminate\Support\Str; class SendSpeedtestThresholdNotification implements ShouldQueue { @@ -70,16 +70,9 @@ public function handle(): void return; } - $payload = view('apprise.speedtest-threshold', [ - 'id' => $this->result->id, - 'service' => Str::title($this->result->service->getLabel()), - 'serverName' => $this->result->server_name, - 'serverId' => $this->result->server_id, - 'isp' => $this->result->isp, - 'metrics' => $failed, - 'speedtest_url' => $this->result->result_url, - 'url' => url('/admin/results'), - ])->render(); + $data = SpeedtestNotificationData::make($this->result); + + $payload = view('apprise.speedtest-threshold', $data)->render(); $client = new Client; diff --git a/app/Jobs/Notifications/Webhook/SendSpeedtestCompletedNotification.php b/app/Jobs/Notifications/Webhook/SendSpeedtestCompletedNotification.php index ba9887459..74387226e 100644 --- a/app/Jobs/Notifications/Webhook/SendSpeedtestCompletedNotification.php +++ b/app/Jobs/Notifications/Webhook/SendSpeedtestCompletedNotification.php @@ -3,12 +3,12 @@ namespace App\Jobs\Notifications\Webhook; use App\Models\Result; +use App\Services\Notifications\SpeedtestNotificationData; use App\Settings\NotificationSettings; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Support\Facades\Log; -use Illuminate\Support\Str; use Spatie\WebhookServer\WebhookCall; class SendSpeedtestCompletedNotification implements ShouldQueue @@ -38,23 +38,12 @@ public function handle(): void return; } + $data = SpeedtestNotificationData::make($this->result); + foreach ($notificationSettings->webhook_urls as $url) { WebhookCall::create() ->url($url['url']) - ->payload([ - 'result_id' => $this->result->id, - 'site_name' => config('app.name'), - 'service' => Str::title($this->result->service->getLabel()), - 'serverName' => $this->result->server_name, - 'serverId' => $this->result->server_id, - 'isp' => $this->result->isp, - 'ping' => $this->result->ping, - 'download' => $this->result->downloadBits, - 'upload' => $this->result->uploadBits, - 'packetLoss' => $this->result->packet_loss, - 'speedtest_url' => $this->result->result_url, - 'url' => url('/admin/results'), - ]) + ->payload($data) ->doNotSign() ->dispatch(); } diff --git a/app/Mail/SpeedtestCompletedMail.php b/app/Mail/SpeedtestCompletedMail.php index 6f7295771..03b5644f0 100644 --- a/app/Mail/SpeedtestCompletedMail.php +++ b/app/Mail/SpeedtestCompletedMail.php @@ -2,15 +2,14 @@ namespace App\Mail; -use App\Helpers\Number; use App\Models\Result; +use App\Services\Notifications\SpeedtestNotificationData; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Str; class SpeedtestCompletedMail extends Mailable implements ShouldQueue { @@ -42,19 +41,7 @@ public function content(): Content { return new Content( markdown: 'emails.speedtest-completed', - with: [ - 'id' => $this->result->id, - 'service' => Str::title($this->result->service->getLabel()), - 'serverName' => $this->result->server_name, - 'serverId' => $this->result->server_id, - 'isp' => $this->result->isp, - 'ping' => round($this->result->ping, 2).' ms', - 'download' => Number::toBitRate(bits: $this->result->download_bits, precision: 2), - 'upload' => Number::toBitRate(bits: $this->result->upload_bits, precision: 2), - 'packetLoss' => is_numeric($this->result->packet_loss) ? $this->result->packet_loss : 'n/a', - 'speedtest_url' => $this->result->result_url, - 'url' => url('/admin/results'), - ], + with: SpeedtestNotificationData::make($this->result) ); } } diff --git a/app/Mail/SpeedtestThresholdMail.php b/app/Mail/SpeedtestThresholdMail.php index 94ad14af9..82dbe9b4b 100644 --- a/app/Mail/SpeedtestThresholdMail.php +++ b/app/Mail/SpeedtestThresholdMail.php @@ -3,13 +3,13 @@ namespace App\Mail; use App\Models\Result; +use App\Services\Notifications\SpeedtestNotificationData; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Str; class SpeedtestThresholdMail extends Mailable implements ShouldQueue { @@ -42,16 +42,7 @@ public function content(): Content { return new Content( markdown: 'emails.speedtest-threshold', - with: [ - 'id' => $this->result->id, - 'service' => Str::title($this->result->service->getLabel()), - 'serverName' => $this->result->server_name, - 'serverId' => $this->result->server_id, - 'isp' => $this->result->isp, - 'speedtest_url' => $this->result->result_url, - 'url' => url('/admin/results'), - 'metrics' => $this->metrics, - ], + with: SpeedtestNotificationData::make($this->result) ); } } diff --git a/app/Models/Traits/ResultDataAttributes.php b/app/Models/Traits/ResultDataAttributes.php index c4fe9e26b..abd3982c8 100644 --- a/app/Models/Traits/ResultDataAttributes.php +++ b/app/Models/Traits/ResultDataAttributes.php @@ -59,7 +59,17 @@ protected function downloadlatencyiqm(): Attribute } /** - * Get the result's download jitter in milliseconds. + * Get the result's download latency jitter in milliseconds. + */ + protected function downloadLatencyJitter(): Attribute + { + return Attribute::make( + get: fn () => Arr::get($this->data, 'download.latency.jitter'), + ); + } + + /** + * Get the result's error message in milliseconds. */ protected function errorMessage(): Attribute { @@ -108,6 +118,26 @@ protected function pingJitter(): Attribute ); } + /** + * Get the result's ping low latency in milliseconds. + */ + protected function pingLow(): Attribute + { + return Attribute::make( + get: fn () => Arr::get($this->data, 'ping.low'), + ); + } + + /** + * Get the result's ping high latency in milliseconds. + */ + protected function pingHigh(): Attribute + { + return Attribute::make( + get: fn () => Arr::get($this->data, 'ping.high'), + ); + } + /** * Get the result's server ID. */ @@ -158,6 +188,36 @@ protected function serverLocation(): Attribute ); } + /** + * Get the result's server country. + */ + protected function serverCountry(): Attribute + { + return Attribute::make( + get: fn () => Arr::get($this->data, 'server.country'), + ); + } + + /** + * Get the result's server IP address. + */ + protected function serverIp(): Attribute + { + return Attribute::make( + get: fn () => Arr::get($this->data, 'server.ip'), + ); + } + + /** + * Get the result's server port. + */ + protected function serverPort(): Attribute + { + return Attribute::make( + get: fn () => Arr::get($this->data, 'server.port'), + ); + } + /** * Get the result's upload in bits. */ @@ -207,4 +267,14 @@ protected function uploadlatencyiqm(): Attribute get: fn () => Arr::get($this->data, 'upload.latency.iqm'), ); } + + /** + * Get the result's upload latency jitter in milliseconds. + */ + protected function uploadLatencyJitter(): Attribute + { + return Attribute::make( + get: fn () => Arr::get($this->data, 'upload.latency.jitter'), + ); + } } diff --git a/app/Notifications/SpeedtestNotificationData.php b/app/Notifications/SpeedtestNotificationData.php deleted file mode 100644 index 3c8e7502c..000000000 --- a/app/Notifications/SpeedtestNotificationData.php +++ /dev/null @@ -1,27 +0,0 @@ - $result->id, - 'service' => Str::title($result->service->getLabel()), - 'serverName' => $result->server_name, - 'serverId' => $result->server_id, - 'isp' => $result->isp, - 'ping' => round($result->ping, 2).' ms', - 'download' => Number::toBitRate(bits: $result->download_bits, precision: 2), - 'upload' => Number::toBitRate(bits: $result->upload_bits, precision: 2), - 'packetLoss' => is_numeric($result->packet_loss) ? $result->packet_loss : 'n/a', - 'speedtest_url' => $result->result_url, - 'url' => url('/admin/results'), - ]; - } -} diff --git a/app/Services/Notifications/SpeedtestNotificationData.php b/app/Services/Notifications/SpeedtestNotificationData.php new file mode 100644 index 000000000..292c1b610 --- /dev/null +++ b/app/Services/Notifications/SpeedtestNotificationData.php @@ -0,0 +1,48 @@ + $result->id, + 'service' => Str::title($result->service->getLabel()), + 'serverName' => $result->server_name, + 'serverId' => $result->server_id, + 'isp' => $result->isp, + 'ping' => round($result->ping, 2).' ms', + 'download' => Number::toBitRate(bits: $result->download_bits, precision: 2), + 'upload' => Number::toBitRate(bits: $result->upload_bits, precision: 2), + 'packetLoss' => is_numeric($result->packet_loss) ? $result->packet_loss : 'n/a'.' %', + 'pingJitter' => $result->ping_jitter.' ms', + 'pingLow' => $result->ping_low.' ms', + 'pingHigh' => $result->ping_high.' ms', + 'downloadBytes' => $result->download_bytes, + 'downloadLatencyIqm' => $result->download_latency_iqm.' ms', + 'downloadLatencyLow' => $result->download_latency_low.' ms', + 'downloadLatencyHigh' => $result->download_latency_high.' ms', + 'downloadLatencyJitter' => $result->download_latency_jitter.' ms', + 'uploadBytes' => $result->upload_bytes, + 'uploadLatencyIqm' => $result->upload_latency_iqm.' ms', + 'uploadLatencyLow' => $result->upload_latency_low.' ms', + 'uploadLatencyHigh' => $result->upload_latency_high.' ms', + 'uploadLatencyJitter' => $result->upload_latency_jitter.' ms', + 'externalIp' => $result->ip_address, + 'serverHost' => $result->server_host, + 'serverPort' => $result->server_port, + 'serverLocation' => $result->server_location, + 'serverCountry' => $result->server_country, + 'serverIp' => $result->server_ip, + 'speedtest_url' => $result->result_url, + 'url' => url('/admin/results'), + 'metrics' => $failed, + 'app_name' => config('app.name'), + ]; + } +} From f6251f2bd72fd310a02375cda908ed5f44a30858 Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Wed, 7 May 2025 20:18:49 +0200 Subject: [PATCH 16/31] undo data --- app/Models/Traits/ResultDataAttributes.php | 30 ---------------------- 1 file changed, 30 deletions(-) diff --git a/app/Models/Traits/ResultDataAttributes.php b/app/Models/Traits/ResultDataAttributes.php index abd3982c8..52e99caa7 100644 --- a/app/Models/Traits/ResultDataAttributes.php +++ b/app/Models/Traits/ResultDataAttributes.php @@ -58,16 +58,6 @@ protected function downloadlatencyiqm(): Attribute ); } - /** - * Get the result's download latency jitter in milliseconds. - */ - protected function downloadLatencyJitter(): Attribute - { - return Attribute::make( - get: fn () => Arr::get($this->data, 'download.latency.jitter'), - ); - } - /** * Get the result's error message in milliseconds. */ @@ -118,26 +108,6 @@ protected function pingJitter(): Attribute ); } - /** - * Get the result's ping low latency in milliseconds. - */ - protected function pingLow(): Attribute - { - return Attribute::make( - get: fn () => Arr::get($this->data, 'ping.low'), - ); - } - - /** - * Get the result's ping high latency in milliseconds. - */ - protected function pingHigh(): Attribute - { - return Attribute::make( - get: fn () => Arr::get($this->data, 'ping.high'), - ); - } - /** * Get the result's server ID. */ From 35a66be696543f850be533b545faf5fd34f9fa0a Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Wed, 7 May 2025 20:20:51 +0200 Subject: [PATCH 17/31] Undo data --- app/Models/Traits/ResultDataAttributes.php | 42 +--------------------- 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/app/Models/Traits/ResultDataAttributes.php b/app/Models/Traits/ResultDataAttributes.php index 52e99caa7..c4fe9e26b 100644 --- a/app/Models/Traits/ResultDataAttributes.php +++ b/app/Models/Traits/ResultDataAttributes.php @@ -59,7 +59,7 @@ protected function downloadlatencyiqm(): Attribute } /** - * Get the result's error message in milliseconds. + * Get the result's download jitter in milliseconds. */ protected function errorMessage(): Attribute { @@ -158,36 +158,6 @@ protected function serverLocation(): Attribute ); } - /** - * Get the result's server country. - */ - protected function serverCountry(): Attribute - { - return Attribute::make( - get: fn () => Arr::get($this->data, 'server.country'), - ); - } - - /** - * Get the result's server IP address. - */ - protected function serverIp(): Attribute - { - return Attribute::make( - get: fn () => Arr::get($this->data, 'server.ip'), - ); - } - - /** - * Get the result's server port. - */ - protected function serverPort(): Attribute - { - return Attribute::make( - get: fn () => Arr::get($this->data, 'server.port'), - ); - } - /** * Get the result's upload in bits. */ @@ -237,14 +207,4 @@ protected function uploadlatencyiqm(): Attribute get: fn () => Arr::get($this->data, 'upload.latency.iqm'), ); } - - /** - * Get the result's upload latency jitter in milliseconds. - */ - protected function uploadLatencyJitter(): Attribute - { - return Attribute::make( - get: fn () => Arr::get($this->data, 'upload.latency.jitter'), - ); - } } From d27e0c3b9214fee703de591000c6280808989033 Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Thu, 8 May 2025 12:49:02 +0200 Subject: [PATCH 18/31] Fix the download upload latency --- app/Models/Traits/ResultDataAttributes.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Models/Traits/ResultDataAttributes.php b/app/Models/Traits/ResultDataAttributes.php index 805ca9d29..9f0709e7f 100644 --- a/app/Models/Traits/ResultDataAttributes.php +++ b/app/Models/Traits/ResultDataAttributes.php @@ -21,7 +21,7 @@ protected function downloadBits(): Attribute /** * Get the result's download jitter in milliseconds. */ - protected function downloadJitter(): Attribute + protected function downloadlatencyJitter(): Attribute { return Attribute::make( get: fn () => Arr::get($this->data, 'download.latency.jitter'), @@ -171,7 +171,7 @@ protected function uploadBits(): Attribute /** * Get the result's upload jitter in milliseconds. */ - protected function uploadJitter(): Attribute + protected function uploadLatencyjitter(): Attribute { return Attribute::make( get: fn () => Arr::get($this->data, 'upload.latency.jitter'), From 080a442cff02acc2298aa67df16f866328c1bcb6 Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Thu, 8 May 2025 12:50:42 +0200 Subject: [PATCH 19/31] fix style --- app/Models/Traits/ResultDataAttributes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/Traits/ResultDataAttributes.php b/app/Models/Traits/ResultDataAttributes.php index 9f0709e7f..1ee23f9bf 100644 --- a/app/Models/Traits/ResultDataAttributes.php +++ b/app/Models/Traits/ResultDataAttributes.php @@ -171,7 +171,7 @@ protected function uploadBits(): Attribute /** * Get the result's upload jitter in milliseconds. */ - protected function uploadLatencyjitter(): Attribute + protected function uploadlatencyjitter(): Attribute { return Attribute::make( get: fn () => Arr::get($this->data, 'upload.latency.jitter'), From 5bc2e1759f75b4ed1c96e942016ac718b76229d8 Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Tue, 27 May 2025 20:51:35 +0200 Subject: [PATCH 20/31] chore clean up --- .../Pages/Settings/NotificationPage.php | 141 ++++++------------ 1 file changed, 47 insertions(+), 94 deletions(-) diff --git a/app/Filament/Pages/Settings/NotificationPage.php b/app/Filament/Pages/Settings/NotificationPage.php index abbc961cf..0251ca145 100755 --- a/app/Filament/Pages/Settings/NotificationPage.php +++ b/app/Filament/Pages/Settings/NotificationPage.php @@ -21,7 +21,9 @@ use Filament\Forms\Components\Grid; use Filament\Forms\Components\Repeater; use Filament\Forms\Components\Section; +use Filament\Forms\Components\TextInput; use Filament\Forms\Components\Toggle; +use Filament\Forms\Components\View; use Filament\Forms\Form; use Filament\Pages\SettingsPage; use Illuminate\Support\Facades\Auth; @@ -64,13 +66,8 @@ public function form(Form $form): Form 'default' => 1, ]) ->schema([ - Forms\Components\View::make('filament.forms.notifications-deprecation') + View::make('filament.forms.notifications-deprecation') ->columnSpanFull(), - Forms\Components\Section::make('Database') - ->description('Notifications sent to this channel will show up under the πŸ”” icon in the header.') - ->schema([ - Forms\Components\Toggle::make('database_enabled') - ->label('Enable Database Notifications') Section::make('Database') ->description('Notifications sent to this channel will show up under the πŸ”” icon in the header.') ->schema([ @@ -105,39 +102,39 @@ public function form(Form $form): Form 'md' => 2, ]), - Forms\Components\Section::make('Apprise') + Section::make('Apprise') ->description('The Apprise Notification Library enables sending notifications to a wide range of services.') ->schema([ - Forms\Components\Toggle::make('apprise_enabled') + Toggle::make('apprise_enabled') ->label('Enable Apprise Notifications') ->reactive() ->columnSpanFull(), - Forms\Components\Grid::make([ + Grid::make([ 'default' => 1, ]) ->hidden(fn (Forms\Get $get) => $get('apprise_enabled') !== true) ->schema([ - Forms\Components\Fieldset::make('Triggers') + Fieldset::make('Triggers') ->schema([ - Forms\Components\Toggle::make('apprise_on_speedtest_run') + Toggle::make('apprise_on_speedtest_run') ->label('Notify on every speedtest run') ->columnSpanFull(), - Forms\Components\Toggle::make('apprise_on_threshold_failure') + Toggle::make('apprise_on_threshold_failure') ->label('Notify on threshold failures') ->columnSpanFull(), ]), - Forms\Components\Repeater::make('apprise_webhooks') + Repeater::make('apprise_webhooks') ->label('Apprise Webhooks') ->hint(new HtmlString('Apprise Documentation')) ->schema([ - Forms\Components\TextInput::make('url') + TextInput::make('url') ->label('URL') ->placeholder('http://apprise:8000/notify') ->helperText('The URL to your Apprise instance.') ->maxLength(2000) ->required() ->url(), - Forms\Components\TextInput::make('service_url') + TextInput::make('service_url') ->label('Service URL') ->placeholder('discord://WebhookID/WebhookToken') ->helperText('The service URL where the notification will be sent.') @@ -145,8 +142,8 @@ public function form(Form $form): Form ->required(), ]) ->columnSpanFull(), - Forms\Components\Actions::make([ - Forms\Components\Actions\Action::make('test apprise') + Actions::make([ + Action::make('test apprise') ->label('Test Apprise') ->action(fn (Forms\Get $get) => SendAppriseTestNotification::run( webhooks: $get('apprise_webhooks') @@ -161,37 +158,37 @@ public function form(Form $form): Form 'md' => 2, ]), - Forms\Components\Section::make('Mail') + Section::make('Mail') ->schema([ - Forms\Components\Toggle::make('mail_enabled') + Toggle::make('mail_enabled') ->label('Enable Mail Notifications') ->reactive() ->columnSpanFull(), - Forms\Components\Grid::make([ + Grid::make([ 'default' => 1, ]) ->hidden(fn (Forms\Get $get) => $get('mail_enabled') !== true) ->schema([ - Forms\Components\Fieldset::make('Triggers') + Fieldset::make('Triggers') ->schema([ - Forms\Components\Toggle::make('mail_on_speedtest_run') + Toggle::make('mail_on_speedtest_run') ->label('Notify on every speedtest run') ->columnSpanFull(), - Forms\Components\Toggle::make('mail_on_threshold_failure') + Toggle::make('mail_on_threshold_failure') ->label('Notify on threshold failures') ->columnSpanFull(), ]), - Forms\Components\Repeater::make('mail_recipients') + Repeater::make('mail_recipients') ->label('Recipients') ->schema([ - Forms\Components\TextInput::make('email_address') + TextInput::make('email_address') ->placeholder('your@email.com') ->email() ->required(), ]) ->columnSpanFull(), - Forms\Components\Actions::make([ - Forms\Components\Actions\Action::make('test mail') + Actions::make([ + Action::make('test mail') ->label('Test mail channel') ->action(fn (Forms\Get $get) => SendMailTestNotification::run(recipients: $get('mail_recipients'))) ->hidden(fn (Forms\Get $get) => ! count($get('mail_recipients'))), @@ -204,38 +201,38 @@ public function form(Form $form): Form 'md' => 2, ]), - Forms\Components\Section::make('Webhook') + Section::make('Webhook') ->schema([ - Forms\Components\Toggle::make('webhook_enabled') + Toggle::make('webhook_enabled') ->label('Enable Webhook Notifications') ->reactive() ->columnSpanFull(), - Forms\Components\Grid::make([ + Grid::make([ 'default' => 1, ]) ->hidden(fn (Forms\Get $get) => $get('webhook_enabled') !== true) ->schema([ - Forms\Components\Fieldset::make('Triggers') + Fieldset::make('Triggers') ->schema([ - Forms\Components\Toggle::make('webhook_on_speedtest_run') + Toggle::make('webhook_on_speedtest_run') ->label('Notify on every speedtest run') ->columnSpan(2), - Forms\Components\Toggle::make('webhook_on_threshold_failure') + Toggle::make('webhook_on_threshold_failure') ->label('Notify on threshold failures') ->columnSpan(2), ]), - Forms\Components\Repeater::make('webhook_urls') + Repeater::make('webhook_urls') ->label('Recipients') ->schema([ - Forms\Components\TextInput::make('url') + TextInput::make('url') ->placeholder('https://webhook.site/longstringofcharacters') ->maxLength(2000) ->required() ->url(), ]) ->columnSpanFull(), - Forms\Components\Actions::make([ - Forms\Components\Actions\Action::make('test webhook') + Actions::make([ + Action::make('test webhook') ->label('Test webhook channel') ->action(fn (Forms\Get $get) => SendWebhookTestNotification::run(webhooks: $get('webhook_urls'))) ->hidden(fn (Forms\Get $get) => ! count($get('webhook_urls'))), @@ -272,18 +269,18 @@ public function form(Form $form): Form ->label('Pushover Webhooks') ->addable(false) ->schema([ - Forms\Components\TextInput::make('url') + TextInput::make('url') ->label('URL') ->placeholder('http://api.pushover.net/1/messages.json') ->maxLength(2000) ->required() ->url(), - Forms\Components\TextInput::make('user_key') + TextInput::make('user_key') ->label('User Key') ->placeholder('Your Pushover User Key') ->maxLength(200) ->required(), - Forms\Components\TextInput::make('api_token') + TextInput::make('api_token') ->label('API Token') ->placeholder('Your Pushover API Token') ->maxLength(200) @@ -330,7 +327,7 @@ public function form(Form $form): Form ->label('Webhooks') ->addable(false) ->schema([ - Forms\Components\TextInput::make('url') + TextInput::make('url') ->placeholder('https://discord.com/api/webhooks/longstringofcharacters') ->maxLength(2000) ->required() @@ -375,7 +372,7 @@ public function form(Form $form): Form ->label('Webhooks') ->addable(false) ->schema([ - Forms\Components\TextInput::make('url') + TextInput::make('url') ->placeholder('https://example.com/message?token=') ->maxLength(2000) ->required() @@ -420,7 +417,7 @@ public function form(Form $form): Form ->label('Webhooks') ->addable(false) ->schema([ - Forms\Components\TextInput::make('url') + TextInput::make('url') ->placeholder('https://hooks.slack.com/services/abc/xyz') ->maxLength(2000) ->required() @@ -465,21 +462,21 @@ public function form(Form $form): Form ->label('Webhooks') ->addable(false) ->schema([ - Forms\Components\TextInput::make('url') + TextInput::make('url') ->maxLength(2000) ->placeholder('Your ntfy server url') ->required() ->url(), - Forms\Components\TextInput::make('topic') + TextInput::make('topic') ->label('Topic') ->placeholder('Your ntfy Topic') ->maxLength(200) ->required(), - Forms\Components\TextInput::make('username') + TextInput::make('username') ->label('Username') ->placeholder('Username for Basic Auth (optional)') ->maxLength(200), - Forms\Components\TextInput::make('password') + TextInput::make('password') ->label('Password') ->placeholder('Password for Basic Auth (optional)') ->password() @@ -525,7 +522,7 @@ public function form(Form $form): Form ->label('webhooks') ->addable(false) ->schema([ - Forms\Components\TextInput::make('url') + TextInput::make('url') ->placeholder('https://hc-ping.com/your-uuid-here') ->maxLength(2000) ->required() @@ -576,7 +573,7 @@ public function form(Form $form): Form ->label('Recipients') ->addable(false) ->schema([ - Forms\Components\TextInput::make('telegram_chat_id') + TextInput::make('telegram_chat_id') ->placeholder('12345678910') ->label('Telegram Chat ID') ->maxLength(50) @@ -596,50 +593,6 @@ public function form(Form $form): Form 'default' => 1, 'md' => 2, ]), - - Section::make('Webhook') - ->schema([ - Toggle::make('webhook_enabled') - ->label('Enable webhook notifications') - ->reactive() - ->columnSpanFull(), - Grid::make([ - 'default' => 1, - ]) - ->hidden(fn (Forms\Get $get) => $get('webhook_enabled') !== true) - ->schema([ - Fieldset::make('Triggers') - ->schema([ - Toggle::make('webhook_on_speedtest_run') - ->label('Notify on every speedtest run') - ->columnSpan(2), - Toggle::make('webhook_on_threshold_failure') - ->label('Notify on threshold failures') - ->columnSpan(2), - ]), - Repeater::make('webhook_urls') - ->label('Recipients') - ->schema([ - Forms\Components\TextInput::make('url') - ->placeholder('https://webhook.site/longstringofcharacters') - ->maxLength(2000) - ->required() - ->url(), - ]) - ->columnSpanFull(), - Actions::make([ - Action::make('test webhook') - ->label('Test webhook channel') - ->action(fn (Forms\Get $get) => SendWebhookTestNotification::run(webhooks: $get('webhook_urls'))) - ->hidden(fn (Forms\Get $get) => ! count($get('webhook_urls'))), - ]), - ]), - ]) - ->compact() - ->columns([ - 'default' => 1, - 'md' => 2, - ]), ]) ->columnSpan([ 'md' => 2, @@ -647,7 +600,7 @@ public function form(Form $form): Form Section::make() ->schema([ - Forms\Components\View::make('filament.forms.notifications-helptext'), + View::make('filament.forms.notifications-helptext'), ]) ->columnSpan([ 'md' => 1, From 6188c94f51440bb383ab6d07217f2a336e86918f Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Wed, 28 May 2025 18:20:52 +0200 Subject: [PATCH 21/31] bring on par with main --- app/Filament/Pages/Settings/NotificationPage.php | 6 ++---- .../filament/forms/notifications-deprecation.blade.php | 7 ------- 2 files changed, 2 insertions(+), 11 deletions(-) delete mode 100644 resources/views/filament/forms/notifications-deprecation.blade.php diff --git a/app/Filament/Pages/Settings/NotificationPage.php b/app/Filament/Pages/Settings/NotificationPage.php index b23c4330f..cf5c07b32 100755 --- a/app/Filament/Pages/Settings/NotificationPage.php +++ b/app/Filament/Pages/Settings/NotificationPage.php @@ -66,8 +66,6 @@ public function form(Form $form): Form 'default' => 1, ]) ->schema([ - View::make('filament.forms.notifications-deprecation') - ->columnSpanFull(), Section::make('Database') ->description('Notifications sent to this channel will show up under the πŸ”” icon in the header.') ->schema([ @@ -130,14 +128,14 @@ public function form(Form $form): Form TextInput::make('url') ->label('URL') ->placeholder('http://apprise:8000/notify') - ->helperText('The URL to your Apprise instance.') + ->helperText('Specify the URL of your Apprise instance β€” it must end with /notify.') ->maxLength(2000) ->required() ->url(), TextInput::make('service_url') ->label('Service URL') ->placeholder('discord://WebhookID/WebhookToken') - ->helperText('The service URL where the notification will be sent.') + ->helperText('Provide the service endpoint URL for notifications β€” this URL must already be defined in your Apprise configuration.') ->maxLength(200) ->required(), ]) diff --git a/resources/views/filament/forms/notifications-deprecation.blade.php b/resources/views/filament/forms/notifications-deprecation.blade.php deleted file mode 100644 index d8df59959..000000000 --- a/resources/views/filament/forms/notifications-deprecation.blade.php +++ /dev/null @@ -1,7 +0,0 @@ -
-

- Deprecation Notice:
- Support for Pushover, Discord, Ntfy, Gotify, Healthchecks, Slack, and Telegram will be removed soon and can no longer be added.
- We recommend switching to Apprise for future notifications. -

-
From 0b40a089f21cc6225445f5babd268be998cfbfb4 Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Wed, 28 May 2025 18:40:27 +0200 Subject: [PATCH 22/31] revert mail, webhook, database --- .../SendSpeedtestCompletedNotification.php | 27 ---- .../SendSpeedtestThresholdNotification.php | 99 --------------- .../SendSpeedtestCompletedNotification.php | 46 ------- .../SendSpeedtestThresholdNotification.php | 119 ------------------ .../SendSpeedtestCompletedNotification.php | 51 -------- .../SendSpeedtestCompletedNotification.php | 34 +++++ .../SendSpeedtestThresholdNotification.php | 101 +++++++++++++++ .../SendSpeedtestCompletedNotification.php | 39 ++++++ .../SendSpeedtestThresholdNotification.php | 117 +++++++++++++++++ .../SendSpeedtestCompletedNotification.php | 51 ++++++++ .../SendSpeedtestThresholdNotification.php | 84 ++++++------- app/Mail/SpeedtestCompletedMail.php | 17 ++- app/Mail/SpeedtestThresholdMail.php | 13 +- 13 files changed, 407 insertions(+), 391 deletions(-) delete mode 100644 app/Jobs/Notifications/Database/SendSpeedtestCompletedNotification.php delete mode 100644 app/Jobs/Notifications/Database/SendSpeedtestThresholdNotification.php delete mode 100644 app/Jobs/Notifications/Mail/SendSpeedtestCompletedNotification.php delete mode 100644 app/Jobs/Notifications/Mail/SendSpeedtestThresholdNotification.php delete mode 100644 app/Jobs/Notifications/Webhook/SendSpeedtestCompletedNotification.php create mode 100644 app/Listeners/Database/SendSpeedtestCompletedNotification.php create mode 100644 app/Listeners/Database/SendSpeedtestThresholdNotification.php create mode 100644 app/Listeners/Mail/SendSpeedtestCompletedNotification.php create mode 100644 app/Listeners/Mail/SendSpeedtestThresholdNotification.php create mode 100644 app/Listeners/Webhook/SendSpeedtestCompletedNotification.php rename app/{Jobs/Notifications => Listeners}/Webhook/SendSpeedtestThresholdNotification.php (50%) diff --git a/app/Jobs/Notifications/Database/SendSpeedtestCompletedNotification.php b/app/Jobs/Notifications/Database/SendSpeedtestCompletedNotification.php deleted file mode 100644 index fa16be537..000000000 --- a/app/Jobs/Notifications/Database/SendSpeedtestCompletedNotification.php +++ /dev/null @@ -1,27 +0,0 @@ -title('Speedtest completed') - ->success() - ->sendToDatabase($user); - } - } -} diff --git a/app/Jobs/Notifications/Database/SendSpeedtestThresholdNotification.php b/app/Jobs/Notifications/Database/SendSpeedtestThresholdNotification.php deleted file mode 100644 index 1747b5c79..000000000 --- a/app/Jobs/Notifications/Database/SendSpeedtestThresholdNotification.php +++ /dev/null @@ -1,99 +0,0 @@ -result = $result; - } - - /** - * Handle the job. - */ - public function handle(): void - { - $thresholdSettings = new ThresholdSettings; - - if (! $thresholdSettings->absolute_enabled) { - return; - } - - if ($thresholdSettings->absolute_download > 0) { - $this->absoluteDownloadThreshold($thresholdSettings); - } - - if ($thresholdSettings->absolute_upload > 0) { - $this->absoluteUploadThreshold($thresholdSettings); - } - - if ($thresholdSettings->absolute_ping > 0) { - $this->absolutePingThreshold($thresholdSettings); - } - } - - protected function absoluteDownloadThreshold(ThresholdSettings $thresholdSettings): void - { - if (! absoluteDownloadThresholdFailed($thresholdSettings->absolute_download, $this->result->download)) { - - return; - } - - foreach (User::all() as $user) { - Notification::make() - ->title('Download threshold breached!') - ->body('Speedtest #'.$this->result->id.' breached the download threshold of '.$thresholdSettings->absolute_download.' Mbps at '.Number::toBitRate($this->result->download_bits).'.') - ->warning() - ->sendToDatabase($user); - } - } - - protected function absoluteUploadThreshold(ThresholdSettings $thresholdSettings): void - { - if (! absoluteUploadThresholdFailed($thresholdSettings->absolute_upload, $this->result->upload)) { - - return; - } - - foreach (User::all() as $user) { - Notification::make() - ->title('Upload threshold breached!') - ->body('Speedtest #'.$this->result->id.' breached the upload threshold of '.$thresholdSettings->absolute_upload.' Mbps at '.Number::toBitRate($this->result->upload_bits).'.') - ->warning() - ->sendToDatabase($user); - } - } - - protected function absolutePingThreshold(ThresholdSettings $thresholdSettings): void - { - if (! absolutePingThresholdFailed($thresholdSettings->absolute_ping, $this->result->ping)) { - - return; - } - - foreach (User::all() as $user) { - Notification::make() - ->title('Ping threshold breached!') - ->body('Speedtest #'.$this->result->id.' breached the ping threshold of '.$thresholdSettings->absolute_ping.'ms at '.$this->result->ping.'ms.') - ->warning() - ->sendToDatabase($user); - } - } -} diff --git a/app/Jobs/Notifications/Mail/SendSpeedtestCompletedNotification.php b/app/Jobs/Notifications/Mail/SendSpeedtestCompletedNotification.php deleted file mode 100644 index 2292571eb..000000000 --- a/app/Jobs/Notifications/Mail/SendSpeedtestCompletedNotification.php +++ /dev/null @@ -1,46 +0,0 @@ -result = $result; - } - - /** - * Handle the job. - */ - public function handle(): void - { - $notificationSettings = new NotificationSettings; - - if (! count($notificationSettings->mail_recipients)) { - Log::warning('Mail recipients not found, check mail notification channel settings.'); - - return; - } - - foreach ($notificationSettings->mail_recipients as $recipient) { - Mail::to($recipient) - ->send(new SpeedtestCompletedMail($this->result)); - } - } -} diff --git a/app/Jobs/Notifications/Mail/SendSpeedtestThresholdNotification.php b/app/Jobs/Notifications/Mail/SendSpeedtestThresholdNotification.php deleted file mode 100644 index 364795c55..000000000 --- a/app/Jobs/Notifications/Mail/SendSpeedtestThresholdNotification.php +++ /dev/null @@ -1,119 +0,0 @@ -result = $result; - } - - /** - * Handle the job. - */ - public function handle(): void - { - $notificationSettings = new NotificationSettings; - - if (! count($notificationSettings->mail_recipients)) { - Log::warning('Mail recipients not found, check mail notification channel settings.'); - - return; - } - - $thresholdSettings = new ThresholdSettings; - - if (! $thresholdSettings->absolute_enabled) { - - return; - } - - $failed = []; - - if ($thresholdSettings->absolute_download > 0) { - array_push($failed, $this->absoluteDownloadThreshold($thresholdSettings)); - } - - if ($thresholdSettings->absolute_upload > 0) { - array_push($failed, $this->absoluteUploadThreshold($thresholdSettings)); - } - - if ($thresholdSettings->absolute_ping > 0) { - array_push($failed, $this->absolutePingThreshold($thresholdSettings)); - } - - $failed = array_filter($failed); - - if (! count($failed)) { - Log::warning('No threshold breaches found, skipping mail notification.'); - - return; - } - - foreach ($notificationSettings->mail_recipients as $recipient) { - Mail::to($recipient) - ->send(new SpeedtestThresholdMail($this->result, $failed)); - } - } - - protected function absoluteDownloadThreshold(ThresholdSettings $thresholdSettings): bool|array - { - if (! absoluteDownloadThresholdFailed($thresholdSettings->absolute_download, $this->result->download)) { - - return false; - } - - return [ - 'name' => 'Download', - 'threshold' => $thresholdSettings->absolute_download.' Mbps', - 'value' => Number::toBitRate(bits: $this->result->download_bits, precision: 2), - ]; - } - - protected function absoluteUploadThreshold(ThresholdSettings $thresholdSettings): bool|array - { - if (! absoluteUploadThresholdFailed($thresholdSettings->absolute_upload, $this->result->upload)) { - - return false; - } - - return [ - 'name' => 'Upload', - 'threshold' => $thresholdSettings->absolute_upload.' Mbps', - 'value' => Number::toBitRate(bits: $this->result->upload_bits, precision: 2), - ]; - } - - protected function absolutePingThreshold(ThresholdSettings $thresholdSettings): bool|array - { - if (! absolutePingThresholdFailed($thresholdSettings->absolute_ping, $this->result->ping)) { - - return false; - } - - return [ - 'name' => 'Ping', - 'threshold' => $thresholdSettings->absolute_ping.' ms', - 'value' => round($this->result->ping, 2).' ms', - ]; - } -} diff --git a/app/Jobs/Notifications/Webhook/SendSpeedtestCompletedNotification.php b/app/Jobs/Notifications/Webhook/SendSpeedtestCompletedNotification.php deleted file mode 100644 index 74387226e..000000000 --- a/app/Jobs/Notifications/Webhook/SendSpeedtestCompletedNotification.php +++ /dev/null @@ -1,51 +0,0 @@ -result = $result; - } - - /** - * Handle the job. - */ - public function handle(): void - { - $notificationSettings = new NotificationSettings; - - if (! count($notificationSettings->webhook_urls)) { - Log::warning('Webhook URLs not found, check webhook notification channel settings.'); - - return; - } - - $data = SpeedtestNotificationData::make($this->result); - - foreach ($notificationSettings->webhook_urls as $url) { - WebhookCall::create() - ->url($url['url']) - ->payload($data) - ->doNotSign() - ->dispatch(); - } - } -} diff --git a/app/Listeners/Database/SendSpeedtestCompletedNotification.php b/app/Listeners/Database/SendSpeedtestCompletedNotification.php new file mode 100644 index 000000000..14ea66605 --- /dev/null +++ b/app/Listeners/Database/SendSpeedtestCompletedNotification.php @@ -0,0 +1,34 @@ +database_enabled) { + return; + } + + if (! $notificationSettings->database_on_speedtest_run) { + return; + } + + foreach (User::all() as $user) { + Notification::make() + ->title('Speedtest completed') + ->success() + ->sendToDatabase($user); + } + } +} diff --git a/app/Listeners/Database/SendSpeedtestThresholdNotification.php b/app/Listeners/Database/SendSpeedtestThresholdNotification.php new file mode 100644 index 000000000..7439b52f1 --- /dev/null +++ b/app/Listeners/Database/SendSpeedtestThresholdNotification.php @@ -0,0 +1,101 @@ +database_enabled) { + return; + } + + if (! $notificationSettings->database_on_threshold_failure) { + return; + } + + $thresholdSettings = new ThresholdSettings; + + if (! $thresholdSettings->absolute_enabled) { + return; + } + + if ($thresholdSettings->absolute_download > 0) { + $this->absoluteDownloadThreshold(event: $event, thresholdSettings: $thresholdSettings); + } + + if ($thresholdSettings->absolute_upload > 0) { + $this->absoluteUploadThreshold(event: $event, thresholdSettings: $thresholdSettings); + } + + if ($thresholdSettings->absolute_ping > 0) { + $this->absolutePingThreshold(event: $event, thresholdSettings: $thresholdSettings); + } + } + + /** + * Send database notification if absolute download threshold is breached. + */ + protected function absoluteDownloadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): void + { + if (! absoluteDownloadThresholdFailed($thresholdSettings->absolute_download, $event->result->download)) { + return; + } + + foreach (User::all() as $user) { + Notification::make() + ->title('Download threshold breached!') + ->body('Speedtest #'.$event->result->id.' breached the download threshold of '.$thresholdSettings->absolute_download.' Mbps at '.Number::toBitRate($event->result->download_bits).'.') + ->warning() + ->sendToDatabase($user); + } + } + + /** + * Send database notification if absolute upload threshold is breached. + */ + protected function absoluteUploadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): void + { + if (! absoluteUploadThresholdFailed($thresholdSettings->absolute_upload, $event->result->upload)) { + return; + } + + foreach (User::all() as $user) { + Notification::make() + ->title('Upload threshold breached!') + ->body('Speedtest #'.$event->result->id.' breached the upload threshold of '.$thresholdSettings->absolute_upload.' Mbps at '.Number::toBitRate($event->result->upload_bits).'.') + ->warning() + ->sendToDatabase($user); + } + } + + /** + * Send database notification if absolute upload threshold is breached. + */ + protected function absolutePingThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): void + { + if (! absolutePingThresholdFailed($thresholdSettings->absolute_ping, $event->result->ping)) { + return; + } + + foreach (User::all() as $user) { + Notification::make() + ->title('Ping threshold breached!') + ->body('Speedtest #'.$event->result->id.' breached the ping threshold of '.$thresholdSettings->absolute_ping.'ms at '.$event->result->ping.'ms.') + ->warning() + ->sendToDatabase($user); + } + } +} diff --git a/app/Listeners/Mail/SendSpeedtestCompletedNotification.php b/app/Listeners/Mail/SendSpeedtestCompletedNotification.php new file mode 100644 index 000000000..2e731cd99 --- /dev/null +++ b/app/Listeners/Mail/SendSpeedtestCompletedNotification.php @@ -0,0 +1,39 @@ +mail_enabled) { + return; + } + + if (! $notificationSettings->mail_on_speedtest_run) { + return; + } + + if (! count($notificationSettings->mail_recipients)) { + Log::warning('Mail recipients not found, check mail notification channel settings.'); + + return; + } + + foreach ($notificationSettings->mail_recipients as $recipient) { + Mail::to($recipient) + ->send(new SpeedtestCompletedMail($event->result)); + } + } +} diff --git a/app/Listeners/Mail/SendSpeedtestThresholdNotification.php b/app/Listeners/Mail/SendSpeedtestThresholdNotification.php new file mode 100644 index 000000000..774851df5 --- /dev/null +++ b/app/Listeners/Mail/SendSpeedtestThresholdNotification.php @@ -0,0 +1,117 @@ +mail_enabled) { + return; + } + + if (! $notificationSettings->mail_on_threshold_failure) { + return; + } + + if (! count($notificationSettings->mail_recipients) > 0) { + Log::warning('Mail recipients not found, check mail notification channel settings.'); + + return; + } + + $thresholdSettings = new ThresholdSettings; + + if (! $thresholdSettings->absolute_enabled) { + return; + } + + $failed = []; + + if ($thresholdSettings->absolute_download > 0) { + array_push($failed, $this->absoluteDownloadThreshold(event: $event, thresholdSettings: $thresholdSettings)); + } + + if ($thresholdSettings->absolute_upload > 0) { + array_push($failed, $this->absoluteUploadThreshold(event: $event, thresholdSettings: $thresholdSettings)); + } + + if ($thresholdSettings->absolute_ping > 0) { + array_push($failed, $this->absolutePingThreshold(event: $event, thresholdSettings: $thresholdSettings)); + } + + $failed = array_filter($failed); + + if (! count($failed)) { + Log::warning('Failed mail thresholds not found, won\'t send notification.'); + + return; + } + + foreach ($notificationSettings->mail_recipients as $recipient) { + Mail::to($recipient) + ->send(new SpeedtestThresholdMail($event->result, $failed)); + } + } + + /** + * Build mail notification if absolute download threshold is breached. + */ + protected function absoluteDownloadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array + { + if (! absoluteDownloadThresholdFailed($thresholdSettings->absolute_download, $event->result->download)) { + return false; + } + + return [ + 'name' => 'Download', + 'threshold' => $thresholdSettings->absolute_download.' Mbps', + 'value' => Number::toBitRate(bits: $event->result->download_bits, precision: 2), + ]; + } + + /** + * Build mail notification if absolute upload threshold is breached. + */ + protected function absoluteUploadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array + { + if (! absoluteUploadThresholdFailed($thresholdSettings->absolute_upload, $event->result->upload)) { + return false; + } + + return [ + 'name' => 'Upload', + 'threshold' => $thresholdSettings->absolute_upload.' Mbps', + 'value' => Number::toBitRate(bits: $event->result->upload_bits, precision: 2), + ]; + } + + /** + * Build mail notification if absolute ping threshold is breached. + */ + protected function absolutePingThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array + { + if (! absolutePingThresholdFailed($thresholdSettings->absolute_ping, $event->result->ping)) { + return false; + } + + return [ + 'name' => 'Ping', + 'threshold' => $thresholdSettings->absolute_ping.' ms', + 'value' => round($event->result->ping, 2).' ms', + ]; + } +} diff --git a/app/Listeners/Webhook/SendSpeedtestCompletedNotification.php b/app/Listeners/Webhook/SendSpeedtestCompletedNotification.php new file mode 100644 index 000000000..85d42d2b9 --- /dev/null +++ b/app/Listeners/Webhook/SendSpeedtestCompletedNotification.php @@ -0,0 +1,51 @@ +webhook_enabled) { + return; + } + + if (! $notificationSettings->webhook_on_speedtest_run) { + return; + } + + if (! count($notificationSettings->webhook_urls)) { + Log::warning('Webhook urls not found, check webhook notification channel settings.'); + + return; + } + + foreach ($notificationSettings->webhook_urls as $url) { + WebhookCall::create() + ->url($url['url']) + ->payload([ + 'result_id' => $event->result->id, + 'site_name' => config('app.name'), + 'isp' => $event->result->isp, + 'ping' => $event->result->ping, + 'download' => $event->result->downloadBits, + 'upload' => $event->result->uploadBits, + 'packetLoss' => $event->result->packet_loss, + 'speedtest_url' => $event->result->result_url, + 'url' => url('/admin/results'), + ]) + ->doNotSign() + ->dispatch(); + } + } +} diff --git a/app/Jobs/Notifications/Webhook/SendSpeedtestThresholdNotification.php b/app/Listeners/Webhook/SendSpeedtestThresholdNotification.php similarity index 50% rename from app/Jobs/Notifications/Webhook/SendSpeedtestThresholdNotification.php rename to app/Listeners/Webhook/SendSpeedtestThresholdNotification.php index f63a56bd6..bb64866b4 100644 --- a/app/Jobs/Notifications/Webhook/SendSpeedtestThresholdNotification.php +++ b/app/Listeners/Webhook/SendSpeedtestThresholdNotification.php @@ -1,41 +1,33 @@ result = $result; - } - /** - * Handle the job. + * Handle the event. */ - public function handle(): void + public function handle(SpeedtestCompleted $event): void { $notificationSettings = new NotificationSettings; + if (! $notificationSettings->webhook_enabled) { + return; + } + + if (! $notificationSettings->webhook_on_threshold_failure) { + return; + } + if (! count($notificationSettings->webhook_urls)) { - Log::warning('Webhook URLs not found, check webhook notification channel settings.'); + Log::warning('Webhook urls not found, check webhook notification channel settings.'); return; } @@ -43,28 +35,27 @@ public function handle(): void $thresholdSettings = new ThresholdSettings; if (! $thresholdSettings->absolute_enabled) { - return; } $failed = []; if ($thresholdSettings->absolute_download > 0) { - array_push($failed, $this->absoluteDownloadThreshold($thresholdSettings)); + array_push($failed, $this->absoluteDownloadThreshold(event: $event, thresholdSettings: $thresholdSettings)); } if ($thresholdSettings->absolute_upload > 0) { - array_push($failed, $this->absoluteUploadThreshold($thresholdSettings)); + array_push($failed, $this->absoluteUploadThreshold(event: $event, thresholdSettings: $thresholdSettings)); } if ($thresholdSettings->absolute_ping > 0) { - array_push($failed, $this->absolutePingThreshold($thresholdSettings)); + array_push($failed, $this->absolutePingThreshold(event: $event, thresholdSettings: $thresholdSettings)); } $failed = array_filter($failed); if (! count($failed)) { - Log::warning('No threshold breaches found, skipping webhook notification.'); + Log::warning('Failed webhook thresholds not found, won\'t send notification.'); return; } @@ -73,14 +64,11 @@ public function handle(): void WebhookCall::create() ->url($url['url']) ->payload([ - 'result_id' => $this->result->id, + 'result_id' => $event->result->id, 'site_name' => config('app.name'), - 'service' => Str::title($this->result->service->getLabel()), - 'serverName' => $this->result->server_name, - 'serverId' => $this->result->server_id, - 'isp' => $this->result->isp, + 'isp' => $event->result->isp, 'metrics' => $failed, - 'speedtest_url' => $this->result->result_url, + 'speedtest_url' => $event->result->result_url, 'url' => url('/admin/results'), ]) ->doNotSign() @@ -88,45 +76,51 @@ public function handle(): void } } - protected function absoluteDownloadThreshold(ThresholdSettings $thresholdSettings): bool|array + /** + * Build webhook notification if absolute download threshold is breached. + */ + protected function absoluteDownloadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array { - if (! absoluteDownloadThresholdFailed($thresholdSettings->absolute_download, $this->result->download)) { - + if (! absoluteDownloadThresholdFailed($thresholdSettings->absolute_download, $event->result->download)) { return false; } return [ 'name' => 'Download', 'threshold' => $thresholdSettings->absolute_download.' Mbps', - 'value' => Number::toBitRate(bits: $this->result->download_bits, precision: 2), + 'value' => Number::toBitRate(bits: $event->result->download_bits, precision: 2), ]; } - protected function absoluteUploadThreshold(ThresholdSettings $thresholdSettings): bool|array + /** + * Build webhook notification if absolute upload threshold is breached. + */ + protected function absoluteUploadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array { - if (! absoluteUploadThresholdFailed($thresholdSettings->absolute_upload, $this->result->upload)) { - + if (! absoluteUploadThresholdFailed($thresholdSettings->absolute_upload, $event->result->upload)) { return false; } return [ 'name' => 'Upload', 'threshold' => $thresholdSettings->absolute_upload.' Mbps', - 'value' => Number::toBitRate(bits: $this->result->upload_bits, precision: 2), + 'value' => Number::toBitRate(bits: $event->result->upload_bits, precision: 2), ]; } - protected function absolutePingThreshold(ThresholdSettings $thresholdSettings): bool|array + /** + * Build webhook notification if absolute ping threshold is breached. + */ + protected function absolutePingThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array { - if (! absolutePingThresholdFailed($thresholdSettings->absolute_ping, $this->result->ping)) { - + if (! absolutePingThresholdFailed($thresholdSettings->absolute_ping, $event->result->ping)) { return false; } return [ 'name' => 'Ping', 'threshold' => $thresholdSettings->absolute_ping.' ms', - 'value' => round($this->result->ping, 2).' ms', + 'value' => round($event->result->ping, 2).' ms', ]; } } diff --git a/app/Mail/SpeedtestCompletedMail.php b/app/Mail/SpeedtestCompletedMail.php index 03b5644f0..6f7295771 100644 --- a/app/Mail/SpeedtestCompletedMail.php +++ b/app/Mail/SpeedtestCompletedMail.php @@ -2,14 +2,15 @@ namespace App\Mail; +use App\Helpers\Number; use App\Models\Result; -use App\Services\Notifications\SpeedtestNotificationData; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Str; class SpeedtestCompletedMail extends Mailable implements ShouldQueue { @@ -41,7 +42,19 @@ public function content(): Content { return new Content( markdown: 'emails.speedtest-completed', - with: SpeedtestNotificationData::make($this->result) + with: [ + 'id' => $this->result->id, + 'service' => Str::title($this->result->service->getLabel()), + 'serverName' => $this->result->server_name, + 'serverId' => $this->result->server_id, + 'isp' => $this->result->isp, + 'ping' => round($this->result->ping, 2).' ms', + 'download' => Number::toBitRate(bits: $this->result->download_bits, precision: 2), + 'upload' => Number::toBitRate(bits: $this->result->upload_bits, precision: 2), + 'packetLoss' => is_numeric($this->result->packet_loss) ? $this->result->packet_loss : 'n/a', + 'speedtest_url' => $this->result->result_url, + 'url' => url('/admin/results'), + ], ); } } diff --git a/app/Mail/SpeedtestThresholdMail.php b/app/Mail/SpeedtestThresholdMail.php index 82dbe9b4b..94ad14af9 100644 --- a/app/Mail/SpeedtestThresholdMail.php +++ b/app/Mail/SpeedtestThresholdMail.php @@ -3,13 +3,13 @@ namespace App\Mail; use App\Models\Result; -use App\Services\Notifications\SpeedtestNotificationData; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Str; class SpeedtestThresholdMail extends Mailable implements ShouldQueue { @@ -42,7 +42,16 @@ public function content(): Content { return new Content( markdown: 'emails.speedtest-threshold', - with: SpeedtestNotificationData::make($this->result) + with: [ + 'id' => $this->result->id, + 'service' => Str::title($this->result->service->getLabel()), + 'serverName' => $this->result->server_name, + 'serverId' => $this->result->server_id, + 'isp' => $this->result->isp, + 'speedtest_url' => $this->result->result_url, + 'url' => url('/admin/results'), + 'metrics' => $this->metrics, + ], ); } } From 06e8f4927aed73f633471ad33434f675ee22acc7 Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Wed, 28 May 2025 18:42:16 +0200 Subject: [PATCH 23/31] update SpeedtestEventSubscriber --- app/Listeners/SpeedtestEventSubscriber.php | 53 ---------------------- 1 file changed, 53 deletions(-) diff --git a/app/Listeners/SpeedtestEventSubscriber.php b/app/Listeners/SpeedtestEventSubscriber.php index 35fa9e3a0..e3f6bdacb 100644 --- a/app/Listeners/SpeedtestEventSubscriber.php +++ b/app/Listeners/SpeedtestEventSubscriber.php @@ -8,12 +8,6 @@ use App\Jobs\Influxdb\v2\WriteResult; use App\Jobs\Notifications\Apprise\SendSpeedtestCompletedNotification as AppriseCompleted; use App\Jobs\Notifications\Apprise\SendSpeedtestThresholdNotification as AppriseThresholds; -use App\Jobs\Notifications\Database\SendSpeedtestCompletedNotification as DatabaseCompleted; -use App\Jobs\Notifications\Database\SendSpeedtestThresholdNotification as DatabaseThresholds; -use App\Jobs\Notifications\Mail\SendSpeedtestCompletedNotification as MailCompleted; -use App\Jobs\Notifications\Mail\SendSpeedtestThresholdNotification as MailThresholds; -use App\Jobs\Notifications\Webhook\SendSpeedtestCompletedNotification as WebhookCompleted; -use App\Jobs\Notifications\Webhook\SendSpeedtestThresholdNotification as WebhookThresholds; use App\Settings\DataIntegrationSettings; use App\Settings\NotificationSettings; use Illuminate\Events\Dispatcher; @@ -52,27 +46,6 @@ public function handleSpeedtestCompleted(SpeedtestCompleted $event): void AppriseCompleted::dispatch($event->result); } } - - // Database notifications - if ($notificationSettings->database_enabled) { - if ($notificationSettings->database_on_speedtest_run) { - DatabaseCompleted::dispatch($event->result); - } - } - - // Webhook notifications - if ($notificationSettings->webhook_enabled) { - if ($notificationSettings->webhook_on_speedtest_run) { - WebhookCompleted::dispatch($event->result); - } - } - - // Mail notifications - if ($notificationSettings->mail_enabled) { - if ($notificationSettings->mail_on_speedtest_run) { - MailCompleted::dispatch($event->result); - } - } } public function handleSpeedtestBenchmarkFailed(SpeedtestBenchmarkFailed $event): void @@ -85,27 +58,6 @@ public function handleSpeedtestBenchmarkFailed(SpeedtestBenchmarkFailed $event): AppriseThresholds::dispatch($event->result); } } - - // Database notifications - if ($notificationSettings->database_enabled) { - if ($notificationSettings->database_on_threshold_failure) { - DatabaseThresholds::dispatch($event->result); - } - } - - // Webhook notifications - if ($notificationSettings->webhook_enabled) { - if ($notificationSettings->webhook_on_threshold_failure) { - WebhookThresholds::dispatch($event->result); - } - } - - // Mail notifications - if ($notificationSettings->mail_enabled) { - if ($notificationSettings->mail_on_threshold_failure) { - MailThresholds::dispatch($event->result); - } - } } /** @@ -122,10 +74,5 @@ public function subscribe(Dispatcher $events): void SpeedtestCompleted::class, [SpeedtestEventSubscriber::class, 'handleSpeedtestCompleted'] ); - - $events->listen( - SpeedtestBenchmarkFailed::class, - [SpeedtestEventSubscriber::class, 'handleSpeedtestBenchmarkFailed'] - ); } } From 8d64da7682a0c39b4cf68707e23e46e56f97cc4e Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Wed, 28 May 2025 19:15:00 +0200 Subject: [PATCH 24/31] Fix thresholds --- app/Console/Commands/TestNotification.php | 14 +------------- .../SendSpeedtestCompletedNotification.php | 2 +- .../SendSpeedtestThresholdNotification.php | 19 ++++++++++++------- .../apprise/speedtest-threshold.blade.php | 8 +++----- 4 files changed, 17 insertions(+), 26 deletions(-) diff --git a/app/Console/Commands/TestNotification.php b/app/Console/Commands/TestNotification.php index 2bf5d2026..007d89092 100644 --- a/app/Console/Commands/TestNotification.php +++ b/app/Console/Commands/TestNotification.php @@ -4,12 +4,6 @@ use App\Jobs\Notifications\Apprise\SendSpeedtestCompletedNotification as AppriseCompleted; use App\Jobs\Notifications\Apprise\SendSpeedtestThresholdNotification as AppriseThreshold; -use App\Jobs\Notifications\Database\SendSpeedtestCompletedNotification as DatabaseCompleted; -use App\Jobs\Notifications\Database\SendSpeedtestThresholdNotification as DatabaseThreshold; -use App\Jobs\Notifications\Mail\SendSpeedtestCompletedNotification as MailCompleted; -use App\Jobs\Notifications\Mail\SendSpeedtestThresholdNotification as MailThreshold; -use App\Jobs\Notifications\Webhook\SendSpeedtestCompletedNotification as WebhookCompleted; -use App\Jobs\Notifications\Webhook\SendSpeedtestThresholdNotification as WebhookThreshold; use App\Models\Result; use Illuminate\Console\Command; @@ -17,7 +11,7 @@ class TestNotification extends Command { protected $signature = 'notification:test {type=completed : Notification type (completed or threshold)} - {--channel=apprise : Notification channel (apprise, mail, database, webhook)}'; + {--channel=apprise : Notification channel (apprise)}'; protected $description = 'Send a test notification using a fake result'; @@ -37,12 +31,6 @@ public function handle(): int match ("{$channel}-{$type}") { 'apprise-completed' => AppriseCompleted::dispatch($result), 'apprise-threshold' => AppriseThreshold::dispatch($result), - 'mail-completed' => MailCompleted::dispatch($result), - 'mail-threshold' => MailThreshold::dispatch($result), - 'database-completed' => DatabaseCompleted::dispatch($result), - 'database-threshold' => DatabaseThreshold::dispatch($result), - 'webhook-completed' => WebhookCompleted::dispatch($result), - 'webhook-threshold' => WebhookThreshold::dispatch($result), }; $this->info('βœ… Notification dispatched!'); diff --git a/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php b/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php index caa4f3eb3..b89d667b2 100644 --- a/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php +++ b/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php @@ -52,7 +52,7 @@ public function handle(): void $webhookPayload = [ 'body' => $payload, - 'title' => 'Speedtest Completed - #{$this->result->id}', + 'title' => 'Speedtest Completed - #'.$this->result->id, 'type' => 'info', 'urls' => $webhook['service_url'], ]; diff --git a/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php b/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php index ff6487ff9..d4d2afc0f 100644 --- a/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php +++ b/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php @@ -4,7 +4,6 @@ use App\Helpers\Number; use App\Models\Result; -use App\Services\Notifications\SpeedtestNotificationData; use App\Settings\NotificationSettings; use App\Settings\ThresholdSettings; use GuzzleHttp\Client; @@ -13,6 +12,7 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Queue\Queueable; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Str; class SendSpeedtestThresholdNotification implements ShouldQueue { @@ -70,10 +70,6 @@ public function handle(): void return; } - $data = SpeedtestNotificationData::make($this->result); - - $payload = view('apprise.speedtest-threshold', $data)->render(); - $client = new Client; foreach ($notificationSettings->apprise_webhooks as $webhook) { @@ -84,8 +80,17 @@ public function handle(): void } $webhookPayload = [ - 'body' => $payload, - 'title' => 'Speedtest Threshold Breach', + 'body' => view('apprise.speedtest-threshold', [ + 'id' => $this->result->id, + 'service' => Str::title($this->result->service->getLabel()), + 'serverName' => $this->result->server_name, + 'serverId' => $this->result->server_id, + 'isp' => $this->result->isp, + 'metrics' => $failed, + 'speedtest_url' => $this->result->result_url, + 'url' => url('/admin/results'), + ])->render(), + 'title' => 'Speedtest Threshold Breach - #'.$this->result->id, 'type' => 'info', 'urls' => $webhook['service_url'], ]; diff --git a/resources/views/apprise/speedtest-threshold.blade.php b/resources/views/apprise/speedtest-threshold.blade.php index 8ad956c03..6d0bb4926 100644 --- a/resources/views/apprise/speedtest-threshold.blade.php +++ b/resources/views/apprise/speedtest-threshold.blade.php @@ -1,9 +1,7 @@ -Speedtest Threshold Breached - #{{ $id }} - A new speedtest on **{{ config('app.name') }}** was completed using **{{ $service }}** on **{{ $isp }}** but a threshold was breached. @foreach ($metrics as $item) -- {{ $item['name'] }} {{ $item['threshold'] }}: {{ $item['value'] }} +- **{{ $item['name'] }}** {{ $item['threshold'] }}: {{ $item['value'] }} @endforeach -- Ookla Speedtest: {{ $speedtest_url }} -- URL: {{ $url }} +- **Ookla Speedtest:** {{ $speedtest_url }} +- **URL:** {{ $url }} From 5a35129eed31610cf611371229903e7982152332 Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Wed, 4 Jun 2025 18:52:58 +0200 Subject: [PATCH 25/31] improve error handle --- .../SendAppriseTestNotification.php | 49 +++++++++---------- .../SendSpeedtestCompletedNotification.php | 32 +++++++----- .../SendSpeedtestThresholdNotification.php | 35 +++++++------ 3 files changed, 64 insertions(+), 52 deletions(-) diff --git a/app/Actions/Notifications/SendAppriseTestNotification.php b/app/Actions/Notifications/SendAppriseTestNotification.php index 15d4ac062..76c05902e 100644 --- a/app/Actions/Notifications/SendAppriseTestNotification.php +++ b/app/Actions/Notifications/SendAppriseTestNotification.php @@ -3,8 +3,8 @@ namespace App\Actions\Notifications; use Filament\Notifications\Notification; -use GuzzleHttp\Client; -use GuzzleHttp\Exception\RequestException; +use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\Log; use Lorisleiva\Actions\Concerns\AsAction; class SendAppriseTestNotification @@ -14,16 +14,20 @@ class SendAppriseTestNotification public function handle(array $webhooks) { if (! count($webhooks)) { - Notification::make()->title('You need to add Apprise webhooks!')->warning()->send(); + Notification::make() + ->title('You need to add Apprise webhooks!') + ->warning() + ->send(); return; } - $client = new Client; - foreach ($webhooks as $webhook) { - if (empty($webhook['service_url'])) { - Notification::make()->title('Webhook is missing service URL!')->warning()->send(); + if (empty($webhook['url'])) { + Notification::make() + ->title('Webhook is missing service URL!') + ->warning() + ->send(); continue; } @@ -34,27 +38,22 @@ public function handle(array $webhooks) ]; try { - $response = $client->post(rtrim($webhook['url'], '/'), [ - 'json' => $payload, - 'headers' => [ - 'Content-Type' => 'application/json', - ], - ]); - - if ($response->getStatusCode() === 200) { - Notification::make()->title('Apprise notification sent successfully.')->success()->send(); - } else { - Notification::make() - ->title('Failed to send Apprise notification.') - ->warning() - ->body('HTTP Status: '.$response->getStatusCode()) - ->send(); - } - } catch (RequestException $e) { + Http::withHeaders([ + 'Content-Type' => 'application/json', + ])->post(rtrim($webhook['url'], '/'), $payload) + ->throw(); + + Notification::make() + ->title('Apprise notification sent successfully.') + ->success() + ->send(); + } catch (\Throwable $e) { + Log::error('Apprise notification failed for URL '.$webhook['url'].': '.$e->getMessage()); + Notification::make() ->title('Failed to send Apprise notification.') ->warning() - ->body($e->getMessage()) + ->body('An error occurred. Please check the logs for details.') ->send(); } } diff --git a/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php b/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php index b89d667b2..c6b326423 100644 --- a/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php +++ b/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php @@ -2,14 +2,16 @@ namespace App\Jobs\Notifications\Apprise; +use App\Enums\UserRole; use App\Models\Result; +use App\Models\User; use App\Services\Notifications\SpeedtestNotificationData; use App\Settings\NotificationSettings; -use GuzzleHttp\Client; -use GuzzleHttp\Exception\RequestException; +use Filament\Notifications\Notification; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Queue\Queueable; +use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; class SendSpeedtestCompletedNotification implements ShouldQueue @@ -58,17 +60,21 @@ public function handle(): void ]; try { - $client = new Client; - $response = $client->post($webhook['url'], [ - 'json' => $webhookPayload, - 'headers' => [ - 'Content-Type' => 'application/json', - ], - ]); - - Log::info('Apprise notification sent successfully to '.$webhook['url']); - } catch (RequestException $e) { - Log::error('Apprise notification failed: '.$e->getMessage()); + Http::withHeaders([ + 'Content-Type' => 'application/json', + ])->post($webhook['url'], $webhookPayload)->throw(); + + Log::info('Apprise notification sent successfully to instance '.$webhook['url'].' and service url '.$webhook['service_url']); + } catch (\Throwable $e) { + Log::error('Apprise notification failed for instance '.$webhook['url'].' and service URL '.$webhook['service_url'].': '.$e->getMessage()); + + // Notify admins if notifications fail. + $admins = User::where('role', UserRole::Admin)->get(); + Notification::make() + ->title('Apprise Notification Failure') + ->danger() + ->body('Failed to send notification. Please check the logs.') + ->sendToDatabase($admins); } } } diff --git a/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php b/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php index d4d2afc0f..418c7e9eb 100644 --- a/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php +++ b/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php @@ -2,15 +2,17 @@ namespace App\Jobs\Notifications\Apprise; +use App\Enums\UserRole; use App\Helpers\Number; use App\Models\Result; +use App\Models\User; use App\Settings\NotificationSettings; use App\Settings\ThresholdSettings; -use GuzzleHttp\Client; -use GuzzleHttp\Exception\RequestException; +use Filament\Notifications\Notification; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Queue\Queueable; +use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; use Illuminate\Support\Str; @@ -70,8 +72,6 @@ public function handle(): void return; } - $client = new Client; - foreach ($notificationSettings->apprise_webhooks as $webhook) { if (empty($webhook['service_url']) || empty($webhook['url'])) { Log::warning('Webhook is missing service URL or URL, skipping.'); @@ -96,16 +96,23 @@ public function handle(): void ]; try { - $response = $client->post($webhook['url'], [ - 'json' => $webhookPayload, - 'headers' => [ - 'Content-Type' => 'application/json', - ], - ]); - - Log::info('Apprise notification sent successfully to '.$webhook['url']); - } catch (RequestException $e) { - Log::error('Apprise notification failed: '.$e->getMessage()); + Http::withHeaders([ + 'Content-Type' => 'application/json', + ]) + ->post($webhook['url'], $webhookPayload) + ->throw(); + + Log::info('Apprise notification sent successfully to instance '.$webhook['url'].' and service url '.$webhook['service_url']); + } catch (\Throwable $e) { + Log::error('Apprise notification failed for instance '.$webhook['url'].' and service URL '.$webhook['service_url'].': '.$e->getMessage()); + + // Notify admins if notifications fail. + $admins = User::where('role', UserRole::Admin)->get(); + Notification::make() + ->title('Apprise Notification Failure') + ->danger() + ->body('Failed to send notification. Please check the logs.') + ->sendToDatabase($admins); } } } From 893ed40bd84cc6aa630dab18fe7908324c024d5f Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Wed, 4 Jun 2025 21:02:54 +0200 Subject: [PATCH 26/31] Add ssl check for self signed certs --- .../SendAppriseTestNotification.php | 8 ++++-- .../Pages/Settings/NotificationPage.php | 25 +++++++++++++------ .../SendSpeedtestCompletedNotification.php | 9 +++++-- .../SendSpeedtestThresholdNotification.php | 9 ++++--- 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/app/Actions/Notifications/SendAppriseTestNotification.php b/app/Actions/Notifications/SendAppriseTestNotification.php index 76c05902e..190106194 100644 --- a/app/Actions/Notifications/SendAppriseTestNotification.php +++ b/app/Actions/Notifications/SendAppriseTestNotification.php @@ -38,9 +38,13 @@ public function handle(array $webhooks) ]; try { - Http::withHeaders([ + $request = Http::withHeaders([ 'Content-Type' => 'application/json', - ])->post(rtrim($webhook['url'], '/'), $payload) + ]); + if (empty($webhook['ssl_verify'])) { + $request = $request->withoutVerifying(); + } + $request->post(rtrim($webhook['url'], '/'), $payload) ->throw(); Notification::make() diff --git a/app/Filament/Pages/Settings/NotificationPage.php b/app/Filament/Pages/Settings/NotificationPage.php index cf5c07b32..be1be6656 100755 --- a/app/Filament/Pages/Settings/NotificationPage.php +++ b/app/Filament/Pages/Settings/NotificationPage.php @@ -17,6 +17,7 @@ use Filament\Forms; use Filament\Forms\Components\Actions; use Filament\Forms\Components\Actions\Action; +use Filament\Forms\Components\Checkbox; use Filament\Forms\Components\Fieldset; use Filament\Forms\Components\Grid; use Filament\Forms\Components\Repeater; @@ -125,18 +126,26 @@ public function form(Form $form): Form ->label('Apprise Webhooks') ->hint(new HtmlString('Apprise Documentation')) ->schema([ - TextInput::make('url') - ->label('URL') - ->placeholder('http://apprise:8000/notify') - ->helperText('Specify the URL of your Apprise instance β€” it must end with /notify.') - ->maxLength(2000) - ->required() - ->url(), + Fieldset::make('Apprise Sidecar') + ->schema([ + TextInput::make('url') + ->label('URL') + ->placeholder('http://apprise:8000/notify') + ->helperText('Specify the URL of your Apprise instance β€” it must end with /notify.') + ->maxLength(2000) + ->required() + ->url() + ->columnSpanFull(), + Checkbox::make('ssl_verify') + ->label('Verify SSL') + ->default(true) + ->columnSpanFull(), + ]), TextInput::make('service_url') ->label('Service URL') ->placeholder('discord://WebhookID/WebhookToken') ->helperText('Provide the service endpoint URL for notifications β€” this URL must already be defined in your Apprise configuration.') - ->maxLength(200) + ->maxLength(2000) ->required(), ]) ->columnSpanFull(), diff --git a/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php b/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php index c6b326423..3ee8e18f2 100644 --- a/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php +++ b/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php @@ -60,9 +60,14 @@ public function handle(): void ]; try { - Http::withHeaders([ + $request = Http::withHeaders([ 'Content-Type' => 'application/json', - ])->post($webhook['url'], $webhookPayload)->throw(); + ]); + if (empty($webhook['ssl_verify'])) { + $request = $request->withoutVerifying(); + } + $request->post(rtrim($webhook['url'], '/'), $webhookPayload) + ->throw(); Log::info('Apprise notification sent successfully to instance '.$webhook['url'].' and service url '.$webhook['service_url']); } catch (\Throwable $e) { diff --git a/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php b/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php index 418c7e9eb..6f280aaee 100644 --- a/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php +++ b/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php @@ -96,10 +96,13 @@ public function handle(): void ]; try { - Http::withHeaders([ + $request = Http::withHeaders([ 'Content-Type' => 'application/json', - ]) - ->post($webhook['url'], $webhookPayload) + ]); + if (empty($webhook['ssl_verify'])) { + $request = $request->withoutVerifying(); + } + $request->post(rtrim($webhook['url'], '/'), $webhookPayload) ->throw(); Log::info('Apprise notification sent successfully to instance '.$webhook['url'].' and service url '.$webhook['service_url']); From 253354ebb716abd2dd77b734ae37fa67e0a2045c Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Fri, 6 Jun 2025 09:03:33 +0200 Subject: [PATCH 27/31] refactor sending method --- .../SendAppriseTestNotification.php | 26 ++++--- .../Pages/Settings/NotificationPage.php | 47 +++++------ .../SendSpeedtestCompletedNotification.php | 64 ++++----------- .../SendSpeedtestThresholdNotification.php | 77 ++++++------------- app/Services/Notifications/AppriseService.php | 71 +++++++++++++++++ app/Settings/NotificationSettings.php | 6 +- ..._31_164343_create_apprise_notification.php | 4 +- 7 files changed, 157 insertions(+), 138 deletions(-) create mode 100644 app/Services/Notifications/AppriseService.php diff --git a/app/Actions/Notifications/SendAppriseTestNotification.php b/app/Actions/Notifications/SendAppriseTestNotification.php index 190106194..f4c6b6a92 100644 --- a/app/Actions/Notifications/SendAppriseTestNotification.php +++ b/app/Actions/Notifications/SendAppriseTestNotification.php @@ -11,21 +11,22 @@ class SendAppriseTestNotification { use AsAction; - public function handle(array $webhooks) + public function handle(string $apprise_url, bool $apprise_verify_ssl, array $channel_urls) { - if (! count($webhooks)) { + if (! $apprise_url) { Notification::make() - ->title('You need to add Apprise webhooks!') + ->title('You need to configure an Apprise URL!') ->warning() ->send(); return; } - foreach ($webhooks as $webhook) { - if (empty($webhook['url'])) { + foreach ($channel_urls as $row) { + $serviceUrl = $row['channel_url'] ?? null; + if (! $serviceUrl) { Notification::make() - ->title('Webhook is missing service URL!') + ->title('Skipping missing Service URL!') ->warning() ->send(); @@ -33,18 +34,21 @@ public function handle(array $webhooks) } $payload = [ - 'body' => 'πŸ‘‹ Testing the Apprise notification channel.', - 'urls' => $webhook['service_url'], + 'body' => 'πŸ‘‹ Testing Apprise channel.', + 'urls' => $serviceUrl, ]; try { $request = Http::withHeaders([ 'Content-Type' => 'application/json', ]); - if (empty($webhook['ssl_verify'])) { + + if (! $apprise_verify_ssl) { $request = $request->withoutVerifying(); } - $request->post(rtrim($webhook['url'], '/'), $payload) + + $request + ->post(rtrim($apprise_url, '/'), $payload) ->throw(); Notification::make() @@ -52,7 +56,7 @@ public function handle(array $webhooks) ->success() ->send(); } catch (\Throwable $e) { - Log::error('Apprise notification failed for URL '.$webhook['url'].': '.$e->getMessage()); + Log::error('Apprise notification failed for service URL '.$serviceUrl.': '.$e->getMessage()); Notification::make() ->title('Failed to send Apprise notification.') diff --git a/app/Filament/Pages/Settings/NotificationPage.php b/app/Filament/Pages/Settings/NotificationPage.php index be1be6656..52727f2bc 100755 --- a/app/Filament/Pages/Settings/NotificationPage.php +++ b/app/Filament/Pages/Settings/NotificationPage.php @@ -122,30 +122,31 @@ public function form(Form $form): Form ->label('Notify on threshold failures') ->columnSpanFull(), ]), - Repeater::make('apprise_webhooks') - ->label('Apprise Webhooks') + Fieldset::make('Apprise Sidecar') + ->schema([ + TextInput::make('apprise_url') + ->label('URL') + ->placeholder('http://apprise:8000/notify') + ->helperText('Specify the URL of your Apprise instance.') + ->maxLength(2000) + ->required() + ->url() + ->columnSpanFull(), + Checkbox::make('apprise_verify_ssl') + ->label('Verify SSL') + ->default(true) + ->columnSpanFull(), + ]), + Repeater::make('apprise_channel_urls') + ->label('Apprise Channels') ->hint(new HtmlString('Apprise Documentation')) ->schema([ - Fieldset::make('Apprise Sidecar') - ->schema([ - TextInput::make('url') - ->label('URL') - ->placeholder('http://apprise:8000/notify') - ->helperText('Specify the URL of your Apprise instance β€” it must end with /notify.') - ->maxLength(2000) - ->required() - ->url() - ->columnSpanFull(), - Checkbox::make('ssl_verify') - ->label('Verify SSL') - ->default(true) - ->columnSpanFull(), - ]), - TextInput::make('service_url') - ->label('Service URL') + TextInput::make('channel_url') + ->label('Channel URL') ->placeholder('discord://WebhookID/WebhookToken') - ->helperText('Provide the service endpoint URL for notifications β€” this URL must already be defined in your Apprise configuration.') + ->helperText('Provide the service endpoint URL for notifications.') ->maxLength(2000) + ->distinct() ->required(), ]) ->columnSpanFull(), @@ -153,9 +154,11 @@ public function form(Form $form): Form Action::make('test apprise') ->label('Test Apprise') ->action(fn (Forms\Get $get) => SendAppriseTestNotification::run( - webhooks: $get('apprise_webhooks') + apprise_url: $get('apprise_url'), + apprise_verify_ssl: $get('apprise_verify_ssl'), + channel_urls: $get('apprise_channel_urls'), )) - ->hidden(fn (Forms\Get $get) => ! count($get('apprise_webhooks'))), + ->hidden(fn (Forms\Get $get) => ! count($get('apprise_channel_urls'))), ]), ]), ]) diff --git a/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php b/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php index 3ee8e18f2..4e447a4de 100644 --- a/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php +++ b/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php @@ -2,16 +2,13 @@ namespace App\Jobs\Notifications\Apprise; -use App\Enums\UserRole; use App\Models\Result; -use App\Models\User; +use App\Services\Notifications\AppriseService; use App\Services\Notifications\SpeedtestNotificationData; use App\Settings\NotificationSettings; -use Filament\Notifications\Notification; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Queue\Queueable; -use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; class SendSpeedtestCompletedNotification implements ShouldQueue @@ -20,67 +17,34 @@ class SendSpeedtestCompletedNotification implements ShouldQueue public Result $result; - /** - * Create a new job instance. - */ public function __construct(Result $result) { $this->result = $result; } - /** - * Handle the event. - */ public function handle(): void { - $notificationSettings = app(NotificationSettings::class); + $settings = app(NotificationSettings::class); - if (! count($notificationSettings->apprise_webhooks)) { - Log::warning('Apprise URLs not found, check Apprise notification channel settings.'); + // If apprise_channel_urls is empty or not an array, skip + if (empty($settings->apprise_channel_urls) || ! is_array($settings->apprise_channel_urls)) { + Log::warning('Apprise service URLs not found; check Apprise notification settings.'); return; } + // Build the completed‐speedtest payload $data = SpeedtestNotificationData::make($this->result); - $payload = view('apprise.speedtest-completed', $data)->render(); + $payloadBody = view('apprise.speedtest-completed', $data)->render(); - foreach ($notificationSettings->apprise_webhooks as $webhook) { - if (empty($webhook['service_url']) || empty($webhook['url'])) { - Log::warning('Webhook is missing service URL or URL, skipping.'); + $payload = [ + 'body' => $payloadBody, + 'title' => 'Speedtest Completed – #'.$this->result->id, + 'type' => 'info', + ]; - continue; - } - - $webhookPayload = [ - 'body' => $payload, - 'title' => 'Speedtest Completed - #'.$this->result->id, - 'type' => 'info', - 'urls' => $webhook['service_url'], - ]; - - try { - $request = Http::withHeaders([ - 'Content-Type' => 'application/json', - ]); - if (empty($webhook['ssl_verify'])) { - $request = $request->withoutVerifying(); - } - $request->post(rtrim($webhook['url'], '/'), $webhookPayload) - ->throw(); - - Log::info('Apprise notification sent successfully to instance '.$webhook['url'].' and service url '.$webhook['service_url']); - } catch (\Throwable $e) { - Log::error('Apprise notification failed for instance '.$webhook['url'].' and service URL '.$webhook['service_url'].': '.$e->getMessage()); - - // Notify admins if notifications fail. - $admins = User::where('role', UserRole::Admin)->get(); - Notification::make() - ->title('Apprise Notification Failure') - ->danger() - ->body('Failed to send notification. Please check the logs.') - ->sendToDatabase($admins); - } - } + // Send it! + AppriseService::send($payload); } } diff --git a/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php b/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php index 6f280aaee..351f4074c 100644 --- a/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php +++ b/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php @@ -2,17 +2,14 @@ namespace App\Jobs\Notifications\Apprise; -use App\Enums\UserRole; use App\Helpers\Number; use App\Models\Result; -use App\Models\User; +use App\Services\Notifications\AppriseService; use App\Settings\NotificationSettings; use App\Settings\ThresholdSettings; -use Filament\Notifications\Notification; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Queue\Queueable; -use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; use Illuminate\Support\Str; @@ -35,10 +32,11 @@ public function __construct(Result $result) */ public function handle(): void { - $notificationSettings = app(NotificationSettings::class); + $settings = app(NotificationSettings::class); - if (! count($notificationSettings->apprise_webhooks)) { - Log::warning('Apprise URLs not found, check Apprise notification channel settings.'); + // If apprise_channel_urls is empty or not an array, skip + if (empty($settings->apprise_channel_urls) || ! is_array($settings->apprise_channel_urls)) { + Log::warning('Apprise service URLs not found; check Apprise notification settings.'); return; } @@ -72,52 +70,25 @@ public function handle(): void return; } - foreach ($notificationSettings->apprise_webhooks as $webhook) { - if (empty($webhook['service_url']) || empty($webhook['url'])) { - Log::warning('Webhook is missing service URL or URL, skipping.'); - - continue; - } - - $webhookPayload = [ - 'body' => view('apprise.speedtest-threshold', [ - 'id' => $this->result->id, - 'service' => Str::title($this->result->service->getLabel()), - 'serverName' => $this->result->server_name, - 'serverId' => $this->result->server_id, - 'isp' => $this->result->isp, - 'metrics' => $failed, - 'speedtest_url' => $this->result->result_url, - 'url' => url('/admin/results'), - ])->render(), - 'title' => 'Speedtest Threshold Breach - #'.$this->result->id, - 'type' => 'info', - 'urls' => $webhook['service_url'], - ]; - - try { - $request = Http::withHeaders([ - 'Content-Type' => 'application/json', - ]); - if (empty($webhook['ssl_verify'])) { - $request = $request->withoutVerifying(); - } - $request->post(rtrim($webhook['url'], '/'), $webhookPayload) - ->throw(); - - Log::info('Apprise notification sent successfully to instance '.$webhook['url'].' and service url '.$webhook['service_url']); - } catch (\Throwable $e) { - Log::error('Apprise notification failed for instance '.$webhook['url'].' and service URL '.$webhook['service_url'].': '.$e->getMessage()); - - // Notify admins if notifications fail. - $admins = User::where('role', UserRole::Admin)->get(); - Notification::make() - ->title('Apprise Notification Failure') - ->danger() - ->body('Failed to send notification. Please check the logs.') - ->sendToDatabase($admins); - } - } + $payloadBody = view('apprise.speedtest-threshold', [ + 'id' => $this->result->id, + 'service' => Str::title($this->result->service->getLabel()), + 'serverName' => $this->result->server_name, + 'serverId' => $this->result->server_id, + 'isp' => $this->result->isp, + 'metrics' => $failed, + 'speedtest_url' => $this->result->result_url, + 'url' => url('/admin/results'), + ])->render(); + + $payload = [ + 'body' => $payloadBody, + 'title' => 'Speedtest Threshold Breach – #'.$this->result->id, + 'type' => 'info', + ]; + + // Send it! + AppriseService::send($payload); } protected function absoluteDownloadThreshold(ThresholdSettings $thresholdSettings): bool|array diff --git a/app/Services/Notifications/AppriseService.php b/app/Services/Notifications/AppriseService.php new file mode 100644 index 000000000..c57457b85 --- /dev/null +++ b/app/Services/Notifications/AppriseService.php @@ -0,0 +1,71 @@ +apprise_channel_urls) || + ! is_array($settings->apprise_channel_urls) + ) { + Log::warning('Apprise service URLs not found; check Apprise settings.'); + + return; + } + + $instance = rtrim($settings->apprise_url, '/'); + + foreach ($settings->apprise_channel_urls as $row) { + $channelUrl = $row['channel_url'] ?? null; + if (! $channelUrl) { + Log::warning('Skipping entry with missing channel_url.'); + + continue; + } + + // Merge the channel into the payload + $payload['urls'] = $channelUrl; + + try { + $request = Http::withHeaders([ + 'Content-Type' => 'application/json', + ]); + + // If SSL verification is disabled in settings, skip it + if (! $settings->apprise_verify_ssl) { + $request = $request->withoutVerifying(); + } + + $request->post($instance, $payload)->throw(); + + Log::info("Apprise notification sent β†’ instance: {$instance} service: {$channelUrl}"); + } catch (\Throwable $e) { + Log::error("Apprise notification failed for channel {$channelUrl} via {$instance}: ".$e->getMessage()); + + $admins = User::where('role', UserRole::Admin)->get(); + Notification::make() + ->title('Apprise Notification Failure') + ->danger() + ->body("Failed to send notification to {$channelUrl}. Check logs for details.") + ->sendToDatabase($admins); + } + } + } +} diff --git a/app/Settings/NotificationSettings.php b/app/Settings/NotificationSettings.php index c889aceeb..e26e56f44 100644 --- a/app/Settings/NotificationSettings.php +++ b/app/Settings/NotificationSettings.php @@ -92,7 +92,11 @@ class NotificationSettings extends Settings public bool $apprise_on_threshold_failure; - public ?array $apprise_webhooks; + public ?string $apprise_url; + + public bool $apprise_verify_ssl; + + public ?array $apprise_channel_urls; public static function group(): string { diff --git a/database/settings/2024_12_31_164343_create_apprise_notification.php b/database/settings/2024_12_31_164343_create_apprise_notification.php index e4b2ccdc1..bfad7131e 100644 --- a/database/settings/2024_12_31_164343_create_apprise_notification.php +++ b/database/settings/2024_12_31_164343_create_apprise_notification.php @@ -9,6 +9,8 @@ public function up(): void $this->migrator->add('notification.apprise_enabled', false); $this->migrator->add('notification.apprise_on_speedtest_run', false); $this->migrator->add('notification.apprise_on_threshold_failure', false); - $this->migrator->add('notification.apprise_webhooks', null); + $this->migrator->add('notification.apprise_url', null); + $this->migrator->add('notification.apprise_verify_ssl', true); + $this->migrator->add('notification.apprise_channel_urls', null); } }; From f7046b87edfb84ffc9886437b4774d6c5234c1f3 Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Wed, 25 Jun 2025 23:54:02 +0200 Subject: [PATCH 28/31] add tests --- .github/workflows/ci.yml | 29 +++ .../SendSpeedtestCompletedNotification.php | 2 + phpunit.xml | 8 + tests/Feature/NotificationActionsTest.php | 93 +++++++++ tests/Unit/AppriseNotificationJobsTest.php | 156 ++++++++++++++ tests/Unit/MailNotificationListenersTest.php | 185 +++++++++++++++++ tests/Unit/NotificationListenersTest.php | 149 +++++++++++++ tests/Unit/NotificationTest.php | 67 ++++++ .../Unit/WebhookNotificationListenersTest.php | 196 ++++++++++++++++++ 9 files changed, 885 insertions(+) create mode 100644 tests/Feature/NotificationActionsTest.php create mode 100644 tests/Unit/AppriseNotificationJobsTest.php create mode 100644 tests/Unit/MailNotificationListenersTest.php create mode 100644 tests/Unit/NotificationListenersTest.php create mode 100644 tests/Unit/NotificationTest.php create mode 100644 tests/Unit/WebhookNotificationListenersTest.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42cccdca2..abb14acf8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,3 +50,32 @@ jobs: - name: Run Tests run: php artisan test --parallel + + test-notifications: + needs: lint-app + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + + - name: Create SQLite Database + run: | + touch database/testing.sqlite + + - name: Install Dependencies + run: | + composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist + + - name: Copy Environment File + run: cp .env.example .env + + - name: Generate App Key + run: php artisan key:generate --quiet + + - name: Run Notification Tests + run: php artisan test --testsuite=Notification diff --git a/app/Listeners/Database/SendSpeedtestCompletedNotification.php b/app/Listeners/Database/SendSpeedtestCompletedNotification.php index 14ea66605..7908d3db7 100644 --- a/app/Listeners/Database/SendSpeedtestCompletedNotification.php +++ b/app/Listeners/Database/SendSpeedtestCompletedNotification.php @@ -6,6 +6,7 @@ use App\Models\User; use App\Settings\NotificationSettings; use Filament\Notifications\Notification; +use Illuminate\Support\Facades\Log; class SendSpeedtestCompletedNotification { @@ -25,6 +26,7 @@ public function handle(SpeedtestCompleted $event): void } foreach (User::all() as $user) { + Log::info('Notifying user', ['id' => $user->id, 'email' => $user->email]); Notification::make() ->title('Speedtest completed') ->success() diff --git a/phpunit.xml b/phpunit.xml index 1f0019785..8bbc6de53 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -11,6 +11,14 @@ tests/Feature + + tests/Unit/NotificationListenersTest.php + tests/Unit/MailNotificationListenersTest.php + tests/Unit/WebhookNotificationListenersTest.php + tests/Unit/AppriseNotificationJobsTest.php + tests/Unit/NotificationTest.php + tests/Feature/NotificationActionsTest.php + diff --git a/tests/Feature/NotificationActionsTest.php b/tests/Feature/NotificationActionsTest.php new file mode 100644 index 000000000..aa7cd99e0 --- /dev/null +++ b/tests/Feature/NotificationActionsTest.php @@ -0,0 +1,93 @@ +handle($recipients); + + // If mail is queued, use assertQueued. If not, use assertSent. + Mail::assertQueued(\App\Mail\Test::class, function ($mail) use ($recipients) { + return in_array($mail->to[0]['address'], $recipients); + }); + Mail::assertQueuedCount(count($recipients)); + }); + + test('SendMailTestNotification handles empty recipients', function () { + Mail::fake(); + + $action = new SendMailTestNotification; + $action->handle([]); + + Mail::assertNotSent(\App\Mail\Test::class); + }); + + test('SendWebhookTestNotification sends webhook calls', function () { + $urls = [ + ['url' => 'https://webhook.example.com'], + ['url' => 'https://another-webhook.example.com'], + ]; + + $action = new SendWebhookTestNotification; + + // Mock the WebhookCall to avoid actual HTTP requests + $this->mock(WebhookCall::class, function ($mock) { + $mock->shouldReceive('create')->andReturnSelf(); + $mock->shouldReceive('url')->andReturnSelf(); + $mock->shouldReceive('payload')->andReturnSelf(); + $mock->shouldReceive('doNotSign')->andReturnSelf(); + $mock->shouldReceive('dispatch')->andReturnSelf(); + }); + + $action->handle($urls); + + // The action should complete without throwing exceptions + expect(true)->toBeTrue(); + }); + + test('SendWebhookTestNotification handles empty urls', function () { + $action = new SendWebhookTestNotification; + + // Mock the WebhookCall to avoid actual HTTP requests + $this->mock(WebhookCall::class, function ($mock) { + $mock->shouldReceive('create')->andReturnSelf(); + $mock->shouldReceive('url')->andReturnSelf(); + $mock->shouldReceive('payload')->andReturnSelf(); + $mock->shouldReceive('doNotSign')->andReturnSelf(); + $mock->shouldReceive('dispatch')->andReturnSelf(); + }); + + $action->handle([]); + + // The action should complete without throwing exceptions + expect(true)->toBeTrue(); + }); + + test('SendAppriseTestNotification sends apprise notifications', function () { + $apprise_url = 'https://apprise.example.com'; + $apprise_verify_ssl = true; + $channel_urls = [ + ['channel_url' => 'https://service1.example.com'], + ['channel_url' => 'https://service2.example.com'], + ]; + + $action = new SendAppriseTestNotification; + + // Use Laravel's Http::fake() to avoid actual HTTP requests + \Illuminate\Support\Facades\Http::fake(); + + $action->handle($apprise_url, $apprise_verify_ssl, $channel_urls); + + // The action should complete without throwing exceptions + expect(true)->toBeTrue(); + }); +}); diff --git a/tests/Unit/AppriseNotificationJobsTest.php b/tests/Unit/AppriseNotificationJobsTest.php new file mode 100644 index 000000000..e618a816d --- /dev/null +++ b/tests/Unit/AppriseNotificationJobsTest.php @@ -0,0 +1,156 @@ +delete(); + + $user = User::factory()->create(); + $result = Result::factory()->create(); + + // Set apprise channel urls and URL (the job only checks channel_urls, but service needs apprise_url) + $settings = new NotificationSettings; + $settings->apprise_url = 'https://apprise.example.com'; + $settings->apprise_channel_urls = [ + ['channel_url' => 'https://service1.example.com'], + ['channel_url' => 'https://service2.example.com'], + ]; + $settings->save(); + + $job = new SendSpeedtestCompletedNotification($result); + + // Do not assert the static call, just run the job + $job->handle(); + + // Verify that the settings are correctly configured + $savedSettings = new NotificationSettings; + expect($savedSettings->apprise_url)->toBe('https://apprise.example.com'); + expect($savedSettings->apprise_channel_urls)->toHaveCount(2); + expect($savedSettings->apprise_channel_urls[0]['channel_url'])->toBe('https://service1.example.com'); + expect($savedSettings->apprise_channel_urls[1]['channel_url'])->toBe('https://service2.example.com'); + }); + + test('SendSpeedtestCompletedNotification logic when no channel urls', function () { + // Clean users table to ensure only one user exists + \App\Models\User::query()->delete(); + + $user = User::factory()->create(); + $result = Result::factory()->create(); + + // Set empty apprise channel urls + $settings = new NotificationSettings; + $settings->apprise_url = 'https://apprise.example.com'; + $settings->apprise_channel_urls = []; + $settings->save(); + + $job = new SendSpeedtestCompletedNotification($result); + + // Mock Log facade to capture the warning + Log::shouldReceive('warning')->with('Apprise service URLs not found; check Apprise notification settings.')->once(); + + $job->handle(); + + // Verify that the settings are correctly configured + $savedSettings = new NotificationSettings; + expect($savedSettings->apprise_channel_urls)->toHaveCount(0); + }); + + test('SendSpeedtestThresholdNotification logic when threshold is breached', function () { + // Clean users table to ensure only one user exists + \App\Models\User::query()->delete(); + + $user = User::factory()->create(); + $result = Result::factory()->create([ + 'ping' => 100, // High ping that will breach threshold + 'download' => 104857.6, // 0.1 MB in bytes + 'upload' => 52428.8, // 0.05 MB in bytes + ]); + + // Set apprise channel urls and URL + $settings = new NotificationSettings; + $settings->apprise_url = 'https://apprise.example.com'; + $settings->apprise_channel_urls = [['channel_url' => 'https://service.example.com']]; + $settings->save(); + + // Set threshold settings + $thresholdSettings = new ThresholdSettings; + $thresholdSettings->absolute_enabled = true; + $thresholdSettings->absolute_ping = 50; // Threshold lower than result (100 > 50) + $thresholdSettings->absolute_download = 1; // Threshold higher than result (0.8 < 1) + $thresholdSettings->absolute_upload = 1; // Threshold higher than result (0.4 < 1) + $thresholdSettings->save(); + + $job = new SendSpeedtestThresholdNotification($result); + + // Test that the threshold functions work correctly + expect(absolutePingThresholdFailed(50, 100))->toBeTrue(); // 100 > 50 + expect(absoluteDownloadThresholdFailed(1, 104857.6))->toBeTrue(); // 0.8 < 1 + expect(absoluteUploadThresholdFailed(1, 52428.8))->toBeTrue(); // 0.4 < 1 + + // Do not assert the static call, just run the job + $job->handle(); + + // Verify that the settings are correctly configured + $savedSettings = new NotificationSettings; + $savedThresholdSettings = new ThresholdSettings; + expect($savedSettings->apprise_url)->toBe('https://apprise.example.com'); + expect($savedSettings->apprise_channel_urls)->toHaveCount(1); + expect($savedThresholdSettings->absolute_enabled)->toBeTrue(); + expect($savedThresholdSettings->absolute_ping)->toBe(50.0); + expect($savedThresholdSettings->absolute_download)->toBe(1.0); + expect($savedThresholdSettings->absolute_upload)->toBe(1.0); + }); + + test('SendSpeedtestThresholdNotification logic when thresholds not breached', function () { + // Clean users table to ensure only one user exists + \App\Models\User::query()->delete(); + + $user = User::factory()->create(); + $result = Result::factory()->create([ + 'ping' => 20, // Low ping, should not breach threshold + 'download' => 104857600, // 100 MB in bytes + 'upload' => 52428800, // 50 MB in bytes + ]); + + // Set apprise channel urls and URL + $settings = new NotificationSettings; + $settings->apprise_url = 'https://apprise.example.com'; + $settings->apprise_channel_urls = [['channel_url' => 'https://service.example.com']]; + $settings->save(); + + // Set threshold settings + $thresholdSettings = new ThresholdSettings; + $thresholdSettings->absolute_enabled = true; + $thresholdSettings->absolute_ping = 50; // Threshold higher than result (20 < 50) + $thresholdSettings->absolute_download = 1; // Threshold lower than result (800 > 1) + $thresholdSettings->absolute_upload = 1; // Threshold lower than result (400 > 1) + $thresholdSettings->save(); + + $job = new SendSpeedtestThresholdNotification($result); + + // Test that the threshold functions work correctly + expect(absolutePingThresholdFailed(50, 20))->toBeFalse(); // 20 < 50 + expect(absoluteDownloadThresholdFailed(1, 104857600))->toBeFalse(); // 800 > 1 + expect(absoluteUploadThresholdFailed(1, 52428800))->toBeFalse(); // 400 > 1 + + // Mock Log facade to capture the warning when no thresholds are breached + Log::shouldReceive('warning')->with('Failed apprise thresholds not found, won\'t send notification.')->once(); + + $job->handle(); + + // Verify that the settings are correctly configured + $savedSettings = new NotificationSettings; + $savedThresholdSettings = new ThresholdSettings; + expect($savedSettings->apprise_url)->toBe('https://apprise.example.com'); + expect($savedSettings->apprise_channel_urls)->toHaveCount(1); + expect($savedThresholdSettings->absolute_enabled)->toBeTrue(); + }); +}); diff --git a/tests/Unit/MailNotificationListenersTest.php b/tests/Unit/MailNotificationListenersTest.php new file mode 100644 index 000000000..0a248a3fd --- /dev/null +++ b/tests/Unit/MailNotificationListenersTest.php @@ -0,0 +1,185 @@ +delete(); + + $user = User::factory()->create(); + $result = Result::factory()->create(); + + // Enable mail notifications in settings + $settings = new NotificationSettings; + $settings->mail_enabled = true; + $settings->mail_on_speedtest_run = true; + $settings->mail_recipients = ['test@example.com', 'admin@example.com']; + $settings->save(); + + $event = new SpeedtestCompleted($result); + $listener = new SendSpeedtestCompletedNotification; + + // Mock Mail facade to capture the notification calls + Mail::fake(); + + $listener->handle($event); + + // Verify that the settings are correctly configured + $savedSettings = new NotificationSettings; + expect($savedSettings->mail_enabled)->toBeTrue(); + expect($savedSettings->mail_on_speedtest_run)->toBeTrue(); + expect($savedSettings->mail_recipients)->toHaveCount(2); + expect($savedSettings->mail_recipients)->toContain('test@example.com'); + expect($savedSettings->mail_recipients)->toContain('admin@example.com'); + }); + + test('SendSpeedtestCompletedNotification logic when disabled', function () { + // Clean users table to ensure only one user exists + \App\Models\User::query()->delete(); + + $user = User::factory()->create(); + $result = Result::factory()->create(); + + // Disable mail notifications in settings + $settings = new NotificationSettings; + $settings->mail_enabled = false; + $settings->save(); + + $event = new SpeedtestCompleted($result); + $listener = new SendSpeedtestCompletedNotification; + + // Mock Mail facade to capture the notification calls + Mail::fake(); + + $listener->handle($event); + + // Verify that the settings are correctly configured + $savedSettings = new NotificationSettings; + expect($savedSettings->mail_enabled)->toBeFalse(); + }); + + test('SendSpeedtestCompletedNotification logic when no recipients', function () { + // Clean users table to ensure only one user exists + \App\Models\User::query()->delete(); + + $user = User::factory()->create(); + $result = Result::factory()->create(); + + // Enable mail notifications but with no recipients + $settings = new NotificationSettings; + $settings->mail_enabled = true; + $settings->mail_on_speedtest_run = true; + $settings->mail_recipients = []; + $settings->save(); + + $event = new SpeedtestCompleted($result); + $listener = new SendSpeedtestCompletedNotification; + + // Mock Log facade to capture the warning + Log::shouldReceive('warning')->with('Mail recipients not found, check mail notification channel settings.')->once(); + + $listener->handle($event); + + // Verify that the settings are correctly configured + $savedSettings = new NotificationSettings; + expect($savedSettings->mail_enabled)->toBeTrue(); + expect($savedSettings->mail_on_speedtest_run)->toBeTrue(); + expect($savedSettings->mail_recipients)->toHaveCount(0); + }); + + test('SendSpeedtestThresholdNotification logic when threshold is breached', function () { + // Clean users table to ensure only one user exists + \App\Models\User::query()->delete(); + + $user = User::factory()->create(); + $result = Result::factory()->create([ + 'ping' => 100, // High ping that will breach threshold + 'download' => 104857.6, // 0.1 MB in bytes + 'upload' => 52428.8, // 0.05 MB in bytes + ]); + + // Enable mail threshold notifications in settings + $settings = new NotificationSettings; + $settings->mail_enabled = true; + $settings->mail_on_threshold_failure = true; + $settings->mail_recipients = ['test@example.com']; + $settings->save(); + + // Set threshold settings + $thresholdSettings = new ThresholdSettings; + $thresholdSettings->absolute_enabled = true; + $thresholdSettings->absolute_ping = 50; // Threshold lower than result (100 > 50) + $thresholdSettings->absolute_download = 1; // Threshold higher than result (0.8 < 1) + $thresholdSettings->absolute_upload = 1; // Threshold higher than result (0.4 < 1) + $thresholdSettings->save(); + + $event = new SpeedtestCompleted($result); + $listener = new SendSpeedtestThresholdNotification; + + // Test that the threshold functions work correctly + expect(absolutePingThresholdFailed(50, 100))->toBeTrue(); // 100 > 50 + expect(absoluteDownloadThresholdFailed(1, 104857.6))->toBeTrue(); // 0.8 < 1 + expect(absoluteUploadThresholdFailed(1, 52428.8))->toBeTrue(); // 0.4 < 1 + + // Verify that the settings are correctly configured + $savedSettings = new NotificationSettings; + $savedThresholdSettings = new ThresholdSettings; + expect($savedSettings->mail_enabled)->toBeTrue(); + expect($savedSettings->mail_on_threshold_failure)->toBeTrue(); + expect($savedThresholdSettings->absolute_enabled)->toBeTrue(); + expect($savedThresholdSettings->absolute_ping)->toBe(50.0); + expect($savedThresholdSettings->absolute_download)->toBe(1.0); + expect($savedThresholdSettings->absolute_upload)->toBe(1.0); + }); + + test('SendSpeedtestThresholdNotification logic when thresholds not breached', function () { + // Clean users table to ensure only one user exists + \App\Models\User::query()->delete(); + + $user = User::factory()->create(); + $result = Result::factory()->create([ + 'ping' => 20, // Low ping, should not breach threshold + 'download' => 104857600, // 100 MB in bytes + 'upload' => 52428800, // 50 MB in bytes + ]); + + // Enable mail threshold notifications in settings + $settings = new NotificationSettings; + $settings->mail_enabled = true; + $settings->mail_on_threshold_failure = true; + $settings->mail_recipients = ['test@example.com']; + $settings->save(); + + // Set threshold settings + $thresholdSettings = new ThresholdSettings; + $thresholdSettings->absolute_enabled = true; + $thresholdSettings->absolute_ping = 50; // Threshold higher than result (20 < 50) + $thresholdSettings->absolute_download = 1; // Threshold lower than result (800 > 1) + $thresholdSettings->absolute_upload = 1; // Threshold lower than result (400 > 1) + $thresholdSettings->save(); + + $event = new SpeedtestCompleted($result); + $listener = new SendSpeedtestThresholdNotification; + + // Test that the threshold functions work correctly + expect(absolutePingThresholdFailed(50, 20))->toBeFalse(); // 20 < 50 + expect(absoluteDownloadThresholdFailed(1, 104857600))->toBeFalse(); // 800 > 1 + expect(absoluteUploadThresholdFailed(1, 52428800))->toBeFalse(); // 400 > 1 + + // Verify that the settings are correctly configured + $savedSettings = new NotificationSettings; + $savedThresholdSettings = new ThresholdSettings; + expect($savedSettings->mail_enabled)->toBeTrue(); + expect($savedSettings->mail_on_threshold_failure)->toBeTrue(); + expect($savedThresholdSettings->absolute_enabled)->toBeTrue(); + }); +}); diff --git a/tests/Unit/NotificationListenersTest.php b/tests/Unit/NotificationListenersTest.php new file mode 100644 index 000000000..583fc750f --- /dev/null +++ b/tests/Unit/NotificationListenersTest.php @@ -0,0 +1,149 @@ +delete(); + + $user = User::factory()->create(); + $result = Result::factory()->create(); + + // Enable database notifications in settings + $settings = new NotificationSettings; + $settings->database_enabled = true; + $settings->database_on_speedtest_run = true; + $settings->save(); + + $event = new SpeedtestCompleted($result); + $listener = new SendSpeedtestCompletedNotification; + + // Mock the Log facade to capture the notification calls + Log::shouldReceive('info')->with('Notifying user', ['id' => $user->id, 'email' => $user->email])->once(); + + $listener->handle($event); + + // Verify that the settings are correctly configured + $savedSettings = new NotificationSettings; + expect($savedSettings->database_enabled)->toBeTrue(); + expect($savedSettings->database_on_speedtest_run)->toBeTrue(); + }); + + test('SendSpeedtestCompletedNotification logic when disabled', function () { + // Clean users table to ensure only one user exists + \App\Models\User::query()->delete(); + + $user = User::factory()->create(); + $result = Result::factory()->create(); + + // Disable database notifications in settings + $settings = new NotificationSettings; + $settings->database_enabled = false; + $settings->save(); + + $event = new SpeedtestCompleted($result); + $listener = new SendSpeedtestCompletedNotification; + + // Should not log any notification calls when disabled + Log::shouldReceive('info')->never(); + + $listener->handle($event); + + // Verify that the settings are correctly configured + $savedSettings = new NotificationSettings; + expect($savedSettings->database_enabled)->toBeFalse(); + }); + + test('SendSpeedtestThresholdNotification logic when threshold is breached', function () { + // Clean users table to ensure only one user exists + \App\Models\User::query()->delete(); + + $user = User::factory()->create(); + $result = Result::factory()->create([ + 'ping' => 100, // High ping that will breach threshold + 'download' => 104857.6, // 0.1 MB in bytes + 'upload' => 52428.8, // 0.05 MB in bytes + ]); + + // Enable database threshold notifications in settings + $settings = new NotificationSettings; + $settings->database_enabled = true; + $settings->database_on_threshold_failure = true; + $settings->save(); + + // Set threshold settings + $thresholdSettings = new ThresholdSettings; + $thresholdSettings->absolute_enabled = true; + $thresholdSettings->absolute_ping = 50; // Threshold lower than result (100 > 50) + $thresholdSettings->absolute_download = 1; // Threshold higher than result (0.8 < 1) + $thresholdSettings->absolute_upload = 1; // Threshold higher than result (0.4 < 1) + $thresholdSettings->save(); + + $event = new SpeedtestCompleted($result); + $listener = new SendSpeedtestThresholdNotification; + + // Test that the threshold functions work correctly + expect(absolutePingThresholdFailed(50, 100))->toBeTrue(); // 100 > 50 + expect(absoluteDownloadThresholdFailed(1, 104857.6))->toBeTrue(); // 0.8 < 1 + expect(absoluteUploadThresholdFailed(1, 52428.8))->toBeTrue(); // 0.4 < 1 + + // Verify that the settings are correctly configured + $savedSettings = new NotificationSettings; + $savedThresholdSettings = new ThresholdSettings; + expect($savedSettings->database_enabled)->toBeTrue(); + expect($savedSettings->database_on_threshold_failure)->toBeTrue(); + expect($savedThresholdSettings->absolute_enabled)->toBeTrue(); + expect($savedThresholdSettings->absolute_ping)->toBe(50.0); + expect($savedThresholdSettings->absolute_download)->toBe(1.0); + expect($savedThresholdSettings->absolute_upload)->toBe(1.0); + }); + + test('SendSpeedtestThresholdNotification logic when thresholds not breached', function () { + // Clean users table to ensure only one user exists + \App\Models\User::query()->delete(); + + $user = User::factory()->create(); + $result = Result::factory()->create([ + 'ping' => 20, // Low ping, should not breach threshold + 'download' => 104857600, // 100 MB in bytes + 'upload' => 52428800, // 50 MB in bytes + ]); + + // Enable database threshold notifications in settings + $settings = new NotificationSettings; + $settings->database_enabled = true; + $settings->database_on_threshold_failure = true; + $settings->save(); + + // Set threshold settings + $thresholdSettings = new ThresholdSettings; + $thresholdSettings->absolute_enabled = true; + $thresholdSettings->absolute_ping = 50; // Threshold higher than result (20 < 50) + $thresholdSettings->absolute_download = 1; // Threshold lower than result (800 > 1) + $thresholdSettings->absolute_upload = 1; // Threshold lower than result (400 > 1) + $thresholdSettings->save(); + + $event = new SpeedtestCompleted($result); + $listener = new SendSpeedtestThresholdNotification; + + // Test that the threshold functions work correctly + expect(absolutePingThresholdFailed(50, 20))->toBeFalse(); // 20 < 50 + expect(absoluteDownloadThresholdFailed(1, 104857600))->toBeFalse(); // 800 > 1 + expect(absoluteUploadThresholdFailed(1, 52428800))->toBeFalse(); // 400 > 1 + + // Verify that the settings are correctly configured + $savedSettings = new NotificationSettings; + $savedThresholdSettings = new ThresholdSettings; + expect($savedSettings->database_enabled)->toBeTrue(); + expect($savedSettings->database_on_threshold_failure)->toBeTrue(); + expect($savedThresholdSettings->absolute_enabled)->toBeTrue(); + }); +}); diff --git a/tests/Unit/NotificationTest.php b/tests/Unit/NotificationTest.php new file mode 100644 index 000000000..2865270d8 --- /dev/null +++ b/tests/Unit/NotificationTest.php @@ -0,0 +1,67 @@ +toBeInstanceOf(NotificationSettings::class); + expect($settings->database_enabled)->toBeFalse(); + expect($settings->mail_enabled)->toBeFalse(); + expect($settings->webhook_enabled)->toBeFalse(); + expect($settings->apprise_enabled)->toBeFalse(); + }); + + test('returns correct group name', function () { + expect(NotificationSettings::group())->toBe('notification'); + }); + + test('can set and retrieve notification settings for supported channels', function () { + $settings = new NotificationSettings; + + // Test database settings + $settings->database_enabled = true; + $settings->database_on_speedtest_run = true; + $settings->database_on_threshold_failure = false; + + expect($settings->database_enabled)->toBeTrue(); + expect($settings->database_on_speedtest_run)->toBeTrue(); + expect($settings->database_on_threshold_failure)->toBeFalse(); + + // Test mail settings + $settings->mail_enabled = true; + $settings->mail_recipients = ['test@example.com']; + + expect($settings->mail_enabled)->toBeTrue(); + expect($settings->mail_recipients)->toBe(['test@example.com']); + + // Test webhook settings + $settings->webhook_enabled = true; + $settings->webhook_urls = ['https://webhook.example.com']; + + expect($settings->webhook_enabled)->toBeTrue(); + expect($settings->webhook_urls)->toBe(['https://webhook.example.com']); + + // Test apprise settings + $settings->apprise_enabled = true; + $settings->apprise_url = 'https://apprise.example.com'; + $settings->apprise_verify_ssl = true; + + expect($settings->apprise_enabled)->toBeTrue(); + expect($settings->apprise_url)->toBe('https://apprise.example.com'); + expect($settings->apprise_verify_ssl)->toBeTrue(); + }); + + test('handles null values correctly', function () { + $settings = new NotificationSettings; + + $settings->mail_recipients = null; + $settings->webhook_urls = null; + $settings->apprise_url = null; + + expect($settings->mail_recipients)->toBeNull(); + expect($settings->webhook_urls)->toBeNull(); + expect($settings->apprise_url)->toBeNull(); + }); +}); diff --git a/tests/Unit/WebhookNotificationListenersTest.php b/tests/Unit/WebhookNotificationListenersTest.php new file mode 100644 index 000000000..a86c0c4ef --- /dev/null +++ b/tests/Unit/WebhookNotificationListenersTest.php @@ -0,0 +1,196 @@ +delete(); + + $user = User::factory()->create(); + $result = Result::factory()->create(); + + // Enable webhook notifications in settings + $settings = new NotificationSettings; + $settings->webhook_enabled = true; + $settings->webhook_on_speedtest_run = true; + $settings->webhook_urls = [ + ['url' => 'https://webhook1.example.com'], + ['url' => 'https://webhook2.example.com'], + ]; + $settings->save(); + + $event = new SpeedtestCompleted($result); + $listener = new SendSpeedtestCompletedNotification; + + // Mock WebhookCall to avoid actual HTTP requests + $this->mock(WebhookCall::class, function ($mock) { + $mock->shouldReceive('create')->andReturnSelf(); + $mock->shouldReceive('url')->andReturnSelf(); + $mock->shouldReceive('payload')->andReturnSelf(); + $mock->shouldReceive('doNotSign')->andReturnSelf(); + $mock->shouldReceive('dispatch')->andReturnSelf(); + }); + + $listener->handle($event); + + // Verify that the settings are correctly configured + $savedSettings = new NotificationSettings; + expect($savedSettings->webhook_enabled)->toBeTrue(); + expect($savedSettings->webhook_on_speedtest_run)->toBeTrue(); + expect($savedSettings->webhook_urls)->toHaveCount(2); + expect($savedSettings->webhook_urls[0]['url'])->toBe('https://webhook1.example.com'); + expect($savedSettings->webhook_urls[1]['url'])->toBe('https://webhook2.example.com'); + }); + + test('SendSpeedtestCompletedNotification logic when disabled', function () { + // Clean users table to ensure only one user exists + \App\Models\User::query()->delete(); + + $user = User::factory()->create(); + $result = Result::factory()->create(); + + // Disable webhook notifications in settings + $settings = new NotificationSettings; + $settings->webhook_enabled = false; + $settings->save(); + + $event = new SpeedtestCompleted($result); + $listener = new SendSpeedtestCompletedNotification; + + // Mock WebhookCall to avoid actual HTTP requests + $this->mock(WebhookCall::class, function ($mock) { + $mock->shouldReceive('create')->never(); + }); + + $listener->handle($event); + + // Verify that the settings are correctly configured + $savedSettings = new NotificationSettings; + expect($savedSettings->webhook_enabled)->toBeFalse(); + }); + + test('SendSpeedtestCompletedNotification logic when no urls', function () { + // Clean users table to ensure only one user exists + \App\Models\User::query()->delete(); + + $user = User::factory()->create(); + $result = Result::factory()->create(); + + // Enable webhook notifications but with no urls + $settings = new NotificationSettings; + $settings->webhook_enabled = true; + $settings->webhook_on_speedtest_run = true; + $settings->webhook_urls = []; + $settings->save(); + + $event = new SpeedtestCompleted($result); + $listener = new SendSpeedtestCompletedNotification; + + // Mock Log facade to capture the warning + Log::shouldReceive('warning')->with('Webhook urls not found, check webhook notification channel settings.')->once(); + + $listener->handle($event); + + // Verify that the settings are correctly configured + $savedSettings = new NotificationSettings; + expect($savedSettings->webhook_enabled)->toBeTrue(); + expect($savedSettings->webhook_on_speedtest_run)->toBeTrue(); + expect($savedSettings->webhook_urls)->toHaveCount(0); + }); + + test('SendSpeedtestThresholdNotification logic when threshold is breached', function () { + // Clean users table to ensure only one user exists + \App\Models\User::query()->delete(); + + $user = User::factory()->create(); + $result = Result::factory()->create([ + 'ping' => 100, // High ping that will breach threshold + 'download' => 104857.6, // 0.1 MB in bytes + 'upload' => 52428.8, // 0.05 MB in bytes + ]); + + // Enable webhook threshold notifications in settings + $settings = new NotificationSettings; + $settings->webhook_enabled = true; + $settings->webhook_on_threshold_failure = true; + $settings->webhook_urls = [['url' => 'https://webhook.example.com']]; + $settings->save(); + + // Set threshold settings + $thresholdSettings = new ThresholdSettings; + $thresholdSettings->absolute_enabled = true; + $thresholdSettings->absolute_ping = 50; // Threshold lower than result (100 > 50) + $thresholdSettings->absolute_download = 1; // Threshold higher than result (0.8 < 1) + $thresholdSettings->absolute_upload = 1; // Threshold higher than result (0.4 < 1) + $thresholdSettings->save(); + + $event = new SpeedtestCompleted($result); + $listener = new SendSpeedtestThresholdNotification; + + // Test that the threshold functions work correctly + expect(absolutePingThresholdFailed(50, 100))->toBeTrue(); // 100 > 50 + expect(absoluteDownloadThresholdFailed(1, 104857.6))->toBeTrue(); // 0.8 < 1 + expect(absoluteUploadThresholdFailed(1, 52428.8))->toBeTrue(); // 0.4 < 1 + + // Verify that the settings are correctly configured + $savedSettings = new NotificationSettings; + $savedThresholdSettings = new ThresholdSettings; + expect($savedSettings->webhook_enabled)->toBeTrue(); + expect($savedSettings->webhook_on_threshold_failure)->toBeTrue(); + expect($savedThresholdSettings->absolute_enabled)->toBeTrue(); + expect($savedThresholdSettings->absolute_ping)->toBe(50.0); + expect($savedThresholdSettings->absolute_download)->toBe(1.0); + expect($savedThresholdSettings->absolute_upload)->toBe(1.0); + }); + + test('SendSpeedtestThresholdNotification logic when thresholds not breached', function () { + // Clean users table to ensure only one user exists + \App\Models\User::query()->delete(); + + $user = User::factory()->create(); + $result = Result::factory()->create([ + 'ping' => 20, // Low ping, should not breach threshold + 'download' => 104857600, // 100 MB in bytes + 'upload' => 52428800, // 50 MB in bytes + ]); + + // Enable webhook threshold notifications in settings + $settings = new NotificationSettings; + $settings->webhook_enabled = true; + $settings->webhook_on_threshold_failure = true; + $settings->webhook_urls = [['url' => 'https://webhook.example.com']]; + $settings->save(); + + // Set threshold settings + $thresholdSettings = new ThresholdSettings; + $thresholdSettings->absolute_enabled = true; + $thresholdSettings->absolute_ping = 50; // Threshold higher than result (20 < 50) + $thresholdSettings->absolute_download = 1; // Threshold lower than result (800 > 1) + $thresholdSettings->absolute_upload = 1; // Threshold lower than result (400 > 1) + $thresholdSettings->save(); + + $event = new SpeedtestCompleted($result); + $listener = new SendSpeedtestThresholdNotification; + + // Test that the threshold functions work correctly + expect(absolutePingThresholdFailed(50, 20))->toBeFalse(); // 20 < 50 + expect(absoluteDownloadThresholdFailed(1, 104857600))->toBeFalse(); // 800 > 1 + expect(absoluteUploadThresholdFailed(1, 52428800))->toBeFalse(); // 400 > 1 + + // Verify that the settings are correctly configured + $savedSettings = new NotificationSettings; + $savedThresholdSettings = new ThresholdSettings; + expect($savedSettings->webhook_enabled)->toBeTrue(); + expect($savedSettings->webhook_on_threshold_failure)->toBeTrue(); + expect($savedThresholdSettings->absolute_enabled)->toBeTrue(); + }); +}); From b4a6e38f57534379ff77a46ef638279098148b93 Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Thu, 26 Jun 2025 10:49:14 +0200 Subject: [PATCH 29/31] Remove tests --- .github/workflows/ci.yml | 29 --- phpunit.xml | 8 - tests/Feature/NotificationActionsTest.php | 93 --------- tests/Unit/AppriseNotificationJobsTest.php | 156 -------------- tests/Unit/MailNotificationListenersTest.php | 185 ----------------- tests/Unit/NotificationListenersTest.php | 149 ------------- tests/Unit/NotificationTest.php | 67 ------ .../Unit/WebhookNotificationListenersTest.php | 196 ------------------ 8 files changed, 883 deletions(-) delete mode 100644 tests/Feature/NotificationActionsTest.php delete mode 100644 tests/Unit/AppriseNotificationJobsTest.php delete mode 100644 tests/Unit/MailNotificationListenersTest.php delete mode 100644 tests/Unit/NotificationListenersTest.php delete mode 100644 tests/Unit/NotificationTest.php delete mode 100644 tests/Unit/WebhookNotificationListenersTest.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index abb14acf8..42cccdca2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,32 +50,3 @@ jobs: - name: Run Tests run: php artisan test --parallel - - test-notifications: - needs: lint-app - runs-on: ubuntu-24.04 - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.3' - - - name: Create SQLite Database - run: | - touch database/testing.sqlite - - - name: Install Dependencies - run: | - composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist - - - name: Copy Environment File - run: cp .env.example .env - - - name: Generate App Key - run: php artisan key:generate --quiet - - - name: Run Notification Tests - run: php artisan test --testsuite=Notification diff --git a/phpunit.xml b/phpunit.xml index 8bbc6de53..1f0019785 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -11,14 +11,6 @@ tests/Feature - - tests/Unit/NotificationListenersTest.php - tests/Unit/MailNotificationListenersTest.php - tests/Unit/WebhookNotificationListenersTest.php - tests/Unit/AppriseNotificationJobsTest.php - tests/Unit/NotificationTest.php - tests/Feature/NotificationActionsTest.php - diff --git a/tests/Feature/NotificationActionsTest.php b/tests/Feature/NotificationActionsTest.php deleted file mode 100644 index aa7cd99e0..000000000 --- a/tests/Feature/NotificationActionsTest.php +++ /dev/null @@ -1,93 +0,0 @@ -handle($recipients); - - // If mail is queued, use assertQueued. If not, use assertSent. - Mail::assertQueued(\App\Mail\Test::class, function ($mail) use ($recipients) { - return in_array($mail->to[0]['address'], $recipients); - }); - Mail::assertQueuedCount(count($recipients)); - }); - - test('SendMailTestNotification handles empty recipients', function () { - Mail::fake(); - - $action = new SendMailTestNotification; - $action->handle([]); - - Mail::assertNotSent(\App\Mail\Test::class); - }); - - test('SendWebhookTestNotification sends webhook calls', function () { - $urls = [ - ['url' => 'https://webhook.example.com'], - ['url' => 'https://another-webhook.example.com'], - ]; - - $action = new SendWebhookTestNotification; - - // Mock the WebhookCall to avoid actual HTTP requests - $this->mock(WebhookCall::class, function ($mock) { - $mock->shouldReceive('create')->andReturnSelf(); - $mock->shouldReceive('url')->andReturnSelf(); - $mock->shouldReceive('payload')->andReturnSelf(); - $mock->shouldReceive('doNotSign')->andReturnSelf(); - $mock->shouldReceive('dispatch')->andReturnSelf(); - }); - - $action->handle($urls); - - // The action should complete without throwing exceptions - expect(true)->toBeTrue(); - }); - - test('SendWebhookTestNotification handles empty urls', function () { - $action = new SendWebhookTestNotification; - - // Mock the WebhookCall to avoid actual HTTP requests - $this->mock(WebhookCall::class, function ($mock) { - $mock->shouldReceive('create')->andReturnSelf(); - $mock->shouldReceive('url')->andReturnSelf(); - $mock->shouldReceive('payload')->andReturnSelf(); - $mock->shouldReceive('doNotSign')->andReturnSelf(); - $mock->shouldReceive('dispatch')->andReturnSelf(); - }); - - $action->handle([]); - - // The action should complete without throwing exceptions - expect(true)->toBeTrue(); - }); - - test('SendAppriseTestNotification sends apprise notifications', function () { - $apprise_url = 'https://apprise.example.com'; - $apprise_verify_ssl = true; - $channel_urls = [ - ['channel_url' => 'https://service1.example.com'], - ['channel_url' => 'https://service2.example.com'], - ]; - - $action = new SendAppriseTestNotification; - - // Use Laravel's Http::fake() to avoid actual HTTP requests - \Illuminate\Support\Facades\Http::fake(); - - $action->handle($apprise_url, $apprise_verify_ssl, $channel_urls); - - // The action should complete without throwing exceptions - expect(true)->toBeTrue(); - }); -}); diff --git a/tests/Unit/AppriseNotificationJobsTest.php b/tests/Unit/AppriseNotificationJobsTest.php deleted file mode 100644 index e618a816d..000000000 --- a/tests/Unit/AppriseNotificationJobsTest.php +++ /dev/null @@ -1,156 +0,0 @@ -delete(); - - $user = User::factory()->create(); - $result = Result::factory()->create(); - - // Set apprise channel urls and URL (the job only checks channel_urls, but service needs apprise_url) - $settings = new NotificationSettings; - $settings->apprise_url = 'https://apprise.example.com'; - $settings->apprise_channel_urls = [ - ['channel_url' => 'https://service1.example.com'], - ['channel_url' => 'https://service2.example.com'], - ]; - $settings->save(); - - $job = new SendSpeedtestCompletedNotification($result); - - // Do not assert the static call, just run the job - $job->handle(); - - // Verify that the settings are correctly configured - $savedSettings = new NotificationSettings; - expect($savedSettings->apprise_url)->toBe('https://apprise.example.com'); - expect($savedSettings->apprise_channel_urls)->toHaveCount(2); - expect($savedSettings->apprise_channel_urls[0]['channel_url'])->toBe('https://service1.example.com'); - expect($savedSettings->apprise_channel_urls[1]['channel_url'])->toBe('https://service2.example.com'); - }); - - test('SendSpeedtestCompletedNotification logic when no channel urls', function () { - // Clean users table to ensure only one user exists - \App\Models\User::query()->delete(); - - $user = User::factory()->create(); - $result = Result::factory()->create(); - - // Set empty apprise channel urls - $settings = new NotificationSettings; - $settings->apprise_url = 'https://apprise.example.com'; - $settings->apprise_channel_urls = []; - $settings->save(); - - $job = new SendSpeedtestCompletedNotification($result); - - // Mock Log facade to capture the warning - Log::shouldReceive('warning')->with('Apprise service URLs not found; check Apprise notification settings.')->once(); - - $job->handle(); - - // Verify that the settings are correctly configured - $savedSettings = new NotificationSettings; - expect($savedSettings->apprise_channel_urls)->toHaveCount(0); - }); - - test('SendSpeedtestThresholdNotification logic when threshold is breached', function () { - // Clean users table to ensure only one user exists - \App\Models\User::query()->delete(); - - $user = User::factory()->create(); - $result = Result::factory()->create([ - 'ping' => 100, // High ping that will breach threshold - 'download' => 104857.6, // 0.1 MB in bytes - 'upload' => 52428.8, // 0.05 MB in bytes - ]); - - // Set apprise channel urls and URL - $settings = new NotificationSettings; - $settings->apprise_url = 'https://apprise.example.com'; - $settings->apprise_channel_urls = [['channel_url' => 'https://service.example.com']]; - $settings->save(); - - // Set threshold settings - $thresholdSettings = new ThresholdSettings; - $thresholdSettings->absolute_enabled = true; - $thresholdSettings->absolute_ping = 50; // Threshold lower than result (100 > 50) - $thresholdSettings->absolute_download = 1; // Threshold higher than result (0.8 < 1) - $thresholdSettings->absolute_upload = 1; // Threshold higher than result (0.4 < 1) - $thresholdSettings->save(); - - $job = new SendSpeedtestThresholdNotification($result); - - // Test that the threshold functions work correctly - expect(absolutePingThresholdFailed(50, 100))->toBeTrue(); // 100 > 50 - expect(absoluteDownloadThresholdFailed(1, 104857.6))->toBeTrue(); // 0.8 < 1 - expect(absoluteUploadThresholdFailed(1, 52428.8))->toBeTrue(); // 0.4 < 1 - - // Do not assert the static call, just run the job - $job->handle(); - - // Verify that the settings are correctly configured - $savedSettings = new NotificationSettings; - $savedThresholdSettings = new ThresholdSettings; - expect($savedSettings->apprise_url)->toBe('https://apprise.example.com'); - expect($savedSettings->apprise_channel_urls)->toHaveCount(1); - expect($savedThresholdSettings->absolute_enabled)->toBeTrue(); - expect($savedThresholdSettings->absolute_ping)->toBe(50.0); - expect($savedThresholdSettings->absolute_download)->toBe(1.0); - expect($savedThresholdSettings->absolute_upload)->toBe(1.0); - }); - - test('SendSpeedtestThresholdNotification logic when thresholds not breached', function () { - // Clean users table to ensure only one user exists - \App\Models\User::query()->delete(); - - $user = User::factory()->create(); - $result = Result::factory()->create([ - 'ping' => 20, // Low ping, should not breach threshold - 'download' => 104857600, // 100 MB in bytes - 'upload' => 52428800, // 50 MB in bytes - ]); - - // Set apprise channel urls and URL - $settings = new NotificationSettings; - $settings->apprise_url = 'https://apprise.example.com'; - $settings->apprise_channel_urls = [['channel_url' => 'https://service.example.com']]; - $settings->save(); - - // Set threshold settings - $thresholdSettings = new ThresholdSettings; - $thresholdSettings->absolute_enabled = true; - $thresholdSettings->absolute_ping = 50; // Threshold higher than result (20 < 50) - $thresholdSettings->absolute_download = 1; // Threshold lower than result (800 > 1) - $thresholdSettings->absolute_upload = 1; // Threshold lower than result (400 > 1) - $thresholdSettings->save(); - - $job = new SendSpeedtestThresholdNotification($result); - - // Test that the threshold functions work correctly - expect(absolutePingThresholdFailed(50, 20))->toBeFalse(); // 20 < 50 - expect(absoluteDownloadThresholdFailed(1, 104857600))->toBeFalse(); // 800 > 1 - expect(absoluteUploadThresholdFailed(1, 52428800))->toBeFalse(); // 400 > 1 - - // Mock Log facade to capture the warning when no thresholds are breached - Log::shouldReceive('warning')->with('Failed apprise thresholds not found, won\'t send notification.')->once(); - - $job->handle(); - - // Verify that the settings are correctly configured - $savedSettings = new NotificationSettings; - $savedThresholdSettings = new ThresholdSettings; - expect($savedSettings->apprise_url)->toBe('https://apprise.example.com'); - expect($savedSettings->apprise_channel_urls)->toHaveCount(1); - expect($savedThresholdSettings->absolute_enabled)->toBeTrue(); - }); -}); diff --git a/tests/Unit/MailNotificationListenersTest.php b/tests/Unit/MailNotificationListenersTest.php deleted file mode 100644 index 0a248a3fd..000000000 --- a/tests/Unit/MailNotificationListenersTest.php +++ /dev/null @@ -1,185 +0,0 @@ -delete(); - - $user = User::factory()->create(); - $result = Result::factory()->create(); - - // Enable mail notifications in settings - $settings = new NotificationSettings; - $settings->mail_enabled = true; - $settings->mail_on_speedtest_run = true; - $settings->mail_recipients = ['test@example.com', 'admin@example.com']; - $settings->save(); - - $event = new SpeedtestCompleted($result); - $listener = new SendSpeedtestCompletedNotification; - - // Mock Mail facade to capture the notification calls - Mail::fake(); - - $listener->handle($event); - - // Verify that the settings are correctly configured - $savedSettings = new NotificationSettings; - expect($savedSettings->mail_enabled)->toBeTrue(); - expect($savedSettings->mail_on_speedtest_run)->toBeTrue(); - expect($savedSettings->mail_recipients)->toHaveCount(2); - expect($savedSettings->mail_recipients)->toContain('test@example.com'); - expect($savedSettings->mail_recipients)->toContain('admin@example.com'); - }); - - test('SendSpeedtestCompletedNotification logic when disabled', function () { - // Clean users table to ensure only one user exists - \App\Models\User::query()->delete(); - - $user = User::factory()->create(); - $result = Result::factory()->create(); - - // Disable mail notifications in settings - $settings = new NotificationSettings; - $settings->mail_enabled = false; - $settings->save(); - - $event = new SpeedtestCompleted($result); - $listener = new SendSpeedtestCompletedNotification; - - // Mock Mail facade to capture the notification calls - Mail::fake(); - - $listener->handle($event); - - // Verify that the settings are correctly configured - $savedSettings = new NotificationSettings; - expect($savedSettings->mail_enabled)->toBeFalse(); - }); - - test('SendSpeedtestCompletedNotification logic when no recipients', function () { - // Clean users table to ensure only one user exists - \App\Models\User::query()->delete(); - - $user = User::factory()->create(); - $result = Result::factory()->create(); - - // Enable mail notifications but with no recipients - $settings = new NotificationSettings; - $settings->mail_enabled = true; - $settings->mail_on_speedtest_run = true; - $settings->mail_recipients = []; - $settings->save(); - - $event = new SpeedtestCompleted($result); - $listener = new SendSpeedtestCompletedNotification; - - // Mock Log facade to capture the warning - Log::shouldReceive('warning')->with('Mail recipients not found, check mail notification channel settings.')->once(); - - $listener->handle($event); - - // Verify that the settings are correctly configured - $savedSettings = new NotificationSettings; - expect($savedSettings->mail_enabled)->toBeTrue(); - expect($savedSettings->mail_on_speedtest_run)->toBeTrue(); - expect($savedSettings->mail_recipients)->toHaveCount(0); - }); - - test('SendSpeedtestThresholdNotification logic when threshold is breached', function () { - // Clean users table to ensure only one user exists - \App\Models\User::query()->delete(); - - $user = User::factory()->create(); - $result = Result::factory()->create([ - 'ping' => 100, // High ping that will breach threshold - 'download' => 104857.6, // 0.1 MB in bytes - 'upload' => 52428.8, // 0.05 MB in bytes - ]); - - // Enable mail threshold notifications in settings - $settings = new NotificationSettings; - $settings->mail_enabled = true; - $settings->mail_on_threshold_failure = true; - $settings->mail_recipients = ['test@example.com']; - $settings->save(); - - // Set threshold settings - $thresholdSettings = new ThresholdSettings; - $thresholdSettings->absolute_enabled = true; - $thresholdSettings->absolute_ping = 50; // Threshold lower than result (100 > 50) - $thresholdSettings->absolute_download = 1; // Threshold higher than result (0.8 < 1) - $thresholdSettings->absolute_upload = 1; // Threshold higher than result (0.4 < 1) - $thresholdSettings->save(); - - $event = new SpeedtestCompleted($result); - $listener = new SendSpeedtestThresholdNotification; - - // Test that the threshold functions work correctly - expect(absolutePingThresholdFailed(50, 100))->toBeTrue(); // 100 > 50 - expect(absoluteDownloadThresholdFailed(1, 104857.6))->toBeTrue(); // 0.8 < 1 - expect(absoluteUploadThresholdFailed(1, 52428.8))->toBeTrue(); // 0.4 < 1 - - // Verify that the settings are correctly configured - $savedSettings = new NotificationSettings; - $savedThresholdSettings = new ThresholdSettings; - expect($savedSettings->mail_enabled)->toBeTrue(); - expect($savedSettings->mail_on_threshold_failure)->toBeTrue(); - expect($savedThresholdSettings->absolute_enabled)->toBeTrue(); - expect($savedThresholdSettings->absolute_ping)->toBe(50.0); - expect($savedThresholdSettings->absolute_download)->toBe(1.0); - expect($savedThresholdSettings->absolute_upload)->toBe(1.0); - }); - - test('SendSpeedtestThresholdNotification logic when thresholds not breached', function () { - // Clean users table to ensure only one user exists - \App\Models\User::query()->delete(); - - $user = User::factory()->create(); - $result = Result::factory()->create([ - 'ping' => 20, // Low ping, should not breach threshold - 'download' => 104857600, // 100 MB in bytes - 'upload' => 52428800, // 50 MB in bytes - ]); - - // Enable mail threshold notifications in settings - $settings = new NotificationSettings; - $settings->mail_enabled = true; - $settings->mail_on_threshold_failure = true; - $settings->mail_recipients = ['test@example.com']; - $settings->save(); - - // Set threshold settings - $thresholdSettings = new ThresholdSettings; - $thresholdSettings->absolute_enabled = true; - $thresholdSettings->absolute_ping = 50; // Threshold higher than result (20 < 50) - $thresholdSettings->absolute_download = 1; // Threshold lower than result (800 > 1) - $thresholdSettings->absolute_upload = 1; // Threshold lower than result (400 > 1) - $thresholdSettings->save(); - - $event = new SpeedtestCompleted($result); - $listener = new SendSpeedtestThresholdNotification; - - // Test that the threshold functions work correctly - expect(absolutePingThresholdFailed(50, 20))->toBeFalse(); // 20 < 50 - expect(absoluteDownloadThresholdFailed(1, 104857600))->toBeFalse(); // 800 > 1 - expect(absoluteUploadThresholdFailed(1, 52428800))->toBeFalse(); // 400 > 1 - - // Verify that the settings are correctly configured - $savedSettings = new NotificationSettings; - $savedThresholdSettings = new ThresholdSettings; - expect($savedSettings->mail_enabled)->toBeTrue(); - expect($savedSettings->mail_on_threshold_failure)->toBeTrue(); - expect($savedThresholdSettings->absolute_enabled)->toBeTrue(); - }); -}); diff --git a/tests/Unit/NotificationListenersTest.php b/tests/Unit/NotificationListenersTest.php deleted file mode 100644 index 583fc750f..000000000 --- a/tests/Unit/NotificationListenersTest.php +++ /dev/null @@ -1,149 +0,0 @@ -delete(); - - $user = User::factory()->create(); - $result = Result::factory()->create(); - - // Enable database notifications in settings - $settings = new NotificationSettings; - $settings->database_enabled = true; - $settings->database_on_speedtest_run = true; - $settings->save(); - - $event = new SpeedtestCompleted($result); - $listener = new SendSpeedtestCompletedNotification; - - // Mock the Log facade to capture the notification calls - Log::shouldReceive('info')->with('Notifying user', ['id' => $user->id, 'email' => $user->email])->once(); - - $listener->handle($event); - - // Verify that the settings are correctly configured - $savedSettings = new NotificationSettings; - expect($savedSettings->database_enabled)->toBeTrue(); - expect($savedSettings->database_on_speedtest_run)->toBeTrue(); - }); - - test('SendSpeedtestCompletedNotification logic when disabled', function () { - // Clean users table to ensure only one user exists - \App\Models\User::query()->delete(); - - $user = User::factory()->create(); - $result = Result::factory()->create(); - - // Disable database notifications in settings - $settings = new NotificationSettings; - $settings->database_enabled = false; - $settings->save(); - - $event = new SpeedtestCompleted($result); - $listener = new SendSpeedtestCompletedNotification; - - // Should not log any notification calls when disabled - Log::shouldReceive('info')->never(); - - $listener->handle($event); - - // Verify that the settings are correctly configured - $savedSettings = new NotificationSettings; - expect($savedSettings->database_enabled)->toBeFalse(); - }); - - test('SendSpeedtestThresholdNotification logic when threshold is breached', function () { - // Clean users table to ensure only one user exists - \App\Models\User::query()->delete(); - - $user = User::factory()->create(); - $result = Result::factory()->create([ - 'ping' => 100, // High ping that will breach threshold - 'download' => 104857.6, // 0.1 MB in bytes - 'upload' => 52428.8, // 0.05 MB in bytes - ]); - - // Enable database threshold notifications in settings - $settings = new NotificationSettings; - $settings->database_enabled = true; - $settings->database_on_threshold_failure = true; - $settings->save(); - - // Set threshold settings - $thresholdSettings = new ThresholdSettings; - $thresholdSettings->absolute_enabled = true; - $thresholdSettings->absolute_ping = 50; // Threshold lower than result (100 > 50) - $thresholdSettings->absolute_download = 1; // Threshold higher than result (0.8 < 1) - $thresholdSettings->absolute_upload = 1; // Threshold higher than result (0.4 < 1) - $thresholdSettings->save(); - - $event = new SpeedtestCompleted($result); - $listener = new SendSpeedtestThresholdNotification; - - // Test that the threshold functions work correctly - expect(absolutePingThresholdFailed(50, 100))->toBeTrue(); // 100 > 50 - expect(absoluteDownloadThresholdFailed(1, 104857.6))->toBeTrue(); // 0.8 < 1 - expect(absoluteUploadThresholdFailed(1, 52428.8))->toBeTrue(); // 0.4 < 1 - - // Verify that the settings are correctly configured - $savedSettings = new NotificationSettings; - $savedThresholdSettings = new ThresholdSettings; - expect($savedSettings->database_enabled)->toBeTrue(); - expect($savedSettings->database_on_threshold_failure)->toBeTrue(); - expect($savedThresholdSettings->absolute_enabled)->toBeTrue(); - expect($savedThresholdSettings->absolute_ping)->toBe(50.0); - expect($savedThresholdSettings->absolute_download)->toBe(1.0); - expect($savedThresholdSettings->absolute_upload)->toBe(1.0); - }); - - test('SendSpeedtestThresholdNotification logic when thresholds not breached', function () { - // Clean users table to ensure only one user exists - \App\Models\User::query()->delete(); - - $user = User::factory()->create(); - $result = Result::factory()->create([ - 'ping' => 20, // Low ping, should not breach threshold - 'download' => 104857600, // 100 MB in bytes - 'upload' => 52428800, // 50 MB in bytes - ]); - - // Enable database threshold notifications in settings - $settings = new NotificationSettings; - $settings->database_enabled = true; - $settings->database_on_threshold_failure = true; - $settings->save(); - - // Set threshold settings - $thresholdSettings = new ThresholdSettings; - $thresholdSettings->absolute_enabled = true; - $thresholdSettings->absolute_ping = 50; // Threshold higher than result (20 < 50) - $thresholdSettings->absolute_download = 1; // Threshold lower than result (800 > 1) - $thresholdSettings->absolute_upload = 1; // Threshold lower than result (400 > 1) - $thresholdSettings->save(); - - $event = new SpeedtestCompleted($result); - $listener = new SendSpeedtestThresholdNotification; - - // Test that the threshold functions work correctly - expect(absolutePingThresholdFailed(50, 20))->toBeFalse(); // 20 < 50 - expect(absoluteDownloadThresholdFailed(1, 104857600))->toBeFalse(); // 800 > 1 - expect(absoluteUploadThresholdFailed(1, 52428800))->toBeFalse(); // 400 > 1 - - // Verify that the settings are correctly configured - $savedSettings = new NotificationSettings; - $savedThresholdSettings = new ThresholdSettings; - expect($savedSettings->database_enabled)->toBeTrue(); - expect($savedSettings->database_on_threshold_failure)->toBeTrue(); - expect($savedThresholdSettings->absolute_enabled)->toBeTrue(); - }); -}); diff --git a/tests/Unit/NotificationTest.php b/tests/Unit/NotificationTest.php deleted file mode 100644 index 2865270d8..000000000 --- a/tests/Unit/NotificationTest.php +++ /dev/null @@ -1,67 +0,0 @@ -toBeInstanceOf(NotificationSettings::class); - expect($settings->database_enabled)->toBeFalse(); - expect($settings->mail_enabled)->toBeFalse(); - expect($settings->webhook_enabled)->toBeFalse(); - expect($settings->apprise_enabled)->toBeFalse(); - }); - - test('returns correct group name', function () { - expect(NotificationSettings::group())->toBe('notification'); - }); - - test('can set and retrieve notification settings for supported channels', function () { - $settings = new NotificationSettings; - - // Test database settings - $settings->database_enabled = true; - $settings->database_on_speedtest_run = true; - $settings->database_on_threshold_failure = false; - - expect($settings->database_enabled)->toBeTrue(); - expect($settings->database_on_speedtest_run)->toBeTrue(); - expect($settings->database_on_threshold_failure)->toBeFalse(); - - // Test mail settings - $settings->mail_enabled = true; - $settings->mail_recipients = ['test@example.com']; - - expect($settings->mail_enabled)->toBeTrue(); - expect($settings->mail_recipients)->toBe(['test@example.com']); - - // Test webhook settings - $settings->webhook_enabled = true; - $settings->webhook_urls = ['https://webhook.example.com']; - - expect($settings->webhook_enabled)->toBeTrue(); - expect($settings->webhook_urls)->toBe(['https://webhook.example.com']); - - // Test apprise settings - $settings->apprise_enabled = true; - $settings->apprise_url = 'https://apprise.example.com'; - $settings->apprise_verify_ssl = true; - - expect($settings->apprise_enabled)->toBeTrue(); - expect($settings->apprise_url)->toBe('https://apprise.example.com'); - expect($settings->apprise_verify_ssl)->toBeTrue(); - }); - - test('handles null values correctly', function () { - $settings = new NotificationSettings; - - $settings->mail_recipients = null; - $settings->webhook_urls = null; - $settings->apprise_url = null; - - expect($settings->mail_recipients)->toBeNull(); - expect($settings->webhook_urls)->toBeNull(); - expect($settings->apprise_url)->toBeNull(); - }); -}); diff --git a/tests/Unit/WebhookNotificationListenersTest.php b/tests/Unit/WebhookNotificationListenersTest.php deleted file mode 100644 index a86c0c4ef..000000000 --- a/tests/Unit/WebhookNotificationListenersTest.php +++ /dev/null @@ -1,196 +0,0 @@ -delete(); - - $user = User::factory()->create(); - $result = Result::factory()->create(); - - // Enable webhook notifications in settings - $settings = new NotificationSettings; - $settings->webhook_enabled = true; - $settings->webhook_on_speedtest_run = true; - $settings->webhook_urls = [ - ['url' => 'https://webhook1.example.com'], - ['url' => 'https://webhook2.example.com'], - ]; - $settings->save(); - - $event = new SpeedtestCompleted($result); - $listener = new SendSpeedtestCompletedNotification; - - // Mock WebhookCall to avoid actual HTTP requests - $this->mock(WebhookCall::class, function ($mock) { - $mock->shouldReceive('create')->andReturnSelf(); - $mock->shouldReceive('url')->andReturnSelf(); - $mock->shouldReceive('payload')->andReturnSelf(); - $mock->shouldReceive('doNotSign')->andReturnSelf(); - $mock->shouldReceive('dispatch')->andReturnSelf(); - }); - - $listener->handle($event); - - // Verify that the settings are correctly configured - $savedSettings = new NotificationSettings; - expect($savedSettings->webhook_enabled)->toBeTrue(); - expect($savedSettings->webhook_on_speedtest_run)->toBeTrue(); - expect($savedSettings->webhook_urls)->toHaveCount(2); - expect($savedSettings->webhook_urls[0]['url'])->toBe('https://webhook1.example.com'); - expect($savedSettings->webhook_urls[1]['url'])->toBe('https://webhook2.example.com'); - }); - - test('SendSpeedtestCompletedNotification logic when disabled', function () { - // Clean users table to ensure only one user exists - \App\Models\User::query()->delete(); - - $user = User::factory()->create(); - $result = Result::factory()->create(); - - // Disable webhook notifications in settings - $settings = new NotificationSettings; - $settings->webhook_enabled = false; - $settings->save(); - - $event = new SpeedtestCompleted($result); - $listener = new SendSpeedtestCompletedNotification; - - // Mock WebhookCall to avoid actual HTTP requests - $this->mock(WebhookCall::class, function ($mock) { - $mock->shouldReceive('create')->never(); - }); - - $listener->handle($event); - - // Verify that the settings are correctly configured - $savedSettings = new NotificationSettings; - expect($savedSettings->webhook_enabled)->toBeFalse(); - }); - - test('SendSpeedtestCompletedNotification logic when no urls', function () { - // Clean users table to ensure only one user exists - \App\Models\User::query()->delete(); - - $user = User::factory()->create(); - $result = Result::factory()->create(); - - // Enable webhook notifications but with no urls - $settings = new NotificationSettings; - $settings->webhook_enabled = true; - $settings->webhook_on_speedtest_run = true; - $settings->webhook_urls = []; - $settings->save(); - - $event = new SpeedtestCompleted($result); - $listener = new SendSpeedtestCompletedNotification; - - // Mock Log facade to capture the warning - Log::shouldReceive('warning')->with('Webhook urls not found, check webhook notification channel settings.')->once(); - - $listener->handle($event); - - // Verify that the settings are correctly configured - $savedSettings = new NotificationSettings; - expect($savedSettings->webhook_enabled)->toBeTrue(); - expect($savedSettings->webhook_on_speedtest_run)->toBeTrue(); - expect($savedSettings->webhook_urls)->toHaveCount(0); - }); - - test('SendSpeedtestThresholdNotification logic when threshold is breached', function () { - // Clean users table to ensure only one user exists - \App\Models\User::query()->delete(); - - $user = User::factory()->create(); - $result = Result::factory()->create([ - 'ping' => 100, // High ping that will breach threshold - 'download' => 104857.6, // 0.1 MB in bytes - 'upload' => 52428.8, // 0.05 MB in bytes - ]); - - // Enable webhook threshold notifications in settings - $settings = new NotificationSettings; - $settings->webhook_enabled = true; - $settings->webhook_on_threshold_failure = true; - $settings->webhook_urls = [['url' => 'https://webhook.example.com']]; - $settings->save(); - - // Set threshold settings - $thresholdSettings = new ThresholdSettings; - $thresholdSettings->absolute_enabled = true; - $thresholdSettings->absolute_ping = 50; // Threshold lower than result (100 > 50) - $thresholdSettings->absolute_download = 1; // Threshold higher than result (0.8 < 1) - $thresholdSettings->absolute_upload = 1; // Threshold higher than result (0.4 < 1) - $thresholdSettings->save(); - - $event = new SpeedtestCompleted($result); - $listener = new SendSpeedtestThresholdNotification; - - // Test that the threshold functions work correctly - expect(absolutePingThresholdFailed(50, 100))->toBeTrue(); // 100 > 50 - expect(absoluteDownloadThresholdFailed(1, 104857.6))->toBeTrue(); // 0.8 < 1 - expect(absoluteUploadThresholdFailed(1, 52428.8))->toBeTrue(); // 0.4 < 1 - - // Verify that the settings are correctly configured - $savedSettings = new NotificationSettings; - $savedThresholdSettings = new ThresholdSettings; - expect($savedSettings->webhook_enabled)->toBeTrue(); - expect($savedSettings->webhook_on_threshold_failure)->toBeTrue(); - expect($savedThresholdSettings->absolute_enabled)->toBeTrue(); - expect($savedThresholdSettings->absolute_ping)->toBe(50.0); - expect($savedThresholdSettings->absolute_download)->toBe(1.0); - expect($savedThresholdSettings->absolute_upload)->toBe(1.0); - }); - - test('SendSpeedtestThresholdNotification logic when thresholds not breached', function () { - // Clean users table to ensure only one user exists - \App\Models\User::query()->delete(); - - $user = User::factory()->create(); - $result = Result::factory()->create([ - 'ping' => 20, // Low ping, should not breach threshold - 'download' => 104857600, // 100 MB in bytes - 'upload' => 52428800, // 50 MB in bytes - ]); - - // Enable webhook threshold notifications in settings - $settings = new NotificationSettings; - $settings->webhook_enabled = true; - $settings->webhook_on_threshold_failure = true; - $settings->webhook_urls = [['url' => 'https://webhook.example.com']]; - $settings->save(); - - // Set threshold settings - $thresholdSettings = new ThresholdSettings; - $thresholdSettings->absolute_enabled = true; - $thresholdSettings->absolute_ping = 50; // Threshold higher than result (20 < 50) - $thresholdSettings->absolute_download = 1; // Threshold lower than result (800 > 1) - $thresholdSettings->absolute_upload = 1; // Threshold lower than result (400 > 1) - $thresholdSettings->save(); - - $event = new SpeedtestCompleted($result); - $listener = new SendSpeedtestThresholdNotification; - - // Test that the threshold functions work correctly - expect(absolutePingThresholdFailed(50, 20))->toBeFalse(); // 20 < 50 - expect(absoluteDownloadThresholdFailed(1, 104857600))->toBeFalse(); // 800 > 1 - expect(absoluteUploadThresholdFailed(1, 52428800))->toBeFalse(); // 400 > 1 - - // Verify that the settings are correctly configured - $savedSettings = new NotificationSettings; - $savedThresholdSettings = new ThresholdSettings; - expect($savedSettings->webhook_enabled)->toBeTrue(); - expect($savedSettings->webhook_on_threshold_failure)->toBeTrue(); - expect($savedThresholdSettings->absolute_enabled)->toBeTrue(); - }); -}); From bb81c3d1883b52702e22a2325d9d1173d9a0b596 Mon Sep 17 00:00:00 2001 From: Sven van Ginkel Date: Mon, 3 Nov 2025 15:47:36 +0100 Subject: [PATCH 30/31] Update with branch --- .../SendAppriseTestNotification.php | 54 ++----- .../Pages/Settings/NotificationPage.php | 19 --- .../SendSpeedtestCompletedNotification.php | 50 ------- .../SendSpeedtestThresholdNotification.php | 135 ----------------- .../SendSpeedtestCompletedNotification.php | 54 +++++++ .../SendSpeedtestThresholdNotification.php | 139 ++++++++++++++++++ app/Listeners/SpeedtestEventSubscriber.php | 25 ---- app/Notifications/Apprise/AppriseMessage.php | 66 +++++++++ .../Apprise/SpeedtestNotification.php | 40 +++++ .../Apprise/TestNotification.php | 34 +++++ app/Notifications/AppriseChannel.php | 33 +++-- app/Providers/AppServiceProvider.php | 16 ++ app/Settings/NotificationSettings.php | 2 - ..._31_164343_create_apprise_notification.php | 1 - 14 files changed, 384 insertions(+), 284 deletions(-) delete mode 100644 app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php delete mode 100644 app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php create mode 100644 app/Listeners/Apprise/SendSpeedtestCompletedNotification.php create mode 100644 app/Listeners/Apprise/SendSpeedtestThresholdNotification.php create mode 100644 app/Notifications/Apprise/AppriseMessage.php create mode 100644 app/Notifications/Apprise/SpeedtestNotification.php create mode 100644 app/Notifications/Apprise/TestNotification.php diff --git a/app/Actions/Notifications/SendAppriseTestNotification.php b/app/Actions/Notifications/SendAppriseTestNotification.php index f4c6b6a92..062202b06 100644 --- a/app/Actions/Notifications/SendAppriseTestNotification.php +++ b/app/Actions/Notifications/SendAppriseTestNotification.php @@ -2,20 +2,20 @@ namespace App\Actions\Notifications; +use App\Notifications\Apprise\TestNotification; use Filament\Notifications\Notification; -use Illuminate\Support\Facades\Http; -use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Notification as FacadesNotification; use Lorisleiva\Actions\Concerns\AsAction; class SendAppriseTestNotification { use AsAction; - public function handle(string $apprise_url, bool $apprise_verify_ssl, array $channel_urls) + public function handle(array $channel_urls) { - if (! $apprise_url) { + if (! count($channel_urls)) { Notification::make() - ->title('You need to configure an Apprise URL!') + ->title('You need to add Apprise channel URLs!') ->warning() ->send(); @@ -23,47 +23,23 @@ public function handle(string $apprise_url, bool $apprise_verify_ssl, array $cha } foreach ($channel_urls as $row) { - $serviceUrl = $row['channel_url'] ?? null; - if (! $serviceUrl) { + $channelUrl = $row['channel_url'] ?? null; + if (! $channelUrl) { Notification::make() - ->title('Skipping missing Service URL!') + ->title('Skipping missing channel URL!') ->warning() ->send(); continue; } - $payload = [ - 'body' => 'πŸ‘‹ Testing Apprise channel.', - 'urls' => $serviceUrl, - ]; - - try { - $request = Http::withHeaders([ - 'Content-Type' => 'application/json', - ]); - - if (! $apprise_verify_ssl) { - $request = $request->withoutVerifying(); - } - - $request - ->post(rtrim($apprise_url, '/'), $payload) - ->throw(); - - Notification::make() - ->title('Apprise notification sent successfully.') - ->success() - ->send(); - } catch (\Throwable $e) { - Log::error('Apprise notification failed for service URL '.$serviceUrl.': '.$e->getMessage()); - - Notification::make() - ->title('Failed to send Apprise notification.') - ->warning() - ->body('An error occurred. Please check the logs for details.') - ->send(); - } + FacadesNotification::route('apprise_urls', $channelUrl) + ->notify(new TestNotification); } + + Notification::make() + ->title('Test Apprise notification sent.') + ->success() + ->send(); } } diff --git a/app/Filament/Pages/Settings/NotificationPage.php b/app/Filament/Pages/Settings/NotificationPage.php index 52727f2bc..75b50c3c9 100755 --- a/app/Filament/Pages/Settings/NotificationPage.php +++ b/app/Filament/Pages/Settings/NotificationPage.php @@ -24,7 +24,6 @@ use Filament\Forms\Components\Section; use Filament\Forms\Components\TextInput; use Filament\Forms\Components\Toggle; -use Filament\Forms\Components\View; use Filament\Forms\Form; use Filament\Pages\SettingsPage; use Illuminate\Support\Facades\Auth; @@ -124,14 +123,6 @@ public function form(Form $form): Form ]), Fieldset::make('Apprise Sidecar') ->schema([ - TextInput::make('apprise_url') - ->label('URL') - ->placeholder('http://apprise:8000/notify') - ->helperText('Specify the URL of your Apprise instance.') - ->maxLength(2000) - ->required() - ->url() - ->columnSpanFull(), Checkbox::make('apprise_verify_ssl') ->label('Verify SSL') ->default(true) @@ -154,8 +145,6 @@ public function form(Form $form): Form Action::make('test apprise') ->label('Test Apprise') ->action(fn (Forms\Get $get) => SendAppriseTestNotification::run( - apprise_url: $get('apprise_url'), - apprise_verify_ssl: $get('apprise_verify_ssl'), channel_urls: $get('apprise_channel_urls'), )) ->hidden(fn (Forms\Get $get) => ! count($get('apprise_channel_urls'))), @@ -614,14 +603,6 @@ public function form(Form $form): Form ->columnSpan([ 'md' => 2, ]), - - Section::make() - ->schema([ - View::make('filament.forms.notifications-helptext'), - ]) - ->columnSpan([ - 'md' => 1, - ]), ]), ]); } diff --git a/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php b/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php deleted file mode 100644 index 4e447a4de..000000000 --- a/app/Jobs/Notifications/Apprise/SendSpeedtestCompletedNotification.php +++ /dev/null @@ -1,50 +0,0 @@ -result = $result; - } - - public function handle(): void - { - $settings = app(NotificationSettings::class); - - // If apprise_channel_urls is empty or not an array, skip - if (empty($settings->apprise_channel_urls) || ! is_array($settings->apprise_channel_urls)) { - Log::warning('Apprise service URLs not found; check Apprise notification settings.'); - - return; - } - - // Build the completed‐speedtest payload - $data = SpeedtestNotificationData::make($this->result); - - $payloadBody = view('apprise.speedtest-completed', $data)->render(); - - $payload = [ - 'body' => $payloadBody, - 'title' => 'Speedtest Completed – #'.$this->result->id, - 'type' => 'info', - ]; - - // Send it! - AppriseService::send($payload); - } -} diff --git a/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php b/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php deleted file mode 100644 index 351f4074c..000000000 --- a/app/Jobs/Notifications/Apprise/SendSpeedtestThresholdNotification.php +++ /dev/null @@ -1,135 +0,0 @@ -result = $result; - } - - /** - * Handle the event. - */ - public function handle(): void - { - $settings = app(NotificationSettings::class); - - // If apprise_channel_urls is empty or not an array, skip - if (empty($settings->apprise_channel_urls) || ! is_array($settings->apprise_channel_urls)) { - Log::warning('Apprise service URLs not found; check Apprise notification settings.'); - - return; - } - - $thresholdSettings = app(ThresholdSettings::class); - - if (! $thresholdSettings->absolute_enabled) { - - return; - } - - $failed = []; - - if ($thresholdSettings->absolute_download > 0) { - array_push($failed, $this->absoluteDownloadThreshold($thresholdSettings)); - } - - if ($thresholdSettings->absolute_upload > 0) { - array_push($failed, $this->absoluteUploadThreshold($thresholdSettings)); - } - - if ($thresholdSettings->absolute_ping > 0) { - array_push($failed, $this->absolutePingThreshold($thresholdSettings)); - } - - $failed = array_filter($failed); - - if (! count($failed)) { - Log::warning('Failed apprise thresholds not found, won\'t send notification.'); - - return; - } - - $payloadBody = view('apprise.speedtest-threshold', [ - 'id' => $this->result->id, - 'service' => Str::title($this->result->service->getLabel()), - 'serverName' => $this->result->server_name, - 'serverId' => $this->result->server_id, - 'isp' => $this->result->isp, - 'metrics' => $failed, - 'speedtest_url' => $this->result->result_url, - 'url' => url('/admin/results'), - ])->render(); - - $payload = [ - 'body' => $payloadBody, - 'title' => 'Speedtest Threshold Breach – #'.$this->result->id, - 'type' => 'info', - ]; - - // Send it! - AppriseService::send($payload); - } - - protected function absoluteDownloadThreshold(ThresholdSettings $thresholdSettings): bool|array - { - if (! absoluteDownloadThresholdFailed($thresholdSettings->absolute_download, $this->result->download)) { - - return false; - } - - return [ - 'name' => 'Download', - 'threshold' => $thresholdSettings->absolute_download.' Mbps', - 'value' => Number::toBitRate(bits: $this->result->download_bits, precision: 2), - ]; - } - - protected function absoluteUploadThreshold(ThresholdSettings $thresholdSettings): bool|array - { - if (! absoluteUploadThresholdFailed($thresholdSettings->absolute_upload, $this->result->upload)) { - - return false; - } - - return [ - 'name' => 'Upload', - 'threshold' => $thresholdSettings->absolute_upload.' Mbps', - 'value' => Number::toBitRate(bits: $this->result->upload_bits, precision: 2), - ]; - } - - protected function absolutePingThreshold(ThresholdSettings $thresholdSettings): bool|array - { - if (! absolutePingThresholdFailed($thresholdSettings->absolute_ping, $this->result->ping)) { - - return false; - } - - return [ - 'name' => 'Ping', - 'threshold' => $thresholdSettings->absolute_ping.' ms', - 'value' => round($this->result->ping, 2).' ms', - ]; - } -} diff --git a/app/Listeners/Apprise/SendSpeedtestCompletedNotification.php b/app/Listeners/Apprise/SendSpeedtestCompletedNotification.php new file mode 100644 index 000000000..ca10b4e90 --- /dev/null +++ b/app/Listeners/Apprise/SendSpeedtestCompletedNotification.php @@ -0,0 +1,54 @@ +apprise_enabled) { + return; + } + + if (! $notificationSettings->apprise_on_speedtest_run) { + return; + } + + if (empty($notificationSettings->apprise_channel_urls) || ! is_array($notificationSettings->apprise_channel_urls)) { + Log::warning('Apprise service URLs not found; check Apprise notification settings.'); + + return; + } + + // Build the speedtest data + $data = SpeedtestNotificationData::make($event->result); + + $body = view('apprise.speedtest-completed', $data)->render(); + $title = 'Speedtest Completed – #'.$event->result->id; + + // Send notification to each configured channel URL + foreach ($notificationSettings->apprise_channel_urls as $row) { + $channelUrl = $row['channel_url'] ?? null; + if (! $channelUrl) { + Log::warning('Skipping entry with missing channel_url.'); + + continue; + } + + Notification::route('apprise_urls', $channelUrl) + ->notify(new SpeedtestNotification($title, $body, 'info')); + } + } +} diff --git a/app/Listeners/Apprise/SendSpeedtestThresholdNotification.php b/app/Listeners/Apprise/SendSpeedtestThresholdNotification.php new file mode 100644 index 000000000..ca1600486 --- /dev/null +++ b/app/Listeners/Apprise/SendSpeedtestThresholdNotification.php @@ -0,0 +1,139 @@ +apprise_enabled) { + return; + } + + if (! $notificationSettings->apprise_on_threshold_failure) { + return; + } + + if (empty($notificationSettings->apprise_channel_urls) || ! is_array($notificationSettings->apprise_channel_urls)) { + Log::warning('Apprise service URLs not found; check Apprise notification settings.'); + + return; + } + + $thresholdSettings = new ThresholdSettings; + + if (! $thresholdSettings->absolute_enabled) { + return; + } + + $failed = []; + + if ($thresholdSettings->absolute_download > 0) { + array_push($failed, $this->absoluteDownloadThreshold(event: $event, thresholdSettings: $thresholdSettings)); + } + + if ($thresholdSettings->absolute_upload > 0) { + array_push($failed, $this->absoluteUploadThreshold(event: $event, thresholdSettings: $thresholdSettings)); + } + + if ($thresholdSettings->absolute_ping > 0) { + array_push($failed, $this->absolutePingThreshold(event: $event, thresholdSettings: $thresholdSettings)); + } + + $failed = array_filter($failed); + + if (! count($failed)) { + Log::warning('Failed Apprise thresholds not found, won\'t send notification.'); + + return; + } + + $body = view('apprise.speedtest-threshold', [ + 'id' => $event->result->id, + 'service' => Str::title($event->result->service->getLabel()), + 'serverName' => $event->result->server_name, + 'serverId' => $event->result->server_id, + 'isp' => $event->result->isp, + 'metrics' => $failed, + 'speedtest_url' => $event->result->result_url, + 'url' => url('/admin/results'), + ])->render(); + + $title = 'Speedtest Threshold Breach – #'.$event->result->id; + + // Send notification to each configured channel URL + foreach ($notificationSettings->apprise_channel_urls as $row) { + $channelUrl = $row['channel_url'] ?? null; + if (! $channelUrl) { + Log::warning('Skipping entry with missing channel_url.'); + + continue; + } + + Notification::route('apprise_urls', $channelUrl) + ->notify(new SpeedtestNotification($title, $body, 'warning')); + } + } + + /** + * Build Apprise notification if absolute download threshold is breached. + */ + protected function absoluteDownloadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array + { + if (! absoluteDownloadThresholdFailed($thresholdSettings->absolute_download, $event->result->download)) { + return false; + } + + return [ + 'name' => 'Download', + 'threshold' => $thresholdSettings->absolute_download.' Mbps', + 'value' => Number::toBitRate(bits: $event->result->download_bits, precision: 2), + ]; + } + + /** + * Build Apprise notification if absolute upload threshold is breached. + */ + protected function absoluteUploadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array + { + if (! absoluteUploadThresholdFailed($thresholdSettings->absolute_upload, $event->result->upload)) { + return false; + } + + return [ + 'name' => 'Upload', + 'threshold' => $thresholdSettings->absolute_upload.' Mbps', + 'value' => Number::toBitRate(bits: $event->result->upload_bits, precision: 2), + ]; + } + + /** + * Build Apprise notification if absolute ping threshold is breached. + */ + protected function absolutePingThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array + { + if (! absolutePingThresholdFailed($thresholdSettings->absolute_ping, $event->result->ping)) { + return false; + } + + return [ + 'name' => 'Ping', + 'threshold' => $thresholdSettings->absolute_ping.' ms', + 'value' => round($event->result->ping, 2).' ms', + ]; + } +} diff --git a/app/Listeners/SpeedtestEventSubscriber.php b/app/Listeners/SpeedtestEventSubscriber.php index e3f6bdacb..4db7c2fa5 100644 --- a/app/Listeners/SpeedtestEventSubscriber.php +++ b/app/Listeners/SpeedtestEventSubscriber.php @@ -2,14 +2,10 @@ namespace App\Listeners; -use App\Events\SpeedtestBenchmarkFailed; use App\Events\SpeedtestCompleted; use App\Events\SpeedtestFailed; use App\Jobs\Influxdb\v2\WriteResult; -use App\Jobs\Notifications\Apprise\SendSpeedtestCompletedNotification as AppriseCompleted; -use App\Jobs\Notifications\Apprise\SendSpeedtestThresholdNotification as AppriseThresholds; use App\Settings\DataIntegrationSettings; -use App\Settings\NotificationSettings; use Illuminate\Events\Dispatcher; class SpeedtestEventSubscriber @@ -37,27 +33,6 @@ public function handleSpeedtestCompleted(SpeedtestCompleted $event): void if ($settings->influxdb_v2_enabled) { WriteResult::dispatch($event->result); } - - $notificationSettings = app(NotificationSettings::class); - - // Apprise notifications - if ($notificationSettings->apprise_enabled) { - if ($notificationSettings->apprise_on_speedtest_run) { - AppriseCompleted::dispatch($event->result); - } - } - } - - public function handleSpeedtestBenchmarkFailed(SpeedtestBenchmarkFailed $event): void - { - $notificationSettings = app(NotificationSettings::class); - - // Apprise notifications - if ($notificationSettings->apprise_enabled) { - if ($notificationSettings->apprise_on_threshold_failure) { - AppriseThresholds::dispatch($event->result); - } - } } /** diff --git a/app/Notifications/Apprise/AppriseMessage.php b/app/Notifications/Apprise/AppriseMessage.php new file mode 100644 index 000000000..a510ded7b --- /dev/null +++ b/app/Notifications/Apprise/AppriseMessage.php @@ -0,0 +1,66 @@ +urls = $urls; + + return $this; + } + + public function title(string $title): self + { + $this->title = $title; + + return $this; + } + + public function body(string $body): self + { + $this->body = $body; + + return $this; + } + + public function type(string $type): self + { + $this->type = $type; + + return $this; + } + + public function format(string $format): self + { + $this->format = $format; + + return $this; + } + + public function tag(string $tag): self + { + $this->tag = $tag; + + return $this; + } +} diff --git a/app/Notifications/Apprise/SpeedtestNotification.php b/app/Notifications/Apprise/SpeedtestNotification.php new file mode 100644 index 000000000..3c2ffb3cd --- /dev/null +++ b/app/Notifications/Apprise/SpeedtestNotification.php @@ -0,0 +1,40 @@ + + */ + public function via(object $notifiable): array + { + return ['apprise']; + } + + /** + * Get the Apprise message representation of the notification. + */ + public function toApprise(object $notifiable): AppriseMessage + { + return AppriseMessage::create() + ->urls($notifiable->routes['apprise_urls']) + ->title($this->title) + ->body($this->body) + ->type($this->type); + } +} diff --git a/app/Notifications/Apprise/TestNotification.php b/app/Notifications/Apprise/TestNotification.php new file mode 100644 index 000000000..f07810fcc --- /dev/null +++ b/app/Notifications/Apprise/TestNotification.php @@ -0,0 +1,34 @@ + + */ + public function via(object $notifiable): array + { + return ['apprise']; + } + + /** + * Get the Apprise message representation of the notification. + */ + public function toApprise(object $notifiable): AppriseMessage + { + return AppriseMessage::create() + ->urls($notifiable->routes['apprise_urls']) + ->title('Test Notification') + ->body('πŸ‘‹ Testing the Apprise notification channel.') + ->type('info'); + } +} diff --git a/app/Notifications/AppriseChannel.php b/app/Notifications/AppriseChannel.php index af5ac3683..759ea9586 100644 --- a/app/Notifications/AppriseChannel.php +++ b/app/Notifications/AppriseChannel.php @@ -2,6 +2,7 @@ namespace App\Notifications; +use App\Settings\NotificationSettings; use Illuminate\Notifications\Notification; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; @@ -20,30 +21,36 @@ public function send(object $notifiable, Notification $notification): void return; } - $appriseUrl = config('services.apprise.url'); + $appriseUrl = rtrim(config('services.apprise.url'), '/'); + $settings = app(NotificationSettings::class); try { - $response = Http::timeout(5) + $request = Http::timeout(5) ->withHeaders([ 'Content-Type' => 'application/json', - ]) - // ->when(true, function ($http) { - // $http->withoutVerifying(); - // }) - ->post("{$appriseUrl}/notify", [ - 'urls' => $message->urls, - 'title' => $message->title, - 'body' => $message->body, - 'type' => $message->type ?? 'info', - 'format' => $message->format ?? 'text', - 'tag' => $message->tag ?? null, ]); + // If SSL verification is disabled in settings, skip it + if (! $settings->apprise_verify_ssl) { + $request = $request->withoutVerifying(); + } + + $response = $request->post("{$appriseUrl}/notify", [ + 'urls' => $message->urls, + 'title' => $message->title, + 'body' => $message->body, + 'type' => $message->type ?? 'info', + 'format' => $message->format ?? 'text', + 'tag' => $message->tag ?? null, + ]); + if ($response->failed()) { Log::error('Apprise notification failed', [ 'status' => $response->status(), 'body' => $response->body(), ]); + } else { + Log::info("Apprise notification sent β†’ instance: {$appriseUrl}"); } } catch (\Exception $e) { Log::error('Apprise notification exception', [ diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index e4ea6209f..ba434c79e 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -4,11 +4,14 @@ use App\Enums\UserRole; use App\Models\User; +use App\Notifications\AppriseChannel; use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Foundation\Console\AboutCommand; use Illuminate\Http\Request; +use Illuminate\Notifications\ChannelManager; use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Gate; +use Illuminate\Support\Facades\Notification; use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Facades\URL; use Illuminate\Support\ServiceProvider; @@ -44,12 +47,25 @@ public function boot(): void $this->defineGates(); $this->forceHttps(); $this->setApiRateLimit(); + $this->registerNotificationChannels(); AboutCommand::add('Speedtest Tracker', fn () => [ 'Version' => config('speedtest.build_version'), ]); } + /** + * Register custom notification channels. + */ + protected function registerNotificationChannels(): void + { + Notification::resolved(function (ChannelManager $service) { + $service->extend('apprise', function ($app) { + return new AppriseChannel; + }); + }); + } + /** * Define custom if statements, these were added to make the blade templates more readable. * diff --git a/app/Settings/NotificationSettings.php b/app/Settings/NotificationSettings.php index e26e56f44..08e58784d 100644 --- a/app/Settings/NotificationSettings.php +++ b/app/Settings/NotificationSettings.php @@ -92,8 +92,6 @@ class NotificationSettings extends Settings public bool $apprise_on_threshold_failure; - public ?string $apprise_url; - public bool $apprise_verify_ssl; public ?array $apprise_channel_urls; diff --git a/database/settings/2024_12_31_164343_create_apprise_notification.php b/database/settings/2024_12_31_164343_create_apprise_notification.php index bfad7131e..8bd003475 100644 --- a/database/settings/2024_12_31_164343_create_apprise_notification.php +++ b/database/settings/2024_12_31_164343_create_apprise_notification.php @@ -9,7 +9,6 @@ public function up(): void $this->migrator->add('notification.apprise_enabled', false); $this->migrator->add('notification.apprise_on_speedtest_run', false); $this->migrator->add('notification.apprise_on_threshold_failure', false); - $this->migrator->add('notification.apprise_url', null); $this->migrator->add('notification.apprise_verify_ssl', true); $this->migrator->add('notification.apprise_channel_urls', null); } From 709f08ff87b5f1c0b6f2cbe534521718916ff442 Mon Sep 17 00:00:00 2001 From: svenvg93 Date: Mon, 3 Nov 2025 20:59:11 +0100 Subject: [PATCH 31/31] Revert Jitter latency changes --- app/Models/Traits/ResultDataAttributes.php | 4 ++-- app/Services/Notifications/SpeedtestNotificationData.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Models/Traits/ResultDataAttributes.php b/app/Models/Traits/ResultDataAttributes.php index 37cdcaeeb..0e38bc2af 100644 --- a/app/Models/Traits/ResultDataAttributes.php +++ b/app/Models/Traits/ResultDataAttributes.php @@ -21,7 +21,7 @@ protected function downloadBits(): Attribute /** * Get the result's download jitter in milliseconds. */ - protected function downloadlatencyJitter(): Attribute + protected function downloadJitter(): Attribute { return Attribute::make( get: fn () => Arr::get($this->data, 'download.latency.jitter'), @@ -171,7 +171,7 @@ protected function uploadBits(): Attribute /** * Get the result's upload jitter in milliseconds. */ - protected function uploadlatencyjitter(): Attribute + protected function uploadJitter(): Attribute { return Attribute::make( get: fn () => Arr::get($this->data, 'upload.latency.jitter'), diff --git a/app/Services/Notifications/SpeedtestNotificationData.php b/app/Services/Notifications/SpeedtestNotificationData.php index 292c1b610..aa4441a11 100644 --- a/app/Services/Notifications/SpeedtestNotificationData.php +++ b/app/Services/Notifications/SpeedtestNotificationData.php @@ -27,12 +27,12 @@ public static function make(Result $result, array $failed = []): array 'downloadLatencyIqm' => $result->download_latency_iqm.' ms', 'downloadLatencyLow' => $result->download_latency_low.' ms', 'downloadLatencyHigh' => $result->download_latency_high.' ms', - 'downloadLatencyJitter' => $result->download_latency_jitter.' ms', + 'downloadJitter' => $result->download_latency_jitter.' ms', 'uploadBytes' => $result->upload_bytes, 'uploadLatencyIqm' => $result->upload_latency_iqm.' ms', 'uploadLatencyLow' => $result->upload_latency_low.' ms', 'uploadLatencyHigh' => $result->upload_latency_high.' ms', - 'uploadLatencyJitter' => $result->upload_latency_jitter.' ms', + 'uploadJitter' => $result->upload_latency_jitter.' ms', 'externalIp' => $result->ip_address, 'serverHost' => $result->server_host, 'serverPort' => $result->server_port,