diff --git a/app/Filament/Pages/Settings/NotificationPage.php b/app/Filament/Pages/Settings/NotificationPage.php index 325b04996..5dfef8e17 100644 --- a/app/Filament/Pages/Settings/NotificationPage.php +++ b/app/Filament/Pages/Settings/NotificationPage.php @@ -3,16 +3,21 @@ namespace App\Filament\Pages\Settings; use App\Forms\Components\TestDatabaseNotification; +use App\Forms\Components\TestMailNotification; +use App\Mail\Test; use App\Settings\NotificationSettings; use Closure; use Filament\Forms\Components\Card; use Filament\Forms\Components\Fieldset; 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\Notifications\Notification; use Filament\Pages\SettingsPage; +use Illuminate\Support\Facades\Mail; class NotificationPage extends SettingsPage { @@ -69,6 +74,45 @@ protected function getFormSchema(): array 'default' => 1, 'md' => 2, ]), + + Section::make('Mail') + ->schema([ + Toggle::make('mail_enabled') + ->label('Enable mail notifications') + ->reactive() + ->columnSpan(2), + Grid::make([ + 'default' => 1, + ]) + ->hidden(fn (Closure $get) => $get('mail_enabled') !== true) + ->schema([ + Fieldset::make('Triggers') + ->schema([ + Toggle::make('mail_on_speedtest_run') + ->label('Notify on every speetest run') + ->columnSpan(2), + Toggle::make('mail_on_threshold_failure') + ->label('Notify on threshold failures') + ->columnSpan(2), + ]), + ]), + Repeater::make('mail_recipients') + ->label('Recipients') + ->schema([ + TextInput::make('email_address') + ->email() + ->required(fn (Closure $get) => $get('mail_enabled') == true), + ]) + ->hidden(fn (Closure $get) => $get('mail_enabled') !== true) + ->columnSpan(['md' => 2]), + TestMailNotification::make('test channel') + ->hidden(fn (Closure $get) => $get('mail_enabled') !== true), + ]) + ->compact() + ->columns([ + 'default' => 1, + 'md' => 2, + ]), ]) ->columnSpan([ 'md' => 2, @@ -103,4 +147,19 @@ public function sendTestDatabaseNotification() ->success() ->send(); } + + public function sendTestMailNotification() + { + $notificationSettings = new (NotificationSettings::class); + + foreach ($notificationSettings->mail_recipients as $recipient) { + Mail::to($recipient) + ->send(new Test()); + } + + Notification::make() + ->title('Test mail notification sent.') + ->success() + ->send(); + } } diff --git a/app/Forms/Components/TestMailNotification.php b/app/Forms/Components/TestMailNotification.php new file mode 100644 index 000000000..a8f83fd52 --- /dev/null +++ b/app/Forms/Components/TestMailNotification.php @@ -0,0 +1,10 @@ +notificationSettings->database_enabled) { - return; + if ($this->notificationSettings->database_enabled) { + if ($this->notificationSettings->database_on_speedtest_run) { + Notification::make() + ->title('Speedtest completed') + ->success() + ->sendToDatabase($event->user); + } } - if ($this->notificationSettings->database_on_speedtest_run) { - Notification::make() - ->title('Speedtest completed') - ->success() - ->sendToDatabase($event->user); + if ($this->notificationSettings->mail_enabled) { + if ($this->notificationSettings->mail_on_speedtest_run && count($this->notificationSettings->mail_recipients)) { + foreach ($this->notificationSettings->mail_recipients as $recipient) { + Mail::to($recipient) + ->send(new SpeedtestCompletedMail($event->result)); + } + } } } } diff --git a/app/Listeners/Threshold/AbsoluteDownloadListener.php b/app/Listeners/Threshold/AbsoluteDownloadListener.php deleted file mode 100644 index 88acf3191..000000000 --- a/app/Listeners/Threshold/AbsoluteDownloadListener.php +++ /dev/null @@ -1,49 +0,0 @@ -notificationSettings = new (NotificationSettings::class); - - $this->thresholdSettings = new (ThresholdSettings::class); - } - - /** - * Handle the event. - * - * @param \App\Events\ResultCreated $event - * @return void - */ - public function handle(ResultCreated $event) - { - if (! $this->thresholdSettings->absolute_enabled && ! $this->thresholdSettings->absolute_download) { - return; - } - - if (formatBits(formatBytesToBits($event->result->download), 2, false) < $this->thresholdSettings->absolute_download) { - Notification::make() - ->title('Threshold breached') - ->body('Speedtest #'.$event->result->id.' breached the download threshold of '.$this->thresholdSettings->absolute_download.'Mbps at '.formatBits(formatBytesToBits($event->result->download), 2, false).'Mbps.') - ->warning() - ->sendToDatabase($event->user); - } - } -} diff --git a/app/Listeners/Threshold/AbsoluteListener.php b/app/Listeners/Threshold/AbsoluteListener.php new file mode 100644 index 000000000..95cef7f4f --- /dev/null +++ b/app/Listeners/Threshold/AbsoluteListener.php @@ -0,0 +1,153 @@ +notificationSettings = new (NotificationSettings::class); + + $this->thresholdSettings = new (ThresholdSettings::class); + } + + /** + * Handle the event. + * + * @param \App\Events\ResultCreated $event + * @return void + */ + public function handle(ResultCreated $event) + { + if ($this->thresholdSettings->absolute_enabled !== true) { + Log::info('Absolute threshold notifications disabled.'); + + return; + } + + // Database notification channel + if ($this->notificationSettings->database_enabled == true && $this->notificationSettings->database_on_threshold_failure == true) { + $this->databaseChannel($event); + } + + // Mail notification channel + if ($this->notificationSettings->mail_enabled == true && $this->notificationSettings->mail_on_threshold_failure == true) { + $this->mailChannel($event); + } + } + + /** + * Handle database notifications. + * + * @param \App\Events\ResultCreated $event + * @return void + */ + protected function databaseChannel(ResultCreated $event) + { + // Download threshold + if ($this->thresholdSettings->absolute_download > 0) { + if (absoluteDownloadThresholdFailed($this->thresholdSettings->absolute_download, $event->result->download)) { + Notification::make() + ->title('Threshold breached') + ->body('Speedtest #'.$event->result->id.' breached the download threshold of '.$this->thresholdSettings->absolute_download.'Mbps at '.formatBits(formatBytesToBits($event->result->download), 2, false).'Mbps.') + ->warning() + ->sendToDatabase($event->user); + } + } + + // Upload threshold + if ($this->thresholdSettings->absolute_upload > 0) { + if (absoluteUploadThresholdFailed($this->thresholdSettings->absolute_upload, $event->result->upload)) { + Notification::make() + ->title('Threshold breached') + ->body('Speedtest #'.$event->result->id.' breached the upload threshold of '.$this->thresholdSettings->absolute_upload.'Mbps at '.formatBits(formatBytesToBits($event->result->upload), 2, false).'Mbps.') + ->warning() + ->sendToDatabase($event->user); + } + } + + // Ping threshold + if ($this->thresholdSettings->absolute_ping > 0) { + if (absolutePingThresholdFailed($this->thresholdSettings->absolute_ping, $event->result->ping)) { + Notification::make() + ->title('Threshold breached') + ->body('Speedtest #'.$event->result->id.' breached the ping threshold of '.$this->thresholdSettings->absolute_ping.'ms at '.$event->result->ping.'ms.') + ->warning() + ->sendToDatabase($event->user); + } + } + } + + /** + * Handle database notifications. + * + * @param \App\Events\ResultCreated $event + * @return void + */ + protected function mailChannel(ResultCreated $event) + { + $failedThresholds = []; + + if (! count($this->notificationSettings->mail_recipients) > 0) { + Log::info('Skipping sending mail notification, no recipients.'); + } + + // Download threshold + if ($this->thresholdSettings->absolute_download > 0) { + if (absoluteDownloadThresholdFailed($this->thresholdSettings->absolute_download, $event->result->download)) { + array_push($failedThresholds, [ + 'name' => 'Download', + 'threshold' => $this->thresholdSettings->absolute_download.' Mbps', + 'value' => formatBits(formatBytesToBits($event->result->download)).'ps', + ]); + } + } + + // Upload threshold + if ($this->thresholdSettings->absolute_upload > 0) { + if (absoluteUploadThresholdFailed($this->thresholdSettings->absolute_upload, $event->result->upload)) { + array_push($failedThresholds, [ + 'name' => 'Upload', + 'threshold' => $this->thresholdSettings->absolute_upload.' Mbps', + 'value' => formatBits(formatBytesToBits($event->result->upload)).'ps', + ]); + } + } + + // Ping threshold + if ($this->thresholdSettings->absolute_ping > 0) { + if (absolutePingThresholdFailed($this->thresholdSettings->absolute_ping, $event->result->ping)) { + array_push($failedThresholds, [ + 'name' => 'Ping', + 'threshold' => $this->thresholdSettings->absolute_ping.' Ms', + 'value' => round($event->result->ping, 2).' Ms', + ]); + } + } + + if (count($failedThresholds)) { + foreach ($this->notificationSettings->mail_recipients as $recipient) { + Mail::to($recipient) + ->send(new AbsoluteMail($event->result, $failedThresholds)); + } + } + } +} diff --git a/app/Listeners/Threshold/AbsolutePingListener.php b/app/Listeners/Threshold/AbsolutePingListener.php deleted file mode 100644 index 6ae66371f..000000000 --- a/app/Listeners/Threshold/AbsolutePingListener.php +++ /dev/null @@ -1,49 +0,0 @@ -notificationSettings = new (NotificationSettings::class); - - $this->thresholdSettings = new (ThresholdSettings::class); - } - - /** - * Handle the event. - * - * @param \App\Events\ResultCreated $event - * @return void - */ - public function handle(ResultCreated $event) - { - if (! $this->thresholdSettings->absolute_enabled && ! $this->thresholdSettings->absolute_ping) { - return; - } - - if ($event->result->ping > $this->thresholdSettings->absolute_ping) { - Notification::make() - ->title('Threshold breached') - ->body('Speedtest #'.$event->result->id.' breached the ping threshold of '.$this->thresholdSettings->absolute_ping.'ms at '.$event->result->ping.'ms.') - ->warning() - ->sendToDatabase($event->user); - } - } -} diff --git a/app/Listeners/Threshold/AbsoluteUploadListener.php b/app/Listeners/Threshold/AbsoluteUploadListener.php deleted file mode 100644 index 2705bed68..000000000 --- a/app/Listeners/Threshold/AbsoluteUploadListener.php +++ /dev/null @@ -1,49 +0,0 @@ -notificationSettings = new (NotificationSettings::class); - - $this->thresholdSettings = new (ThresholdSettings::class); - } - - /** - * Handle the event. - * - * @param \App\Events\ResultCreated $event - * @return void - */ - public function handle(ResultCreated $event) - { - if (! $this->thresholdSettings->absolute_enabled && ! $this->thresholdSettings->absolute_upload) { - return; - } - - if (formatBits(formatBytesToBits($event->result->upload), 2, false) < $this->thresholdSettings->absolute_upload) { - Notification::make() - ->title('Threshold breached') - ->body('Speedtest #'.$event->result->id.' breached the upload threshold of '.$this->thresholdSettings->absolute_upload.'Mbps at '.formatBits(formatBytesToBits($event->result->upload), 2, false).'Mbps.') - ->warning() - ->sendToDatabase($event->user); - } - } -} diff --git a/app/Mail/SpeedtestCompletedMail.php b/app/Mail/SpeedtestCompletedMail.php new file mode 100644 index 000000000..d21223167 --- /dev/null +++ b/app/Mail/SpeedtestCompletedMail.php @@ -0,0 +1,66 @@ +result = $result; + } + + /** + * Get the message envelope. + * + * @return \Illuminate\Mail\Mailables\Envelope + */ + public function envelope() + { + return new Envelope( + subject: 'Speedtest Result #'.$this->result->id.' - Completed', + ); + } + + /** + * Get the message content definition. + * + * @return \Illuminate\Mail\Mailables\Content + */ + public function content() + { + return new Content( + markdown: 'emails.speedtest-completed', + with: [ + 'id' => $this->result->id, + 'url' => url('/admin/results'), + ], + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments() + { + return []; + } +} diff --git a/app/Mail/Test.php b/app/Mail/Test.php new file mode 100644 index 000000000..7fabb940e --- /dev/null +++ b/app/Mail/Test.php @@ -0,0 +1,39 @@ +result = $result; + + $this->metrics = $metrics; + } + + /** + * Get the message envelope. + * + * @return \Illuminate\Mail\Mailables\Envelope + */ + public function envelope() + { + return new Envelope( + subject: 'Speedtest Result #'.$this->result->id.' - Absolute threshold failed', + ); + } + + /** + * Get the message content definition. + * + * @return \Illuminate\Mail\Mailables\Content + */ + public function content() + { + return new Content( + markdown: 'emails.threshold.absolute', + with: [ + 'id' => $this->result->id, + 'url' => url('/admin/results'), + 'metrics' => $this->metrics, + ], + ); + } +} diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 7774f28b8..d85b7b024 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -27,9 +27,7 @@ class EventServiceProvider extends ServiceProvider \App\Listeners\Data\InfluxDb2Listener::class, // Threashold listeners - \App\Listeners\Threshold\AbsoluteDownloadListener::class, - \App\Listeners\Threshold\AbsoluteUploadListener::class, - \App\Listeners\Threshold\AbsolutePingListener::class, + \App\Listeners\Threshold\AbsoluteListener::class, ], ]; diff --git a/app/Settings/NotificationSettings.php b/app/Settings/NotificationSettings.php index 1ef980246..cd2dd42ad 100644 --- a/app/Settings/NotificationSettings.php +++ b/app/Settings/NotificationSettings.php @@ -12,6 +12,14 @@ class NotificationSettings extends Settings public bool $database_on_threshold_failure; + public bool $mail_enabled; + + public bool $mail_on_speedtest_run; + + public bool $mail_on_threshold_failure; + + public ?array $mail_recipients; + public static function group(): string { return 'notification'; diff --git a/app/helpers.php b/app/helpers.php index 62330bfed..a019e1d7d 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -1,23 +1,5 @@ absolute_enabled) { - return true; - } - - return formatBits(formatBytesToBits($result->download), 2, false) > $thresholds->absolute_download - && formatBits(formatBytesToBits($result->upload), 2, false) > $thresholds->absolute_upload - && $result->ping < $thresholds->absolute_ping; - } -} - if (! function_exists('formatBits')) { function formatBits(int $bits, $precision = 2, $suffix = true) { @@ -62,3 +44,24 @@ function formatBytestoBits(int $bytes) return 0; } } + +if (! function_exists('absoluteDownloadThresholdFailed')) { + function absoluteDownloadThresholdFailed(float $threshold, float $download): bool + { + return formatBits(formatBytesToBits($download), 2, false) < $threshold; + } +} + +if (! function_exists('absoluteUploadThresholdFailed')) { + function absoluteUploadThresholdFailed(float $threshold, float $upload): bool + { + return formatBits(formatBytesToBits($upload), 2, false) < $threshold; + } +} + +if (! function_exists('absolutePingThresholdFailed')) { + function absolutePingThresholdFailed(float $threshold, float $ping): bool + { + return $ping > $threshold; + } +} diff --git a/database/settings/2022_11_11_134355_create_mail_notification_settings.php b/database/settings/2022_11_11_134355_create_mail_notification_settings.php new file mode 100644 index 000000000..8505e7bce --- /dev/null +++ b/database/settings/2022_11_11_134355_create_mail_notification_settings.php @@ -0,0 +1,14 @@ +migrator->add('notification.mail_enabled', false); + $this->migrator->add('notification.mail_on_speedtest_run', false); + $this->migrator->add('notification.mail_on_threshold_failure', false); + $this->migrator->add('notification.mail_recipients', null); + } +} diff --git a/resources/views/emails/speedtest-completed.blade.php b/resources/views/emails/speedtest-completed.blade.php new file mode 100644 index 000000000..b868d0d47 --- /dev/null +++ b/resources/views/emails/speedtest-completed.blade.php @@ -0,0 +1,12 @@ + +# Speedtest Result #{{ $id }} - Completed + +A speedtest was successfully run, click the button below to view the results. + + +View Results + + +Thanks,
+{{ config('app.name') }} +
diff --git a/resources/views/emails/test.blade.php b/resources/views/emails/test.blade.php new file mode 100644 index 000000000..70bf9cffa --- /dev/null +++ b/resources/views/emails/test.blade.php @@ -0,0 +1,3 @@ + +This is just a test of the email notification system. + diff --git a/resources/views/emails/threshold/absolute.blade.php b/resources/views/emails/threshold/absolute.blade.php new file mode 100644 index 000000000..de3fa5506 --- /dev/null +++ b/resources/views/emails/threshold/absolute.blade.php @@ -0,0 +1,18 @@ + +# Speedtest Result #{{ $id }} - Absolute Threshold Failed + + +| Name | Threshold | Value | +| ------------- |:-------------:| --------:| +@foreach ($metrics as $item) + | {{ $item['name'] }} | {{ $item['threshold'] }} | {{ $item['value'] }} | +@endforeach + + + +View Results + + +Thanks,
+{{ config('app.name') }} +
diff --git a/resources/views/forms/components/test-mail-notification.blade.php b/resources/views/forms/components/test-mail-notification.blade.php new file mode 100644 index 000000000..dba69b249 --- /dev/null +++ b/resources/views/forms/components/test-mail-notification.blade.php @@ -0,0 +1,5 @@ +
+ + Test mail channel + +