Skip to content

Commit 9f5fe70

Browse files
committed
Add Support for DingTalk Notifications via Webhook
1 parent 995be29 commit 9f5fe70

File tree

8 files changed

+333
-0
lines changed

8 files changed

+333
-0
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
namespace App\Actions\Notifications;
4+
5+
use Filament\Notifications\Notification;
6+
use Lorisleiva\Actions\Concerns\AsAction;
7+
use Spatie\WebhookServer\WebhookCall;
8+
9+
class SendDingTalkTestNotification
10+
{
11+
use AsAction;
12+
13+
public function handle(array $webhooks)
14+
{
15+
if (! count($webhooks)) {
16+
Notification::make()
17+
->title('You need to add dingtalk urls!')
18+
->warning()
19+
->send();
20+
21+
return;
22+
}
23+
24+
$payload = [
25+
'msgtype' => 'text',
26+
'text' => [
27+
'content' => '👋 Testing the Webhook notification channel on Speedtest Tracker.',
28+
],
29+
];
30+
31+
foreach ($webhooks as $webhook) {
32+
WebhookCall::create()
33+
->url($webhook['url'])
34+
->payload($payload)
35+
->doNotSign()
36+
->dispatch();
37+
}
38+
39+
Notification::make()
40+
->title('Test dingtalk notification sent.')
41+
->success()
42+
->send();
43+
}
44+
}

app/Filament/Pages/Settings/NotificationPage.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace App\Filament\Pages\Settings;
44

55
use App\Actions\Notifications\SendDatabaseTestNotification;
6+
use App\Actions\Notifications\SendDingTalkTestNotification;
67
use App\Actions\Notifications\SendDiscordTestNotification;
78
use App\Actions\Notifications\SendGotifyTestNotification;
89
use App\Actions\Notifications\SendHealthCheckTestNotification;
@@ -517,6 +518,50 @@ public function form(Form $form): Form
517518
'default' => 1,
518519
'md' => 2,
519520
]),
521+
522+
Forms\Components\Section::make('DingTalk')
523+
->schema([
524+
Forms\Components\Toggle::make('dingtalk_enabled')
525+
->label('Enable dingtalk notifications')
526+
->reactive()
527+
->columnSpanFull(),
528+
Forms\Components\Grid::make([
529+
'default' => 1,
530+
])
531+
->hidden(fn (Forms\Get $get) => $get('dingtalk_enabled') !== true)
532+
->schema([
533+
Forms\Components\Fieldset::make('Triggers')
534+
->schema([
535+
Forms\Components\Toggle::make('dingtalk_on_speedtest_run')
536+
->label('Notify on every speedtest run')
537+
->columnSpan(2),
538+
Forms\Components\Toggle::make('dingtalk_on_threshold_failure')
539+
->label('Notify on threshold failures')
540+
->columnSpan(2),
541+
]),
542+
Forms\Components\Repeater::make('dingtalk_webhooks')
543+
->label('Webhooks')
544+
->schema([
545+
Forms\Components\TextInput::make('url')
546+
->placeholder('https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxx')
547+
->maxLength(2000)
548+
->required()
549+
->url(),
550+
])
551+
->columnSpanFull(),
552+
Forms\Components\Actions::make([
553+
Forms\Components\Actions\Action::make('test dingtalk')
554+
->label('Test dingtalk channel')
555+
->action(fn (Forms\Get $get) => SendDingTalkTestNotification::run(webhooks: $get('dingtalk_webhooks')))
556+
->hidden(fn (Forms\Get $get) => ! count($get('dingtalk_webhooks'))),
557+
]),
558+
]),
559+
])
560+
->compact()
561+
->columns([
562+
'default' => 1,
563+
'md' => 2,
564+
]),
520565
])
521566
->columnSpan([
522567
'md' => 2,
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
namespace App\Listeners\DingTalk;
4+
5+
use App\Events\SpeedtestCompleted;
6+
use App\Helpers\Number;
7+
use App\Settings\NotificationSettings;
8+
use Illuminate\Support\Facades\Log;
9+
use Illuminate\Support\Str;
10+
use Spatie\WebhookServer\WebhookCall;
11+
12+
class SendSpeedtestCompletedNotification
13+
{
14+
/**
15+
* Handle the event.
16+
*/
17+
public function handle(SpeedtestCompleted $event): void
18+
{
19+
$notificationSettings = new NotificationSettings;
20+
21+
if (! $notificationSettings->dingtalk_enabled) {
22+
return;
23+
}
24+
25+
if (! $notificationSettings->dingtalk_on_speedtest_run) {
26+
return;
27+
}
28+
29+
if (! count($notificationSettings->dingtalk_webhooks)) {
30+
Log::warning('DingTalk urls not found, check dingtalk notification channel settings.');
31+
32+
return;
33+
}
34+
35+
$payload = [
36+
'msgtype' => 'markdown',
37+
'markdown' => [
38+
'title' => sprintf('Speedtest Completed - #%s', $event->result->id),
39+
'text' => view('dingtalk.speedtest-completed', [
40+
'id' => $event->result->id,
41+
'service' => Str::title($event->result->service->getLabel()),
42+
'serverName' => $event->result->server_name,
43+
'serverId' => $event->result->server_id,
44+
'isp' => $event->result->isp,
45+
'ping' => round($event->result->ping).' ms',
46+
'download' => Number::toBitRate(bits: $event->result->download_bits, precision: 2),
47+
'upload' => Number::toBitRate(bits: $event->result->upload_bits, precision: 2),
48+
'packetLoss' => is_numeric($event->result->packet_loss) ? round($event->result->packet_loss, 2) : 'n/a',
49+
'speedtest_url' => $event->result->result_url,
50+
'url' => url('/admin/results'),
51+
])->render(),
52+
],
53+
];
54+
55+
foreach ($notificationSettings->dingtalk_webhooks as $webhook) {
56+
WebhookCall::create()
57+
->url($webhook['url'])
58+
->payload($payload)
59+
->doNotSign()
60+
->dispatch();
61+
}
62+
}
63+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php
2+
3+
namespace App\Listeners\DingTalk;
4+
5+
use App\Events\SpeedtestCompleted;
6+
use App\Helpers\Number;
7+
use App\Settings\NotificationSettings;
8+
use App\Settings\ThresholdSettings;
9+
use Illuminate\Support\Facades\Log;
10+
use Illuminate\Support\Str;
11+
use Spatie\WebhookServer\WebhookCall;
12+
13+
class SendSpeedtestThresholdNotification
14+
{
15+
/**
16+
* Handle the event.
17+
*/
18+
public function handle(SpeedtestCompleted $event): void
19+
{
20+
$notificationSettings = new NotificationSettings;
21+
22+
if (! $notificationSettings->dingtalk_enabled) {
23+
return;
24+
}
25+
26+
if (! $notificationSettings->dingtalk_on_threshold_failure) {
27+
return;
28+
}
29+
30+
if (! count($notificationSettings->dingtalk_webhooks)) {
31+
Log::warning('DingTalk urls not found, check dingtalk notification channel settings.');
32+
33+
return;
34+
}
35+
36+
$thresholdSettings = new ThresholdSettings;
37+
38+
if (! $thresholdSettings->absolute_enabled) {
39+
return;
40+
}
41+
42+
$failed = [];
43+
44+
if ($thresholdSettings->absolute_download > 0) {
45+
array_push($failed, $this->absoluteDownloadThreshold(event: $event, thresholdSettings: $thresholdSettings));
46+
}
47+
48+
if ($thresholdSettings->absolute_upload > 0) {
49+
array_push($failed, $this->absoluteUploadThreshold(event: $event, thresholdSettings: $thresholdSettings));
50+
}
51+
52+
if ($thresholdSettings->absolute_ping > 0) {
53+
array_push($failed, $this->absolutePingThreshold(event: $event, thresholdSettings: $thresholdSettings));
54+
}
55+
56+
$failed = array_filter($failed);
57+
58+
if (! count($failed)) {
59+
Log::warning('Failed dingtalk thresholds not found, won\'t send notification.');
60+
61+
return;
62+
}
63+
64+
$payload = [
65+
'msgtype' => 'markdown',
66+
'markdown' => [
67+
'title' => sprintf('Speedtest Threshold Breached - #%s', $event->result->id),
68+
'text' => view('dingtalk.speedtest-threshold', [
69+
'id' => $event->result->id,
70+
'service' => Str::title($event->result->service->getLabel()),
71+
'serverName' => $event->result->server_name,
72+
'serverId' => $event->result->server_id,
73+
'isp' => $event->result->isp,
74+
'metrics' => $failed,
75+
'speedtest_url' => $event->result->result_url,
76+
'url' => url('/admin/results'),
77+
])->render(),
78+
],
79+
];
80+
81+
foreach ($notificationSettings->dingtalk_webhooks as $webhook) {
82+
WebhookCall::create()
83+
->url($webhook['url'])
84+
->payload($payload)
85+
->doNotSign()
86+
->dispatch();
87+
}
88+
}
89+
90+
/**
91+
* Build webhook notification if absolute download threshold is breached.
92+
*/
93+
protected function absoluteDownloadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array
94+
{
95+
if (! absoluteDownloadThresholdFailed($thresholdSettings->absolute_download, $event->result->download)) {
96+
return false;
97+
}
98+
99+
return [
100+
'name' => 'Download',
101+
'threshold' => $thresholdSettings->absolute_download.' Mbps',
102+
'value' => Number::toBitRate(bits: $event->result->download_bits, precision: 2),
103+
];
104+
}
105+
106+
/**
107+
* Build webhook notification if absolute upload threshold is breached.
108+
*/
109+
protected function absoluteUploadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array
110+
{
111+
if (! absoluteUploadThresholdFailed($thresholdSettings->absolute_upload, $event->result->upload)) {
112+
return false;
113+
}
114+
115+
return [
116+
'name' => 'Upload',
117+
'threshold' => $thresholdSettings->absolute_upload.' Mbps',
118+
'value' => Number::toBitRate(bits: $event->result->upload_bits, precision: 2),
119+
];
120+
}
121+
122+
/**
123+
* Build webhook notification if absolute ping threshold is breached.
124+
*/
125+
protected function absolutePingThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): bool|array
126+
{
127+
if (! absolutePingThresholdFailed($thresholdSettings->absolute_ping, $event->result->ping)) {
128+
return false;
129+
}
130+
131+
return [
132+
'name' => 'Ping',
133+
'threshold' => $thresholdSettings->absolute_ping.' ms',
134+
'value' => round($event->result->ping, 2).' ms',
135+
];
136+
}
137+
}

app/Settings/NotificationSettings.php

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

8787
public ?array $gotify_webhooks;
8888

89+
public bool $dingtalk_enabled;
90+
91+
public bool $dingtalk_on_speedtest_run;
92+
93+
public bool $dingtalk_on_threshold_failure;
94+
95+
public ?array $dingtalk_webhooks;
96+
8997
public static function group(): string
9098
{
9199
return 'notification';
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
use Spatie\LaravelSettings\Migrations\SettingsMigration;
4+
5+
return new class extends SettingsMigration
6+
{
7+
public function up(): void
8+
{
9+
$this->migrator->add('notification.dingtalk_enabled', false);
10+
$this->migrator->add('notification.dingtalk_on_speedtest_run', false);
11+
$this->migrator->add('notification.dingtalk_on_threshold_failure', false);
12+
$this->migrator->add('notification.dingtalk_webhooks', null);
13+
}
14+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
*Speedtest Completed - #{{ $id }}*
2+
3+
A new speedtest on *{{ config('app.name') }}* was completed using *{{ $service }}*.
4+
5+
- *Server name:* {{ $serverName }}
6+
- *Server ID:* {{ $serverId }}
7+
- **ISP:** {{ $isp }}
8+
- *Ping:* {{ $ping }}
9+
- *Download:* {{ $download }}
10+
- *Upload:* {{ $upload }}
11+
- **Packet Loss:** {{ $packetLoss }}**%**
12+
- **Ookla Speedtest:** {{ $speedtest_url }}
13+
- **URL:** {{ $url }}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
**Speedtest Threshold Breached - #{{ $id }}**
2+
3+
A new speedtest on **{{ config('app.name') }}** was completed using **{{ $service }}** on **{{ $isp }}** but a threshold was breached.
4+
5+
@foreach ($metrics as $item)
6+
- **{{ $item['name'] }}** {{ $item['threshold'] }}: {{ $item['value'] }}
7+
@endforeach
8+
- **Ookla Speedtest:** {{ $speedtest_url }}
9+
- **URL:** {{ $url }}

0 commit comments

Comments
 (0)