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 @@
+
+{{ config('app.name') }}
+
+{{ config('app.name') }}
+