Skip to content

Commit 66d4aeb

Browse files
[Feature] Add webhook notifications (alexjustesen#1103)
Co-authored-by: Nils Uliczka <[email protected]>
1 parent e9907db commit 66d4aeb

File tree

8 files changed

+347
-1
lines changed

8 files changed

+347
-1
lines changed

app/Filament/Pages/Settings/NotificationPage.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Filament\Pages\SettingsPage;
1515
use Illuminate\Support\Facades\Mail;
1616
use Illuminate\Support\Facades\Notification as FacadesNotification;
17+
use Spatie\WebhookServer\WebhookCall;
1718

1819
class NotificationPage extends SettingsPage
1920
{
@@ -162,6 +163,75 @@ public function form(Form $form): Form
162163
'default' => 1,
163164
'md' => 2,
164165
]),
166+
167+
Forms\Components\Section::make('Webhook')
168+
->schema([
169+
Forms\Components\Toggle::make('webhook_enabled')
170+
->label('Enable webhook notifications')
171+
->reactive()
172+
->columnSpanFull(),
173+
Forms\Components\Grid::make([
174+
'default' => 1, ])
175+
->hidden(fn (Forms\Get $get) => $get('webhook_enabled') !== true)
176+
->schema([
177+
Forms\Components\Fieldset::make('Triggers')
178+
->schema([
179+
Forms\Components\Toggle::make('webhook_on_speedtest_run')
180+
->label('Notify on every speedtest run')
181+
->columnSpan(2),
182+
Forms\Components\Toggle::make('webhook_on_threshold_failure')
183+
->label('Notify on threshold failures')
184+
->columnSpan(2),
185+
]),
186+
]),
187+
Forms\Components\Repeater::make('webhook_urls')
188+
->label('Recipients')
189+
->schema([
190+
Forms\Components\TextInput::make('url')
191+
->maxLength(2000)
192+
->required()
193+
->url()
194+
->columnSpanFull(),
195+
])
196+
->hidden(fn (Forms\Get $get) => $get('webhook_enabled') !== true)
197+
->columnSpanFull(),
198+
Forms\Components\Actions::make([
199+
Forms\Components\Actions\Action::make('test webhook')
200+
->label('Test webhook channel')
201+
->action(function (): void {
202+
$notificationSettings = new (NotificationSettings::class);
203+
204+
if (blank($notificationSettings->webhook_urls)) {
205+
Notification::make()
206+
->title('You need to add webhook urls.')
207+
->body('Make sure to click "Save changes" before testing webhook notifications.')
208+
->warning()
209+
->send();
210+
211+
return;
212+
}
213+
214+
foreach ($notificationSettings->webhook_urls as $url) {
215+
WebhookCall::create()
216+
->url($url['url'])
217+
->payload(['message' => '👋 Testing the Webhook notification channel.'])
218+
->doNotSign()
219+
->dispatch();
220+
}
221+
222+
Notification::make()
223+
->title('Test webhook notification sent.')
224+
->success()
225+
->send();
226+
})
227+
->hidden(fn (Forms\Get $get) => $get('webhook_enabled') !== true),
228+
]),
229+
])
230+
->compact()
231+
->columns([
232+
'default' => 1,
233+
'md' => 2,
234+
]),
165235
])
166236
->columnSpan([
167237
'md' => 2,

app/Listeners/SpeedtestCompletedListener.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use App\Telegram\TelegramNotification;
1010
use Filament\Notifications\Notification;
1111
use Illuminate\Support\Facades\Mail;
12+
use Spatie\WebhookServer\WebhookCall;
1213

1314
class SpeedtestCompletedListener
1415
{
@@ -73,5 +74,23 @@ public function handle(ResultCreated $event): void
7374
}
7475
}
7576
}
77+
78+
if ($this->notificationSettings->webhook_enabled) {
79+
if ($this->notificationSettings->webhook_on_speedtest_run && count($this->notificationSettings->webhook_urls)) {
80+
foreach ($this->notificationSettings->webhook_urls as $url) {
81+
WebhookCall::create()
82+
->url($url['url'])
83+
->payload([
84+
'result_id' => $event->result->id,
85+
'site_name' => $this->generalSettings->site_name,
86+
'ping' => $event->result->ping,
87+
'download' => $event->result->downloadBits,
88+
'upload' => $event->result->uploadBits,
89+
])
90+
->doNotSign()
91+
->dispatch();
92+
}
93+
}
94+
}
7695
}
7796
}

app/Listeners/Threshold/AbsoluteListener.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
use App\Telegram\TelegramNotification;
1111
use Filament\Notifications\Notification;
1212
use Illuminate\Contracts\Queue\ShouldQueue;
13+
use Illuminate\Support\Facades\Http;
1314
use Illuminate\Support\Facades\Log;
1415
use Illuminate\Support\Facades\Mail;
16+
use Spatie\WebhookServer\WebhookCall;
1517

1618
class AbsoluteListener implements ShouldQueue
1719
{
@@ -60,6 +62,11 @@ public function handle(ResultCreated $event): void
6062
if ($this->notificationSettings->telegram_enabled == true && $this->notificationSettings->telegram_on_threshold_failure == true) {
6163
$this->telegramChannel($event);
6264
}
65+
66+
// Webhook notification channel
67+
if ($this->notificationSettings->webhook_enabled == true && $this->notificationSettings->webhook_on_threshold_failure == true) {
68+
$this->webhookChannel($event);
69+
}
6370
}
6471

6572
/**
@@ -211,4 +218,71 @@ protected function telegramChannel(ResultCreated $event): void
211218
}
212219
}
213220
}
221+
222+
/**
223+
* Handle webhook notifications.
224+
*
225+
* TODO: refactor
226+
*/
227+
protected function webhookChannel(ResultCreated $event): void
228+
{
229+
$failedThresholds = [];
230+
231+
if (! count($this->notificationSettings->webhook_urls) > 0) {
232+
Log::info('Skipping sending webhook notification, no urls.');
233+
}
234+
235+
// Download threshold
236+
if ($this->thresholdSettings->absolute_download > 0) {
237+
if (absoluteDownloadThresholdFailed($this->thresholdSettings->absolute_download, $event->result->download)) {
238+
array_push($failedThresholds, [
239+
'name' => 'Download',
240+
'threshold' => $this->thresholdSettings->absolute_download,
241+
'value' => toBits(convertSize($event->result->download), 2),
242+
]);
243+
}
244+
}
245+
246+
// Upload threshold
247+
if ($this->thresholdSettings->absolute_upload > 0) {
248+
if (absoluteUploadThresholdFailed($this->thresholdSettings->absolute_upload, $event->result->upload)) {
249+
array_push($failedThresholds, [
250+
'name' => 'Upload',
251+
'threshold' => $this->thresholdSettings->absolute_upload,
252+
'value' => toBits(convertSize($event->result->upload), 2),
253+
]);
254+
}
255+
}
256+
257+
// Ping threshold
258+
if ($this->thresholdSettings->absolute_ping > 0) {
259+
if (absolutePingThresholdFailed($this->thresholdSettings->absolute_ping, $event->result->ping)) {
260+
array_push($failedThresholds, [
261+
'name' => 'Ping',
262+
'threshold' => $this->thresholdSettings->absolute_ping,
263+
'value' => round($event->result->ping, 2),
264+
]);
265+
}
266+
}
267+
268+
if (count($failedThresholds)) {
269+
foreach ($this->notificationSettings->webhook_urls as $url) {
270+
Http::post($url['url'], [
271+
'result_id' => $event->result->id,
272+
'site_name' => $this->generalSettings->site_name,
273+
'metrics' => $failedThresholds,
274+
]);
275+
276+
WebhookCall::create()
277+
->url($url['url'])
278+
->payload([
279+
'result_id' => $event->result->id,
280+
'site_name' => $this->generalSettings->site_name,
281+
'metrics' => $failedThresholds,
282+
])
283+
->doNotSign()
284+
->dispatch();
285+
}
286+
}
287+
}
214288
}

app/Settings/NotificationSettings.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ class NotificationSettings extends Settings
2828

2929
public ?array $telegram_recipients;
3030

31+
public bool $webhook_enabled;
32+
33+
public bool $webhook_on_speedtest_run;
34+
35+
public bool $webhook_on_threshold_failure;
36+
37+
public ?array $webhook_urls;
38+
3139
public static function group(): string
3240
{
3341
return 'notification';

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"maatwebsite/excel": "^3.1.52",
2727
"maennchen/zipstream-php": "^2.4",
2828
"spatie/laravel-settings": "^2.8.3",
29+
"spatie/laravel-webhook-server": "^3.8",
2930
"timokoerber/laravel-one-time-operations": "^1.4"
3031
},
3132
"require-dev": {

composer.lock

Lines changed: 75 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)