diff --git a/app/Actions/Notifications/SendGotifyTestNotification.php b/app/Actions/Notifications/SendGotifyTestNotification.php new file mode 100644 index 000000000..fdd6405de --- /dev/null +++ b/app/Actions/Notifications/SendGotifyTestNotification.php @@ -0,0 +1,37 @@ +title('You need to add Gotify urls!') + ->warning() + ->send(); + + return; + } + + foreach ($webhooks as $webhook) { + WebhookCall::create() + ->url($webhook['url']) + ->payload(['message' => '👋 Testing the Gotify notification channel.']) + ->doNotSign() + ->dispatch(); + } + + Notification::make() + ->title('Test Gotify notification sent.') + ->success() + ->send(); + } +} diff --git a/app/Actions/Notifications/SendHealthCheckTestNotification.php b/app/Actions/Notifications/SendHealthCheckTestNotification.php new file mode 100644 index 000000000..f37e20fad --- /dev/null +++ b/app/Actions/Notifications/SendHealthCheckTestNotification.php @@ -0,0 +1,37 @@ +title('You need to add HealthCheck.io urls!') + ->warning() + ->send(); + + return; + } + + foreach ($webhooks as $webhook) { + WebhookCall::create() + ->url($webhook['url']) + ->payload(['message' => '👋 Testing the HealthCheck.io notification channel.']) + ->doNotSign() + ->dispatch(); + } + + Notification::make() + ->title('Test HealthCheck.io notification sent.') + ->success() + ->send(); + } +} diff --git a/app/Actions/Notifications/SendNtfyTestNotification.php b/app/Actions/Notifications/SendNtfyTestNotification.php new file mode 100644 index 000000000..8975febaa --- /dev/null +++ b/app/Actions/Notifications/SendNtfyTestNotification.php @@ -0,0 +1,49 @@ +title('You need to add ntfy urls!') + ->warning() + ->send(); + + return; + } + + foreach ($webhooks as $webhook) { + $webhookCall = WebhookCall::create() + ->url($webhook['url']) + ->payload([ + 'topic' => $webhook['topic'], + 'message' => '👋 Testing the ntfy notification channel.', + ]) + ->doNotSign(); + + // Only add authentication if username and password are provided + if (! empty($webhook['username']) && ! empty($webhook['password'])) { + $authHeader = 'Basic '.base64_encode($webhook['username'].':'.$webhook['password']); + $webhookCall->withHeaders([ + 'Authorization' => $authHeader, + ]); + } + + $webhookCall->dispatch(); + } + + Notification::make() + ->title('Test ntfy notification sent.') + ->success() + ->send(); + } +} diff --git a/app/Actions/Notifications/SendPushoverTestNotification.php b/app/Actions/Notifications/SendPushoverTestNotification.php new file mode 100644 index 000000000..d37b0594d --- /dev/null +++ b/app/Actions/Notifications/SendPushoverTestNotification.php @@ -0,0 +1,41 @@ +title('You need to add Pushover URLs!') + ->warning() + ->send(); + + return; + } + + foreach ($webhooks as $webhook) { + WebhookCall::create() + ->url($webhook['url']) + ->payload([ + 'token' => $webhook['api_token'], + 'user' => $webhook['user_key'], + 'message' => '👋 Testing the Pushover notification channel.', + ]) + ->doNotSign() + ->dispatch(); + } + + Notification::make() + ->title('Test Pushover notification sent.') + ->success() + ->send(); + } +} diff --git a/app/Actions/Notifications/SendSlackTestNotification.php b/app/Actions/Notifications/SendSlackTestNotification.php new file mode 100644 index 000000000..95db18439 --- /dev/null +++ b/app/Actions/Notifications/SendSlackTestNotification.php @@ -0,0 +1,37 @@ +title('You need to add Slack URLs!') + ->warning() + ->send(); + + return; + } + + foreach ($webhooks as $webhook) { + WebhookCall::create() + ->url($webhook['url']) + ->payload(['text' => '👋 Testing the Slack notification channel.']) + ->doNotSign() + ->dispatch(); + } + + Notification::make() + ->title('Test Slack notification sent.') + ->success() + ->send(); + } +} diff --git a/app/Filament/Pages/Settings/NotificationPage.php b/app/Filament/Pages/Settings/NotificationPage.php index a40a5e080..bd7df5902 100755 --- a/app/Filament/Pages/Settings/NotificationPage.php +++ b/app/Filament/Pages/Settings/NotificationPage.php @@ -4,7 +4,12 @@ use App\Actions\Notifications\SendDatabaseTestNotification; use App\Actions\Notifications\SendDiscordTestNotification; +use App\Actions\Notifications\SendGotifyTestNotification; +use App\Actions\Notifications\SendHealthCheckTestNotification; use App\Actions\Notifications\SendMailTestNotification; +use App\Actions\Notifications\SendNtfyTestNotification; +use App\Actions\Notifications\SendPushoverTestNotification; +use App\Actions\Notifications\SendSlackTestNotification; use App\Actions\Notifications\SendTelegramTestNotification; use App\Actions\Notifications\SendWebhookTestNotification; use App\Settings\NotificationSettings; @@ -84,6 +89,63 @@ public function form(Form $form): Form 'md' => 2, ]), + Forms\Components\Section::make('Pushover') + ->schema([ + Forms\Components\Toggle::make('pushover_enabled') + ->label('Enable Pushover webhook notifications') + ->reactive() + ->columnSpanFull(), + Forms\Components\Grid::make([ + 'default' => 1, + ]) + ->hidden(fn (Forms\Get $get) => $get('pushover_enabled') !== true) + ->schema([ + Forms\Components\Fieldset::make('Triggers') + ->schema([ + Forms\Components\Toggle::make('pushover_on_speedtest_run') + ->label('Notify on every speedtest run') + ->columnSpanFull(), + Forms\Components\Toggle::make('pushover_on_threshold_failure') + ->label('Notify on threshold failures') + ->columnSpanFull(), + ]), + Forms\Components\Repeater::make('pushover_webhooks') + ->label('Pushover Webhooks') + ->schema([ + Forms\Components\TextInput::make('url') + ->label('URL') + ->placeholder('http://api.pushover.net/1/messages.json') + ->maxLength(2000) + ->required() + ->url(), + Forms\Components\TextInput::make('user_key') + ->label('User Key') + ->placeholder('Your Pushover User Key') + ->maxLength(200) + ->required(), + Forms\Components\TextInput::make('api_token') + ->label('API Token') + ->placeholder('Your Pushover API Token') + ->maxLength(200) + ->required(), + ]) + ->columnSpanFull(), + Forms\Components\Actions::make([ + Forms\Components\Actions\Action::make('test pushover') + ->label('Test Pushover webhook') + ->action(fn (Forms\Get $get) => SendPushoverTestNotification::run( + webhooks: $get('pushover_webhooks') + )) + ->hidden(fn (Forms\Get $get) => ! count($get('pushover_webhooks'))), + ]), + ]), + ]) + ->compact() + ->columns([ + 'default' => 1, + 'md' => 2, + ]), + Forms\Components\Section::make('Discord') ->schema([ Forms\Components\Toggle::make('discord_enabled') @@ -108,6 +170,7 @@ public function form(Form $form): Form ->label('Webhooks') ->schema([ Forms\Components\TextInput::make('url') + ->placeholder('https://discord.com/api/webhooks/longstringofcharacters') ->maxLength(2000) ->required() ->url(), @@ -127,6 +190,152 @@ public function form(Form $form): Form 'md' => 2, ]), + Forms\Components\Section::make('Gotify') + ->schema([ + Forms\Components\Toggle::make('gotify_enabled') + ->label('Enable Gotify webhook notifications') + ->reactive() + ->columnSpanFull(), + Forms\Components\Grid::make([ + 'default' => 1, + ]) + ->hidden(fn (Forms\Get $get) => $get('gotify_enabled') !== true) + ->schema([ + Forms\Components\Fieldset::make('Triggers') + ->schema([ + Forms\Components\Toggle::make('gotify_on_speedtest_run') + ->label('Notify on every speedtest run') + ->columnSpanFull(), + Forms\Components\Toggle::make('gotify_on_threshold_failure') + ->label('Notify on threshold failures') + ->columnSpanFull(), + ]), + Forms\Components\Repeater::make('gotify_webhooks') + ->label('Webhooks') + ->schema([ + Forms\Components\TextInput::make('url') + ->placeholder('https://example.com/message?token=') + ->maxLength(2000) + ->required() + ->url(), + ]) + ->columnSpanFull(), + Forms\Components\Actions::make([ + Forms\Components\Actions\Action::make('test gotify') + ->label('Test Gotify webhook') + ->action(fn (Forms\Get $get) => SendgotifyTestNotification::run(webhooks: $get('gotify_webhooks'))) + ->hidden(fn (Forms\Get $get) => ! count($get('gotify_webhooks'))), + ]), + ]), + ]) + ->compact() + ->columns([ + 'default' => 1, + 'md' => 2, + ]), + + Forms\Components\Section::make('Slack') + ->schema([ + Forms\Components\Toggle::make('slack_enabled') + ->label('Enable Slack webhook notifications') + ->reactive() + ->columnSpanFull(), + Forms\Components\Grid::make([ + 'default' => 1, + ]) + ->hidden(fn (Forms\Get $get) => $get('slack_enabled') !== true) + ->schema([ + Forms\Components\Fieldset::make('Triggers') + ->schema([ + Forms\Components\Toggle::make('slack_on_speedtest_run') + ->label('Notify on every speedtest run') + ->columnSpanFull(), + Forms\Components\Toggle::make('slack_on_threshold_failure') + ->label('Notify on threshold failures') + ->columnSpanFull(), + ]), + Forms\Components\Repeater::make('slack_webhooks') + ->label('Webhooks') + ->schema([ + Forms\Components\TextInput::make('url') + ->placeholder('https://hooks.slack.com/services/abc/xyz') + ->maxLength(2000) + ->required() + ->url(), + ]) + ->columnSpanFull(), + Forms\Components\Actions::make([ + Forms\Components\Actions\Action::make('test Slack') + ->label('Test slack webhook') + ->action(fn (Forms\Get $get) => SendSlackTestNotification::run(webhooks: $get('slack_webhooks'))) + ->hidden(fn (Forms\Get $get) => ! count($get('slack_webhooks'))), + ]), + ]), + ]) + ->compact() + ->columns([ + 'default' => 1, + 'md' => 2, + ]), + + Forms\Components\Section::make('Ntfy') + ->schema([ + Forms\Components\Toggle::make('ntfy_enabled') + ->label('Enable Ntfy webhook notifications') + ->reactive() + ->columnSpanFull(), + Forms\Components\Grid::make([ + 'default' => 1, + ]) + ->hidden(fn (Forms\Get $get) => $get('ntfy_enabled') !== true) + ->schema([ + Forms\Components\Fieldset::make('Triggers') + ->schema([ + Forms\Components\Toggle::make('ntfy_on_speedtest_run') + ->label('Notify on every speedtest run') + ->columnSpanFull(), + Forms\Components\Toggle::make('ntfy_on_threshold_failure') + ->label('Notify on threshold failures') + ->columnSpanFull(), + ]), + Forms\Components\Repeater::make('ntfy_webhooks') + ->label('Webhooks') + ->schema([ + Forms\Components\TextInput::make('url') + ->maxLength(2000) + ->placeholder('Your ntfy server url') + ->required() + ->url(), + Forms\Components\TextInput::make('topic') + ->label('Topic') + ->placeholder('Your ntfy Topic') + ->maxLength(200) + ->required(), + Forms\Components\TextInput::make('username') + ->label('Username') + ->placeholder('Username for Basic Auth (optional)') + ->maxLength(200), + Forms\Components\TextInput::make('password') + ->label('Password') + ->placeholder('Password for Basic Auth (optional)') + ->password() + ->maxLength(200), + ]) + ->columnSpanFull(), + Forms\Components\Actions::make([ + Forms\Components\Actions\Action::make('test ntfy') + ->label('Test Ntfy webhook') + ->action(fn (Forms\Get $get) => SendNtfyTestNotification::run(webhooks: $get('ntfy_webhooks'))) + ->hidden(fn (Forms\Get $get) => ! count($get('ntfy_webhooks'))), + ]), + ]), + ]) + ->compact() + ->columns([ + 'default' => 1, + 'md' => 2, + ]), + Forms\Components\Section::make('Mail') ->schema([ Forms\Components\Toggle::make('mail_enabled') @@ -151,6 +360,7 @@ public function form(Form $form): Form ->label('Recipients') ->schema([ Forms\Components\TextInput::make('email_address') + ->placeholder('your@email.com') ->email() ->required(), ]) @@ -169,6 +379,51 @@ public function form(Form $form): Form 'md' => 2, ]), + Forms\Components\Section::make('Healthcheck.io') + ->schema([ + Forms\Components\Toggle::make('healthcheck_enabled') + ->label('Enable healthcheck.io webhook notifications') + ->reactive() + ->columnSpanFull(), + Forms\Components\Grid::make([ + 'default' => 1, + ]) + ->hidden(fn (Forms\Get $get) => $get('healthcheck_enabled') !== true) + ->schema([ + Forms\Components\Fieldset::make('Triggers') + ->schema([ + Forms\Components\Toggle::make('healthcheck_on_speedtest_run') + ->label('Notify on every speedtest run') + ->columnSpanFull(), + Forms\Components\Toggle::make('healthcheck_on_threshold_failure') + ->label('Notify on threshold failures') + ->helperText('Threshold notifications will be sent to the /fail path of the URL.') + ->columnSpanFull(), + ]), + Forms\Components\Repeater::make('healthcheck_webhooks') + ->label('webhooks') + ->schema([ + Forms\Components\TextInput::make('url') + ->placeholder('https://hc-ping.com/your-uuid-here') + ->maxLength(2000) + ->required() + ->url(), + ]) + ->columnSpanFull(), + Forms\Components\Actions::make([ + Forms\Components\Actions\Action::make('test healthcheck') + ->label('Test healthcheck.io webhook') + ->action(fn (Forms\Get $get) => SendHealthCheckTestNotification::run(webhooks: $get('healthcheck_webhooks'))) + ->hidden(fn (Forms\Get $get) => ! count($get('healthcheck_webhooks'))), + ]), + ]), + ]) + ->compact() + ->columns([ + 'default' => 1, + 'md' => 2, + ]), + Forms\Components\Section::make('Telegram') ->schema([ Forms\Components\Toggle::make('telegram_enabled') @@ -199,6 +454,7 @@ public function form(Form $form): Form ->label('Recipients') ->schema([ Forms\Components\TextInput::make('telegram_chat_id') + ->placeholder('12345678910') ->label('Telegram Chat ID') ->maxLength(50) ->required(), @@ -242,6 +498,7 @@ public function form(Form $form): Form ->label('Recipients') ->schema([ Forms\Components\TextInput::make('url') + ->placeholder('https://webhook.site/longstringofcharacters') ->maxLength(2000) ->required() ->url(), diff --git a/app/Listeners/Discord/SendSpeedtestCompletedNotification.php b/app/Listeners/Discord/SendSpeedtestCompletedNotification.php index bca6ba2ac..5200886e2 100644 --- a/app/Listeners/Discord/SendSpeedtestCompletedNotification.php +++ b/app/Listeners/Discord/SendSpeedtestCompletedNotification.php @@ -43,6 +43,7 @@ public function handle(SpeedtestCompleted $event): void 'download' => Number::toBitRate(bits: $event->result->download_bits, precision: 2), 'upload' => Number::toBitRate(bits: $event->result->upload_bits, precision: 2), 'packetLoss' => $event->result->packet_loss, + 'speedtest_url' => $event->result->result_url, 'url' => url('/admin/results'), ])->render(), ]; diff --git a/app/Listeners/Discord/SendSpeedtestThresholdNotification.php b/app/Listeners/Discord/SendSpeedtestThresholdNotification.php index fc7ad9446..4fad0a5eb 100644 --- a/app/Listeners/Discord/SendSpeedtestThresholdNotification.php +++ b/app/Listeners/Discord/SendSpeedtestThresholdNotification.php @@ -69,6 +69,7 @@ public function handle(SpeedtestCompleted $event): void 'serverId' => $event->result->server_id, 'isp' => $event->result->isp, 'metrics' => $failed, + 'speedtest_url' => $event->result->result_url, 'url' => url('/admin/results'), ])->render(), ]; diff --git a/app/Listeners/Gotify/SendSpeedtestCompletedNotification.php b/app/Listeners/Gotify/SendSpeedtestCompletedNotification.php new file mode 100644 index 000000000..8265426c9 --- /dev/null +++ b/app/Listeners/Gotify/SendSpeedtestCompletedNotification.php @@ -0,0 +1,59 @@ +gotify_enabled) { + return; + } + + if (! $notificationSettings->gotify_on_speedtest_run) { + return; + } + + if (! count($notificationSettings->gotify_webhooks)) { + Log::warning('Gotify urls not found, check Gotify notification channel settings.'); + + return; + } + + $payload = [ + 'message' => view('gotify.speedtest-completed', [ + 'id' => $event->result->id, + 'service' => Str::title($event->result->service), + 'serverName' => $event->result->server_name, + 'serverId' => $event->result->server_id, + 'isp' => $event->result->isp, + 'ping' => round($event->result->ping).' ms', + 'download' => Number::toBitRate(bits: $event->result->download_bits, precision: 2), + 'upload' => Number::toBitRate(bits: $event->result->upload_bits, precision: 2), + 'packetLoss' => $event->result->packet_loss, + 'speedtest_url' => $event->result->result_url, + 'url' => url('/admin/results'), + ])->render(), + ]; + + foreach ($notificationSettings->gotify_webhooks as $url) { + WebhookCall::create() + ->url($url['url']) + ->payload($payload) + ->doNotSign() + ->dispatch(); + } + } +} diff --git a/app/Listeners/Gotify/SendSpeedtestThresholdNotification.php b/app/Listeners/Gotify/SendSpeedtestThresholdNotification.php new file mode 100644 index 000000000..8da2daedf --- /dev/null +++ b/app/Listeners/Gotify/SendSpeedtestThresholdNotification.php @@ -0,0 +1,133 @@ +gotify_enabled) { + return; + } + + if (! $notificationSettings->gotify_on_threshold_failure) { + return; + } + + if (! count($notificationSettings->gotify_webhooks)) { + Log::warning('Gotify urls not found, check gotify 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 Gotify thresholds not found, won\'t send notification.'); + + return; + } + + $payload = [ + 'message' => view('gotify.speedtest-threshold', [ + 'id' => $event->result->id, + 'service' => Str::title($event->result->service), + '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(), + ]; + + foreach ($notificationSettings->gotify_webhooks as $url) { + WebhookCall::create() + ->url($url['url']) + ->payload($payload) + ->doNotSign() + ->dispatch(); + } + } + + /** + * Build gotify 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 gotify 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 gotify 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/HealthCheck/SendSpeedtestCompletedNotification.php b/app/Listeners/HealthCheck/SendSpeedtestCompletedNotification.php new file mode 100644 index 000000000..3d127f59b --- /dev/null +++ b/app/Listeners/HealthCheck/SendSpeedtestCompletedNotification.php @@ -0,0 +1,51 @@ +healthcheck_enabled) { + return; + } + + if (! $notificationSettings->healthcheck_on_speedtest_run) { + return; + } + + if (! count($notificationSettings->healthcheck_webhooks)) { + Log::warning('healthcheck urls not found, check healthcheck notification channel settings.'); + + return; + } + + foreach ($notificationSettings->healthcheck_webhooks 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/Listeners/HealthCheck/SendSpeedtestThresholdNotification.php b/app/Listeners/HealthCheck/SendSpeedtestThresholdNotification.php new file mode 100644 index 000000000..11db9fec1 --- /dev/null +++ b/app/Listeners/HealthCheck/SendSpeedtestThresholdNotification.php @@ -0,0 +1,126 @@ +healthcheck_enabled) { + return; + } + + if (! $notificationSettings->healthcheck_on_threshold_failure) { + return; + } + + if (! count($notificationSettings->healthcheck_webhooks)) { + Log::warning('HealthCheck urls not found, check healthcheck 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 healthcheck thresholds not found, won\'t send notification.'); + + return; + } + + foreach ($notificationSettings->healthcheck_webhooks as $url) { + WebhookCall::create() + ->url($url['url'].'/fail') + ->payload([ + 'result_id' => $event->result->id, + 'site_name' => config('app.name'), + 'isp' => $event->result->isp, + 'metrics' => $failed, + 'speedtest_url' => $event->result->result_url, + 'url' => url('/admin/results'), + ]) + ->doNotSign() + ->dispatch(); + } + } + + /** + * Build HealthCheck 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 Healthcheck 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 Healthcheck 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/Ntfy/SendSpeedtestCompletedNotification.php b/app/Listeners/Ntfy/SendSpeedtestCompletedNotification.php new file mode 100644 index 000000000..f63cdcd13 --- /dev/null +++ b/app/Listeners/Ntfy/SendSpeedtestCompletedNotification.php @@ -0,0 +1,69 @@ +ntfy_enabled) { + return; + } + + if (! $notificationSettings->ntfy_on_speedtest_run) { + return; + } + + if (! count($notificationSettings->ntfy_webhooks)) { + Log::warning('Ntfy urls not found, check Ntfy notification channel settings.'); + + return; + } + + $payload = + view('ntfy.speedtest-completed', [ + 'id' => $event->result->id, + 'service' => Str::title($event->result->service), + 'serverName' => $event->result->server_name, + 'serverId' => $event->result->server_id, + 'isp' => $event->result->isp, + 'ping' => round($event->result->ping).' ms', + 'download' => Number::toBitRate(bits: $event->result->download_bits, precision: 2), + 'upload' => Number::toBitRate(bits: $event->result->upload_bits, precision: 2), + 'packetLoss' => $event->result->packet_loss, + 'speedtest_url' => $event->result->result_url, + 'url' => url('/admin/results'), + ])->render(); + + foreach ($notificationSettings->ntfy_webhooks as $url) { + $webhookCall = WebhookCall::create() + ->url($url['url']) + ->payload([ + 'topic' => $url['topic'], + 'message' => $payload, + ]) + ->doNotSign(); + + // Only add authentication if username and password are provided + if (! empty($url['username']) && ! empty($url['password'])) { + $authHeader = 'Basic '.base64_encode($url['username'].':'.$url['password']); + $webhookCall->withHeaders([ + 'Authorization' => $authHeader, + ]); + } + $webhookCall->dispatch(); + } + } +} diff --git a/app/Listeners/Ntfy/SendSpeedtestThresholdNotification.php b/app/Listeners/Ntfy/SendSpeedtestThresholdNotification.php new file mode 100644 index 000000000..2ca1a93e1 --- /dev/null +++ b/app/Listeners/Ntfy/SendSpeedtestThresholdNotification.php @@ -0,0 +1,144 @@ +ntfy_enabled) { + return; + } + + if (! $notificationSettings->ntfy_on_threshold_failure) { + return; + } + + if (! count($notificationSettings->ntfy_webhooks)) { + Log::warning('Ntfy urls not found, check Ntfy 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 ntfy thresholds not found, won\'t send notification.'); + + return; + } + + $payload = + view('ntfy.speedtest-threshold', [ + 'id' => $event->result->id, + 'service' => Str::title($event->result->service), + '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(); + + foreach ($notificationSettings->ntfy_webhooks as $url) { + $webhookCall = WebhookCall::create() + ->url($url['url']) + ->payload([ + 'topic' => $url['topic'], + 'message' => $payload, + ]) + ->doNotSign(); + + // Only add authentication if username and password are provided + if (! empty($url['username']) && ! empty($url['password'])) { + $authHeader = 'Basic '.base64_encode($url['username'].':'.$url['password']); + $webhookCall->withHeaders([ + 'Authorization' => $authHeader, + ]); + } + + $webhookCall->dispatch(); + } + } + + /** + * Build Ntfy 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 Ntfy 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 Ntfy 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/Pushover/SendSpeedtestCompletedNotification.php b/app/Listeners/Pushover/SendSpeedtestCompletedNotification.php new file mode 100644 index 000000000..465c98f9d --- /dev/null +++ b/app/Listeners/Pushover/SendSpeedtestCompletedNotification.php @@ -0,0 +1,63 @@ +pushover_enabled) { + return; + } + + if (! $notificationSettings->pushover_on_speedtest_run) { + return; + } + + if (! count($notificationSettings->pushover_webhooks)) { + Log::warning('Pushover urls not found, check Pushover notification channel settings.'); + + return; + } + + $payload = [ + view('pushover.speedtest-completed', [ + 'id' => $event->result->id, + 'service' => Str::title($event->result->service), + 'serverName' => $event->result->server_name, + 'serverId' => $event->result->server_id, + 'isp' => $event->result->isp, + 'ping' => round($event->result->ping).' ms', + 'download' => Number::toBitRate(bits: $event->result->download_bits, precision: 2), + 'upload' => Number::toBitRate(bits: $event->result->upload_bits, precision: 2), + 'packetLoss' => $event->result->packet_loss, + 'speedtest_url' => $event->result->result_url, + 'url' => url('/admin/results'), + ])->render(), + ]; + + foreach ($notificationSettings->pushover_webhooks as $url) { + WebhookCall::create() + ->url($url['url']) + ->payload([ + 'token' => $url['api_token'], + 'user' => $url['user_key'], + 'message' => $payload, + ]) + ->doNotSign() + ->dispatch(); + } + } +} diff --git a/app/Listeners/Pushover/SendSpeedtestThresholdNotification.php b/app/Listeners/Pushover/SendSpeedtestThresholdNotification.php new file mode 100644 index 000000000..7643702f8 --- /dev/null +++ b/app/Listeners/Pushover/SendSpeedtestThresholdNotification.php @@ -0,0 +1,137 @@ +pushover_enabled) { + return; + } + + if (! $notificationSettings->pushover_on_threshold_failure) { + return; + } + + if (! count($notificationSettings->pushover_webhooks)) { + Log::warning('Pushover urls not found, check Pushover 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 Pushover thresholds not found, won\'t send notification.'); + + return; + } + + $payload = [ + view('pushover.speedtest-threshold', [ + 'id' => $event->result->id, + 'service' => Str::title($event->result->service), + '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(), + ]; + + foreach ($notificationSettings->pushover_webhooks as $url) { + WebhookCall::create() + ->url($url['url']) + ->payload([ + 'token' => $url['api_token'], + 'user' => $url['user_key'], + 'message' => $payload, + ]) + ->doNotSign() + ->dispatch(); + } + } + + /** + * Build Pushover 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 Pushover 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 Pushover 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/Slack/SendSpeedtestCompletedNotification.php b/app/Listeners/Slack/SendSpeedtestCompletedNotification.php new file mode 100644 index 000000000..791aaad33 --- /dev/null +++ b/app/Listeners/Slack/SendSpeedtestCompletedNotification.php @@ -0,0 +1,59 @@ +slack_enabled) { + return; + } + + if (! $notificationSettings->slack_on_speedtest_run) { + return; + } + + if (! count($notificationSettings->slack_webhooks)) { + Log::warning('Slack URLs not found, check Slack notification channel settings.'); + + return; + } + + $payload = [ + 'text' => view('slack.speedtest-completed', [ + 'id' => $event->result->id, + 'service' => Str::title($event->result->service), + 'serverName' => $event->result->server_name, + 'serverId' => $event->result->server_id, + 'isp' => $event->result->isp, + 'ping' => round($event->result->ping).' ms', + 'download' => Number::toBitRate(bits: $event->result->download_bits, precision: 2), + 'upload' => Number::toBitRate(bits: $event->result->upload_bits, precision: 2), + 'packetLoss' => $event->result->packet_loss, + 'speedtest_url' => $event->result->result_url, + 'url' => url('/admin/results'), + ])->render(), + ]; + + foreach ($notificationSettings->slack_webhooks as $url) { + WebhookCall::create() + ->url($url['url']) + ->payload($payload) + ->doNotSign() + ->dispatch(); + } + } +} diff --git a/app/Listeners/Slack/SendSpeedtestThresholdNotification.php b/app/Listeners/Slack/SendSpeedtestThresholdNotification.php new file mode 100644 index 000000000..debeb76cf --- /dev/null +++ b/app/Listeners/Slack/SendSpeedtestThresholdNotification.php @@ -0,0 +1,133 @@ +slack_enabled) { + return; + } + + if (! $notificationSettings->slack_on_threshold_failure) { + return; + } + + if (! count($notificationSettings->slack_webhooks)) { + Log::warning('Slack urls not found, check Slack 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 Slack thresholds not found, won\'t send notification.'); + + return; + } + + $payload = [ + 'text' => view('slack.speedtest-threshold', [ + 'id' => $event->result->id, + 'service' => Str::title($event->result->service), + '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(), + ]; + + foreach ($notificationSettings->slack_webhooks as $url) { + WebhookCall::create() + ->url($url['url']) + ->payload($payload) + ->doNotSign() + ->dispatch(); + } + } + + /** + * Build Slack 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 Slack 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 Slack 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/Telegram/SendSpeedtestCompletedNotification.php b/app/Listeners/Telegram/SendSpeedtestCompletedNotification.php index be407f3c2..93e69fbb4 100644 --- a/app/Listeners/Telegram/SendSpeedtestCompletedNotification.php +++ b/app/Listeners/Telegram/SendSpeedtestCompletedNotification.php @@ -43,6 +43,7 @@ public function handle(SpeedtestCompleted $event): void 'download' => Number::toBitRate(bits: $event->result->download_bits, precision: 2), 'upload' => Number::toBitRate(bits: $event->result->upload_bits, precision: 2), 'packetLoss' => is_numeric($event->result->packet_loss) ? round($event->result->packet_loss, 2) : 'n/a', + 'speedtest_url' => $event->result->result_url, 'url' => url('/admin/results'), ])->render(); diff --git a/app/Listeners/Telegram/SendSpeedtestThresholdNotification.php b/app/Listeners/Telegram/SendSpeedtestThresholdNotification.php index d906cc593..6ae2375b2 100644 --- a/app/Listeners/Telegram/SendSpeedtestThresholdNotification.php +++ b/app/Listeners/Telegram/SendSpeedtestThresholdNotification.php @@ -69,6 +69,7 @@ public function handle(SpeedtestCompleted $event): void 'serverId' => $event->result->server_id, 'isp' => $event->result->isp, 'metrics' => $failed, + 'speedtest_url' => $event->result->result_url, 'url' => url('/admin/results'), ])->render(); diff --git a/app/Listeners/Webhook/SendSpeedtestCompletedNotification.php b/app/Listeners/Webhook/SendSpeedtestCompletedNotification.php index 88560ce20..3956d2bed 100644 --- a/app/Listeners/Webhook/SendSpeedtestCompletedNotification.php +++ b/app/Listeners/Webhook/SendSpeedtestCompletedNotification.php @@ -41,6 +41,7 @@ public function handle(SpeedtestCompleted $event): void 'download' => $event->result->downloadBits, 'upload' => $event->result->uploadBits, 'packetLoss' => $event->result->packet_loss, + 'speedtest_url' => $event->result->result_url, 'url' => url('/admin/results'), ]) ->doNotSign() diff --git a/app/Listeners/Webhook/SendSpeedtestThresholdNotification.php b/app/Listeners/Webhook/SendSpeedtestThresholdNotification.php index aac1866a7..8bf45d768 100644 --- a/app/Listeners/Webhook/SendSpeedtestThresholdNotification.php +++ b/app/Listeners/Webhook/SendSpeedtestThresholdNotification.php @@ -68,6 +68,7 @@ public function handle(SpeedtestCompleted $event): void 'site_name' => config('app.name'), 'isp' => $event->result->isp, 'metrics' => $failed, + 'speedtest_url' => $event->result->result_url, 'url' => url('/admin/results'), ]) ->doNotSign() diff --git a/app/Mail/SpeedtestCompletedMail.php b/app/Mail/SpeedtestCompletedMail.php index f8480f1bb..c6d43bfc3 100644 --- a/app/Mail/SpeedtestCompletedMail.php +++ b/app/Mail/SpeedtestCompletedMail.php @@ -52,6 +52,7 @@ public function content(): Content '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 46202f28f..038709e90 100644 --- a/app/Mail/SpeedtestThresholdMail.php +++ b/app/Mail/SpeedtestThresholdMail.php @@ -48,6 +48,7 @@ public function content(): Content '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, ], diff --git a/app/Settings/NotificationSettings.php b/app/Settings/NotificationSettings.php index ff2d4d22a..0796be61a 100644 --- a/app/Settings/NotificationSettings.php +++ b/app/Settings/NotificationSettings.php @@ -46,6 +46,46 @@ class NotificationSettings extends Settings public ?array $discord_webhooks; + public bool $ntfy_enabled; + + public bool $ntfy_on_speedtest_run; + + public bool $ntfy_on_threshold_failure; + + public ?array $ntfy_webhooks; + + public bool $pushover_enabled; + + public bool $pushover_on_speedtest_run; + + public bool $pushover_on_threshold_failure; + + public ?array $pushover_webhooks; + + public bool $healthcheck_enabled; + + public bool $healthcheck_on_speedtest_run; + + public bool $healthcheck_on_threshold_failure; + + public ?array $healthcheck_webhooks; + + public bool $slack_enabled; + + public bool $slack_on_speedtest_run; + + public bool $slack_on_threshold_failure; + + public ?array $slack_webhooks; + + public bool $gotify_enabled; + + public bool $gotify_on_speedtest_run; + + public bool $gotify_on_threshold_failure; + + public ?array $gotify_webhooks; + public static function group(): string { return 'notification'; diff --git a/database/settings/2024_02_22_144620_create_healthcheck_notification_settings.php b/database/settings/2024_02_22_144620_create_healthcheck_notification_settings.php new file mode 100644 index 000000000..b5dd742bb --- /dev/null +++ b/database/settings/2024_02_22_144620_create_healthcheck_notification_settings.php @@ -0,0 +1,14 @@ +migrator->add('notification.healthcheck_enabled', false); + $this->migrator->add('notification.healthcheck_on_speedtest_run', false); + $this->migrator->add('notification.healthcheck_on_threshold_failure', false); + $this->migrator->add('notification.healthcheck_webhooks', null); + } +}; diff --git a/database/settings/2024_02_22_144650_create_ntfy_notification_settings.php b/database/settings/2024_02_22_144650_create_ntfy_notification_settings.php new file mode 100644 index 000000000..aca36e39f --- /dev/null +++ b/database/settings/2024_02_22_144650_create_ntfy_notification_settings.php @@ -0,0 +1,14 @@ +migrator->add('notification.ntfy_enabled', false); + $this->migrator->add('notification.ntfy_on_speedtest_run', false); + $this->migrator->add('notification.ntfy_on_threshold_failure', false); + $this->migrator->add('notification.ntfy_webhooks', null); + } +}; diff --git a/database/settings/2024_02_22_144650_create_slack_notification_settings.php b/database/settings/2024_02_22_144650_create_slack_notification_settings.php new file mode 100644 index 000000000..755a70b74 --- /dev/null +++ b/database/settings/2024_02_22_144650_create_slack_notification_settings.php @@ -0,0 +1,14 @@ +migrator->add('notification.slack_enabled', false); + $this->migrator->add('notification.slack_on_speedtest_run', false); + $this->migrator->add('notification.slack_on_threshold_failure', false); + $this->migrator->add('notification.slack_webhooks', null); + } +}; diff --git a/database/settings/2024_02_22_144654_create_gotify_notification_settings.php b/database/settings/2024_02_22_144654_create_gotify_notification_settings.php new file mode 100644 index 000000000..af0d818d7 --- /dev/null +++ b/database/settings/2024_02_22_144654_create_gotify_notification_settings.php @@ -0,0 +1,14 @@ +migrator->add('notification.gotify_enabled', false); + $this->migrator->add('notification.gotify_on_speedtest_run', false); + $this->migrator->add('notification.gotify_on_threshold_failure', false); + $this->migrator->add('notification.gotify_webhooks', null); + } +}; diff --git a/database/settings/2024_02_22_144680_create_pushover_notification_settings.php b/database/settings/2024_02_22_144680_create_pushover_notification_settings.php new file mode 100644 index 000000000..92f394001 --- /dev/null +++ b/database/settings/2024_02_22_144680_create_pushover_notification_settings.php @@ -0,0 +1,14 @@ +migrator->add('notification.pushover_enabled', false); + $this->migrator->add('notification.pushover_on_speedtest_run', false); + $this->migrator->add('notification.pushover_on_threshold_failure', false); + $this->migrator->add('notification.pushover_webhooks', null); + } +}; diff --git a/resources/views/discord/speedtest-completed.blade.php b/resources/views/discord/speedtest-completed.blade.php index dda7a8782..023fa9464 100644 --- a/resources/views/discord/speedtest-completed.blade.php +++ b/resources/views/discord/speedtest-completed.blade.php @@ -9,4 +9,5 @@ - **Download:** {{ $download }} - **Upload:** {{ $upload }} - **Packet Loss:** {{ $packetLoss }} **%** +- **Ookla Speedtest:** {{ $speedtest_url }} - **URL:** {{ $url }} diff --git a/resources/views/discord/speedtest-threshold.blade.php b/resources/views/discord/speedtest-threshold.blade.php index 2cb708643..dc38dd40c 100644 --- a/resources/views/discord/speedtest-threshold.blade.php +++ b/resources/views/discord/speedtest-threshold.blade.php @@ -5,4 +5,5 @@ @foreach ($metrics as $item) - **{{ $item['name'] }}** {{ $item['threshold'] }}: {{ $item['value'] }} @endforeach +- **Ookla Speedtest:** {{ $speedtest_url }} - **URL:** {{ $url }} diff --git a/resources/views/emails/speedtest-completed.blade.php b/resources/views/emails/speedtest-completed.blade.php index b8ce7d1d4..a9f3c1ac8 100644 --- a/resources/views/emails/speedtest-completed.blade.php +++ b/resources/views/emails/speedtest-completed.blade.php @@ -12,7 +12,8 @@ | Ping | {{ $ping }} | | Download | {{ $download }} | | Upload | {{ $upload }} | -| Packet Loss | {{ $packetLoss }}**%** | +| Packet Loss | {{ $packetLoss }} **%** | + @@ -20,6 +21,10 @@ View Results + +View Results on Ookla + + Thanks,
{{ config('app.name') }} diff --git a/resources/views/emails/speedtest-threshold.blade.php b/resources/views/emails/speedtest-threshold.blade.php index 18879fd8a..373e33f47 100644 --- a/resources/views/emails/speedtest-threshold.blade.php +++ b/resources/views/emails/speedtest-threshold.blade.php @@ -15,6 +15,10 @@ View Results + +View Results on Ookla + + Thanks,
{{ config('app.name') }} diff --git a/resources/views/gotify/speedtest-completed.blade.php b/resources/views/gotify/speedtest-completed.blade.php new file mode 100644 index 000000000..ee50f07e3 --- /dev/null +++ b/resources/views/gotify/speedtest-completed.blade.php @@ -0,0 +1,13 @@ +Speedtest Completed - #{{ $id }} + +A new speedtest 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/gotify/speedtest-threshold.blade.php b/resources/views/gotify/speedtest-threshold.blade.php new file mode 100644 index 000000000..4a7fe325e --- /dev/null +++ b/resources/views/gotify/speedtest-threshold.blade.php @@ -0,0 +1,9 @@ +Speedtest Threshold Breached - #{{ $id }} + +A new speedtest 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 }} diff --git a/resources/views/ntfy/speedtest-completed.blade.php b/resources/views/ntfy/speedtest-completed.blade.php new file mode 100644 index 000000000..5976b28d4 --- /dev/null +++ b/resources/views/ntfy/speedtest-completed.blade.php @@ -0,0 +1,13 @@ +Speedtest Completed - #{{ $id }} + +A new speedtest 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/ntfy/speedtest-threshold.blade.php b/resources/views/ntfy/speedtest-threshold.blade.php new file mode 100644 index 000000000..4a7fe325e --- /dev/null +++ b/resources/views/ntfy/speedtest-threshold.blade.php @@ -0,0 +1,9 @@ +Speedtest Threshold Breached - #{{ $id }} + +A new speedtest 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 }} diff --git a/resources/views/pushover/speedtest-completed.blade.php b/resources/views/pushover/speedtest-completed.blade.php new file mode 100644 index 000000000..2a209023c --- /dev/null +++ b/resources/views/pushover/speedtest-completed.blade.php @@ -0,0 +1,13 @@ +Speedtest Completed - #{{ $id }} + +A new speedtest 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/pushover/speedtest-threshold.blade.php b/resources/views/pushover/speedtest-threshold.blade.php new file mode 100644 index 000000000..4a7fe325e --- /dev/null +++ b/resources/views/pushover/speedtest-threshold.blade.php @@ -0,0 +1,9 @@ +Speedtest Threshold Breached - #{{ $id }} + +A new speedtest 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 }} diff --git a/resources/views/slack/speedtest-completed.blade.php b/resources/views/slack/speedtest-completed.blade.php new file mode 100644 index 000000000..19875a61e --- /dev/null +++ b/resources/views/slack/speedtest-completed.blade.php @@ -0,0 +1,13 @@ +*Speedtest Completed - #{{ $id }}* + +A new speedtest 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/slack/speedtest-threshold.blade.php b/resources/views/slack/speedtest-threshold.blade.php new file mode 100644 index 000000000..87a365730 --- /dev/null +++ b/resources/views/slack/speedtest-threshold.blade.php @@ -0,0 +1,9 @@ +**Speedtest Threshold Breached - #{{ $id }}** + +A new speedtest 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 }} diff --git a/resources/views/telegram/speedtest-completed.blade.php b/resources/views/telegram/speedtest-completed.blade.php index 75cd08663..6b1ac81f6 100644 --- a/resources/views/telegram/speedtest-completed.blade.php +++ b/resources/views/telegram/speedtest-completed.blade.php @@ -9,4 +9,5 @@ - *Download:* {{ $download }} - *Upload:* {{ $upload }} - **Packet Loss:** {{ $packetLoss }}**%** +- **Ookla Speedtest:** {{ $speedtest_url }} - **URL:** {{ $url }} diff --git a/resources/views/telegram/speedtest-threshold.blade.php b/resources/views/telegram/speedtest-threshold.blade.php index 2cb708643..dc38dd40c 100644 --- a/resources/views/telegram/speedtest-threshold.blade.php +++ b/resources/views/telegram/speedtest-threshold.blade.php @@ -5,4 +5,5 @@ @foreach ($metrics as $item) - **{{ $item['name'] }}** {{ $item['threshold'] }}: {{ $item['value'] }} @endforeach +- **Ookla Speedtest:** {{ $speedtest_url }} - **URL:** {{ $url }}