mirror of
https://github.com/alexjustesen/speedtest-tracker.git
synced 2026-06-23 04:40:08 +00:00
Refactored database, mail and webhook notifications (#2439)
Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Actions\Notifications;
|
||||
|
||||
use App\Mail\Test as TestMail;
|
||||
use App\Mail\TestMail;
|
||||
use Filament\Notifications\Notification;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
@@ -13,7 +13,9 @@ use App\Actions\Notifications\SendSlackTestNotification;
|
||||
use App\Actions\Notifications\SendTelegramTestNotification;
|
||||
use App\Actions\Notifications\SendWebhookTestNotification;
|
||||
use App\Settings\NotificationSettings;
|
||||
use CodeWithDennis\SimpleAlert\Components\SimpleAlert;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
@@ -22,13 +24,16 @@ use Filament\Schemas\Components\Actions;
|
||||
use Filament\Schemas\Components\Fieldset;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class Notification extends SettingsPage
|
||||
{
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-bell';
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-bell-alert';
|
||||
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Settings';
|
||||
|
||||
@@ -60,6 +65,135 @@ class Notification extends SettingsPage
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Tabs::make()
|
||||
->schema([
|
||||
Tab::make(__('settings/notifications.database'))
|
||||
->icon(Heroicon::OutlinedCircleStack)
|
||||
->schema([
|
||||
Toggle::make('database_enabled')
|
||||
->label(__('general.enable'))
|
||||
->live(),
|
||||
|
||||
Grid::make([
|
||||
'default' => 1,
|
||||
])
|
||||
->hidden(fn (Get $get) => $get('database_enabled') !== true)
|
||||
->schema([
|
||||
Fieldset::make(__('settings.triggers'))
|
||||
->columns(1)
|
||||
->schema([
|
||||
Checkbox::make('database_on_speedtest_run')
|
||||
->label(__('settings/notifications.database_on_speedtest_run')),
|
||||
|
||||
Checkbox::make('database_on_threshold_failure')
|
||||
->label(__('settings/notifications.database_on_threshold_failure')),
|
||||
]),
|
||||
|
||||
Actions::make([
|
||||
Action::make('test database')
|
||||
->label(__('settings/notifications.test_database_channel'))
|
||||
->action(fn () => SendDatabaseTestNotification::run(user: Auth::user())),
|
||||
]),
|
||||
]),
|
||||
|
||||
// ...
|
||||
]),
|
||||
|
||||
Tab::make(__('settings/notifications.mail'))
|
||||
->icon(Heroicon::OutlinedEnvelope)
|
||||
->schema([
|
||||
Toggle::make('mail_enabled')
|
||||
->label(__('general.enable'))
|
||||
->live(),
|
||||
|
||||
Grid::make([
|
||||
'default' => 1,
|
||||
])
|
||||
->hidden(fn (Get $get) => $get('mail_enabled') !== true)
|
||||
->schema([
|
||||
Fieldset::make(__('settings.triggers'))
|
||||
->columns(1)
|
||||
->schema([
|
||||
Checkbox::make('mail_on_speedtest_run')
|
||||
->label(__('settings/notifications.mail_on_speedtest_run')),
|
||||
|
||||
Checkbox::make('mail_on_threshold_failure')
|
||||
->label(__('settings/notifications.mail_on_threshold_failure')),
|
||||
]),
|
||||
|
||||
Repeater::make('mail_recipients')
|
||||
->label(__('settings/notifications.recipients'))
|
||||
->schema([
|
||||
TextInput::make('email_address')
|
||||
->placeholder('your@email.com')
|
||||
->email()
|
||||
->required(),
|
||||
]),
|
||||
|
||||
Actions::make([
|
||||
Action::make('test mail')
|
||||
->label(__('settings/notifications.test_mail_channel'))
|
||||
->action(fn (Get $get) => SendMailTestNotification::run(recipients: $get('mail_recipients')))
|
||||
->hidden(fn (Get $get) => ! count($get('mail_recipients'))),
|
||||
]),
|
||||
]),
|
||||
|
||||
// ...
|
||||
]),
|
||||
|
||||
Tab::make(__('settings/notifications.webhook'))
|
||||
->icon(Heroicon::OutlinedGlobeAlt)
|
||||
->schema([
|
||||
Toggle::make('webhook_enabled')
|
||||
->label(__('general.enable'))
|
||||
->live(),
|
||||
|
||||
Grid::make([
|
||||
'default' => 1,
|
||||
])
|
||||
->hidden(fn (Get $get) => $get('webhook_enabled') !== true)
|
||||
->schema([
|
||||
Fieldset::make(__('settings.triggers'))
|
||||
->columns(1)
|
||||
->schema([
|
||||
Checkbox::make('webhook_on_speedtest_run')
|
||||
->label(__('settings/notifications.webhook_on_speedtest_run')),
|
||||
|
||||
Checkbox::make('webhook_on_threshold_failure')
|
||||
->label(__('settings/notifications.webhook_on_threshold_failure')),
|
||||
]),
|
||||
|
||||
Repeater::make('webhook_urls')
|
||||
->label(__('settings/notifications.recipients'))
|
||||
->schema([
|
||||
TextInput::make('url')
|
||||
->placeholder('https://webhook.site/longstringofcharacters')
|
||||
->maxLength(2000)
|
||||
->required()
|
||||
->url(),
|
||||
]),
|
||||
|
||||
Actions::make([
|
||||
Action::make('test webhook')
|
||||
->label(__('settings/notifications.test_webhook_channel'))
|
||||
->action(fn (Get $get) => SendWebhookTestNotification::run(webhooks: $get('webhook_urls')))
|
||||
->hidden(fn (Get $get) => ! count($get('webhook_urls'))),
|
||||
]),
|
||||
]),
|
||||
|
||||
// ...
|
||||
]),
|
||||
])
|
||||
->columnSpanFull(),
|
||||
|
||||
// ! DEPRECATED CHANNELS
|
||||
SimpleAlert::make('deprecation_warning')
|
||||
->title('Deprecated Notification Channels')
|
||||
->description('The following notification channels are deprecated and will be removed in a future release!')
|
||||
->border()
|
||||
->warning()
|
||||
->columnSpanFull(),
|
||||
|
||||
Grid::make([
|
||||
'default' => 1,
|
||||
'md' => 3,
|
||||
@@ -70,118 +204,6 @@ class Notification extends SettingsPage
|
||||
'default' => 1,
|
||||
])
|
||||
->schema([
|
||||
Section::make(__('settings/notifications.database'))
|
||||
->description(__('settings/notifications.database_description'))
|
||||
->schema([
|
||||
Toggle::make('database_enabled')
|
||||
->label(__('settings/notifications.enable_database_notifications'))
|
||||
->reactive()
|
||||
->columnSpanFull(),
|
||||
Grid::make([
|
||||
'default' => 1,
|
||||
])
|
||||
->hidden(fn (Get $get) => $get('database_enabled') !== true)
|
||||
->schema([
|
||||
Fieldset::make(__('settings.triggers'))
|
||||
->schema([
|
||||
Toggle::make('database_on_speedtest_run')
|
||||
->label(__('settings/notifications.database_on_speedtest_run'))
|
||||
->columnSpanFull(),
|
||||
Toggle::make('database_on_threshold_failure')
|
||||
->label(__('settings/notifications.database_on_threshold_failure'))
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
Actions::make([
|
||||
Action::make('test database')
|
||||
->label(__('settings/notifications.test_database_channel'))
|
||||
->action(fn () => SendDatabaseTestNotification::run(user: Auth::user())),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
->compact()
|
||||
->columnSpan('full'),
|
||||
|
||||
Section::make(__('settings/notifications.mail'))
|
||||
->schema([
|
||||
Toggle::make('mail_enabled')
|
||||
->label(__('settings/notifications.enable_mail_notifications'))
|
||||
->reactive()
|
||||
->columnSpanFull(),
|
||||
Grid::make([
|
||||
'default' => 1,
|
||||
])
|
||||
->hidden(fn (Get $get) => $get('mail_enabled') !== true)
|
||||
->schema([
|
||||
Fieldset::make(__('settings.triggers'))
|
||||
->schema([
|
||||
Toggle::make('mail_on_speedtest_run')
|
||||
->label(__('settings/notifications.mail_on_speedtest_run'))
|
||||
->columnSpanFull(),
|
||||
Toggle::make('mail_on_threshold_failure')
|
||||
->label(__('settings/notifications.mail_on_threshold_failure'))
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
Repeater::make('mail_recipients')
|
||||
->label(__('settings/notifications.recipients'))
|
||||
->schema([
|
||||
TextInput::make('email_address')
|
||||
->placeholder('your@email.com')
|
||||
->email()
|
||||
->required(),
|
||||
])
|
||||
->columnSpanFull(),
|
||||
Actions::make([
|
||||
Action::make('test mail')
|
||||
->label(__('settings/notifications.test_mail_channel'))
|
||||
->action(fn (Get $get) => SendMailTestNotification::run(recipients: $get('mail_recipients')))
|
||||
->hidden(fn (Get $get) => ! count($get('mail_recipients'))),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
->compact()
|
||||
->columnSpan('full'),
|
||||
|
||||
Section::make(__('settings/notifications.webhook'))
|
||||
->schema([
|
||||
Toggle::make('webhook_enabled')
|
||||
->label(__('settings/notifications.enable_webhook_notifications'))
|
||||
->reactive()
|
||||
->columnSpanFull(),
|
||||
Grid::make([
|
||||
'default' => 1,
|
||||
])
|
||||
->hidden(fn (Get $get) => $get('webhook_enabled') !== true)
|
||||
->schema([
|
||||
Fieldset::make(__('settings.triggers'))
|
||||
->schema([
|
||||
Toggle::make('webhook_on_speedtest_run')
|
||||
->label(__('settings/notifications.webhook_on_speedtest_run'))
|
||||
->columnSpan(2),
|
||||
Toggle::make('webhook_on_threshold_failure')
|
||||
->label(__('settings/notifications.webhook_on_threshold_failure'))
|
||||
->columnSpan(2),
|
||||
]),
|
||||
Repeater::make('webhook_urls')
|
||||
->label(__('settings/notifications.recipients'))
|
||||
->schema([
|
||||
TextInput::make('url')
|
||||
->placeholder('https://webhook.site/longstringofcharacters')
|
||||
->maxLength(2000)
|
||||
->required()
|
||||
->url(),
|
||||
])
|
||||
->columnSpanFull(),
|
||||
Actions::make([
|
||||
Action::make('test webhook')
|
||||
->label(__('settings/notifications.test_webhook_channel'))
|
||||
->action(fn (Get $get) => SendWebhookTestNotification::run(webhooks: $get('webhook_urls')))
|
||||
->hidden(fn (Get $get) => ! count($get('webhook_urls'))),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
->compact()
|
||||
->columnSpan('full'),
|
||||
|
||||
Section::make('Pushover')
|
||||
->description('⚠️ Pushover is deprecated and will be removed in a future release.')
|
||||
->schema([
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners\Database;
|
||||
|
||||
use App\Events\SpeedtestCompleted;
|
||||
use App\Models\User;
|
||||
use App\Settings\NotificationSettings;
|
||||
use Filament\Notifications\Notification;
|
||||
|
||||
class SendSpeedtestCompletedNotification
|
||||
{
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(SpeedtestCompleted $event): void
|
||||
{
|
||||
$notificationSettings = new NotificationSettings;
|
||||
|
||||
if (! $notificationSettings->database_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $notificationSettings->database_on_speedtest_run) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (User::all() as $user) {
|
||||
Notification::make()
|
||||
->title(__('results.speedtest_completed'))
|
||||
->success()
|
||||
->sendToDatabase($user);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners\Database;
|
||||
|
||||
use App\Events\SpeedtestCompleted;
|
||||
use App\Helpers\Number;
|
||||
use App\Models\User;
|
||||
use App\Settings\NotificationSettings;
|
||||
use App\Settings\ThresholdSettings;
|
||||
use Filament\Notifications\Notification;
|
||||
|
||||
class SendSpeedtestThresholdNotification
|
||||
{
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(SpeedtestCompleted $event): void
|
||||
{
|
||||
$notificationSettings = new NotificationSettings;
|
||||
|
||||
if (! $notificationSettings->database_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $notificationSettings->database_on_threshold_failure) {
|
||||
return;
|
||||
}
|
||||
|
||||
$thresholdSettings = new ThresholdSettings;
|
||||
|
||||
if (! $thresholdSettings->absolute_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($thresholdSettings->absolute_download > 0) {
|
||||
$this->absoluteDownloadThreshold(event: $event, thresholdSettings: $thresholdSettings);
|
||||
}
|
||||
|
||||
if ($thresholdSettings->absolute_upload > 0) {
|
||||
$this->absoluteUploadThreshold(event: $event, thresholdSettings: $thresholdSettings);
|
||||
}
|
||||
|
||||
if ($thresholdSettings->absolute_ping > 0) {
|
||||
$this->absolutePingThreshold(event: $event, thresholdSettings: $thresholdSettings);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send database notification if absolute download threshold is breached.
|
||||
*/
|
||||
protected function absoluteDownloadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): void
|
||||
{
|
||||
if (! absoluteDownloadThresholdFailed($thresholdSettings->absolute_download, $event->result->download)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (User::all() as $user) {
|
||||
Notification::make()
|
||||
->title(__('results.download_threshold_breached'))
|
||||
->body('Speedtest #'.$event->result->id.' breached the download threshold of '.$thresholdSettings->absolute_download.' Mbps at '.Number::toBitRate($event->result->download_bits).'.')
|
||||
->warning()
|
||||
->sendToDatabase($user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send database notification if absolute upload threshold is breached.
|
||||
*/
|
||||
protected function absoluteUploadThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): void
|
||||
{
|
||||
if (! absoluteUploadThresholdFailed($thresholdSettings->absolute_upload, $event->result->upload)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (User::all() as $user) {
|
||||
Notification::make()
|
||||
->title(__('results.upload_threshold_breached'))
|
||||
->body('Speedtest #'.$event->result->id.' breached the upload threshold of '.$thresholdSettings->absolute_upload.' Mbps at '.Number::toBitRate($event->result->upload_bits).'.')
|
||||
->warning()
|
||||
->sendToDatabase($user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send database notification if absolute upload threshold is breached.
|
||||
*/
|
||||
protected function absolutePingThreshold(SpeedtestCompleted $event, ThresholdSettings $thresholdSettings): void
|
||||
{
|
||||
if (! absolutePingThresholdFailed($thresholdSettings->absolute_ping, $event->result->ping)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (User::all() as $user) {
|
||||
Notification::make()
|
||||
->title(__('results.ping_threshold_breached'))
|
||||
->body('Speedtest #'.$event->result->id.' breached the ping threshold of '.$thresholdSettings->absolute_ping.'ms at '.$event->result->ping.'ms.')
|
||||
->warning()
|
||||
->sendToDatabase($user);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners\Mail;
|
||||
|
||||
use App\Events\SpeedtestCompleted;
|
||||
use App\Mail\SpeedtestCompletedMail;
|
||||
use App\Settings\NotificationSettings;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class SendSpeedtestCompletedNotification
|
||||
{
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(SpeedtestCompleted $event): void
|
||||
{
|
||||
$notificationSettings = new NotificationSettings;
|
||||
|
||||
if (! $notificationSettings->mail_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $notificationSettings->mail_on_speedtest_run) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! count($notificationSettings->mail_recipients)) {
|
||||
Log::warning('Mail recipients not found, check mail notification channel settings.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($notificationSettings->mail_recipients as $recipient) {
|
||||
Mail::to($recipient)
|
||||
->send(new SpeedtestCompletedMail($event->result));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners\Mail;
|
||||
|
||||
use App\Events\SpeedtestCompleted;
|
||||
use App\Helpers\Number;
|
||||
use App\Mail\SpeedtestThresholdMail;
|
||||
use App\Settings\NotificationSettings;
|
||||
use App\Settings\ThresholdSettings;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class SendSpeedtestThresholdNotification
|
||||
{
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(SpeedtestCompleted $event): void
|
||||
{
|
||||
$notificationSettings = new NotificationSettings;
|
||||
|
||||
if (! $notificationSettings->mail_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $notificationSettings->mail_on_threshold_failure) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! count($notificationSettings->mail_recipients) > 0) {
|
||||
Log::warning('Mail recipients not found, check mail 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 mail thresholds not found, won\'t send notification.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($notificationSettings->mail_recipients as $recipient) {
|
||||
Mail::to($recipient)
|
||||
->send(new SpeedtestThresholdMail($event->result, $failed));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build mail 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 mail 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 mail 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',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,23 @@
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\SpeedtestCompleted;
|
||||
use App\Mail\CompletedSpeedtestMail;
|
||||
use App\Models\Result;
|
||||
use App\Models\User;
|
||||
use App\Settings\NotificationSettings;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Spatie\WebhookServer\WebhookCall;
|
||||
|
||||
class ProcessCompletedSpeedtest
|
||||
{
|
||||
public function __construct(
|
||||
public NotificationSettings $notificationSettings,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
@@ -17,8 +27,53 @@ class ProcessCompletedSpeedtest
|
||||
{
|
||||
$result = $event->result;
|
||||
|
||||
if ($result->dispatched_by && ! $result->scheduled) {
|
||||
$result->loadMissing(['dispatchedBy']);
|
||||
|
||||
// $this->notifyAppriseChannels($result);
|
||||
$this->notifyDatabaseChannels($result);
|
||||
$this->notifyDispatchingUser($result);
|
||||
$this->notifyMailChannels($result);
|
||||
$this->notifyWebhookChannels($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify Apprise channels.
|
||||
*/
|
||||
private function notifyAppriseChannels(Result $result): void
|
||||
{
|
||||
// Don't send Apprise notification if dispatched by a user or test is unhealthy.
|
||||
if (filled($result->dispatched_by) || ! $result->healthy) {
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify database channels.
|
||||
*/
|
||||
private function notifyDatabaseChannels(Result $result): void
|
||||
{
|
||||
// Don't send database notification if dispatched by a user or test is unhealthy.
|
||||
if (filled($result->dispatched_by) || ! $result->healthy) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if database notifications are enabled.
|
||||
if (! $this->notificationSettings->database_enabled || ! $this->notificationSettings->database_on_speedtest_run) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (User::all() as $user) {
|
||||
Notification::make()
|
||||
->title(__('results.speedtest_completed'))
|
||||
->actions([
|
||||
Action::make('view')
|
||||
->label(__('general.view'))
|
||||
->url(route('filament.admin.resources.results.index')),
|
||||
])
|
||||
->success()
|
||||
->sendToDatabase($user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +82,11 @@ class ProcessCompletedSpeedtest
|
||||
*/
|
||||
private function notifyDispatchingUser(Result $result): void
|
||||
{
|
||||
$user = User::find($result->dispatched_by);
|
||||
if (empty($result->dispatched_by) || ! $result->healthy) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user->notify(
|
||||
$result->dispatchedBy->notify(
|
||||
Notification::make()
|
||||
->title(__('results.speedtest_completed'))
|
||||
->actions([
|
||||
@@ -41,4 +98,72 @@ class ProcessCompletedSpeedtest
|
||||
->toDatabase(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify mail channels.
|
||||
*/
|
||||
private function notifyMailChannels(Result $result): void
|
||||
{
|
||||
if (empty($result->dispatched_by) || ! $result->healthy) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->notificationSettings->mail_enabled || ! $this->notificationSettings->mail_on_speedtest_run) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! count($this->notificationSettings->mail_recipients)) {
|
||||
Log::warning('Mail recipients not found, check mail notification channel settings.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->notificationSettings->mail_recipients as $recipient) {
|
||||
Mail::to($recipient)
|
||||
->send(new CompletedSpeedtestMail($result));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify webhook channels.
|
||||
*/
|
||||
private function notifyWebhookChannels(Result $result): void
|
||||
{
|
||||
// Don't send webhook if dispatched by a user or test is unhealthy.
|
||||
if (filled($result->dispatched_by) || ! $result->healthy) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if webhook notifications are enabled.
|
||||
if (! $this->notificationSettings->webhook_enabled || ! $this->notificationSettings->webhook_on_speedtest_run) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if webhook urls are configured.
|
||||
if (! count($this->notificationSettings->webhook_urls)) {
|
||||
Log::warning('Webhook urls not found, check webhook notification channel settings.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->notificationSettings->webhook_urls as $url) {
|
||||
WebhookCall::create()
|
||||
->url($url['url'])
|
||||
->payload([
|
||||
'result_id' => $result->id,
|
||||
'site_name' => config('app.name'),
|
||||
'server_name' => Arr::get($result->data, 'server.name'),
|
||||
'server_id' => Arr::get($result->data, 'server.id'),
|
||||
'isp' => Arr::get($result->data, 'isp'),
|
||||
'ping' => $result->ping,
|
||||
'download' => $result->downloadBits,
|
||||
'upload' => $result->uploadBits,
|
||||
'packet_loss' => Arr::get($result->data, 'packetLoss'),
|
||||
'speedtest_url' => Arr::get($result->data, 'result.url'),
|
||||
'url' => url('/admin/results'),
|
||||
])
|
||||
->doNotSign()
|
||||
->dispatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace App\Listeners;
|
||||
|
||||
use App\Events\SpeedtestFailed;
|
||||
use App\Models\Result;
|
||||
use App\Models\User;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
|
||||
@@ -17,9 +16,23 @@ class ProcessFailedSpeedtest
|
||||
{
|
||||
$result = $event->result;
|
||||
|
||||
if ($result->dispatched_by && ! $result->scheduled) {
|
||||
$result->loadMissing(['dispatchedBy']);
|
||||
|
||||
// $this->notifyAppriseChannels($result);
|
||||
$this->notifyDispatchingUser($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify Apprise channels.
|
||||
*/
|
||||
private function notifyAppriseChannels(Result $result): void
|
||||
{
|
||||
// Don't send Apprise notification if dispatched by a user or test is unhealthy.
|
||||
if (filled($result->dispatched_by) || ! $result->healthy) {
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -27,9 +40,11 @@ class ProcessFailedSpeedtest
|
||||
*/
|
||||
private function notifyDispatchingUser(Result $result): void
|
||||
{
|
||||
$user = User::find($result->dispatched_by);
|
||||
if (empty($result->dispatched_by)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user->notify(
|
||||
$result->dispatchedBy->notify(
|
||||
Notification::make()
|
||||
->title(__('results.speedtest_failed'))
|
||||
->actions([
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\SpeedtestBenchmarkFailed;
|
||||
use App\Mail\UnhealthySpeedtestMail;
|
||||
use App\Models\Result;
|
||||
use App\Models\User;
|
||||
use App\Settings\NotificationSettings;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Spatie\WebhookServer\WebhookCall;
|
||||
|
||||
class ProcessUnhealthySpeedtest
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*/
|
||||
public function __construct(
|
||||
public NotificationSettings $notificationSettings,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(SpeedtestBenchmarkFailed $event): void
|
||||
{
|
||||
$result = $event->result;
|
||||
|
||||
$result->loadMissing(['dispatchedBy']);
|
||||
|
||||
// $this->notifyAppriseChannels($result);
|
||||
$this->notifyDatabaseChannels($result);
|
||||
$this->notifyDispatchingUser($result);
|
||||
$this->notifyMailChannels($result);
|
||||
$this->notifyWebhookChannels($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify Apprise channels.
|
||||
*/
|
||||
private function notifyAppriseChannels(Result $result): void
|
||||
{
|
||||
// Don't send Apprise notification if dispatched by a user.
|
||||
if (filled($result->dispatched_by)) {
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify database channels.
|
||||
*/
|
||||
private function notifyDatabaseChannels(Result $result): void
|
||||
{
|
||||
// Don't send database notification if dispatched by a user.
|
||||
if (filled($result->dispatched_by)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if database notifications are enabled.
|
||||
if (! $this->notificationSettings->database_enabled || ! $this->notificationSettings->database_on_threshold_failure) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (User::all() as $user) {
|
||||
Notification::make()
|
||||
->title(__('results.speedtest_benchmark_failed'))
|
||||
->actions([
|
||||
Action::make('view')
|
||||
->label(__('general.view'))
|
||||
->url(route('filament.admin.resources.results.index')),
|
||||
])
|
||||
->success()
|
||||
->sendToDatabase($user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the user who dispatched the speedtest.
|
||||
*/
|
||||
private function notifyDispatchingUser(Result $result): void
|
||||
{
|
||||
if (empty($result->dispatched_by)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$result->dispatchedBy->notify(
|
||||
Notification::make()
|
||||
->title(__('results.speedtest_benchmark_failed'))
|
||||
->actions([
|
||||
Action::make('view')
|
||||
->label(__('general.view'))
|
||||
->url(route('filament.admin.resources.results.index')),
|
||||
])
|
||||
->warning()
|
||||
->toDatabase(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify mail channels.
|
||||
*/
|
||||
private function notifyMailChannels(Result $result): void
|
||||
{
|
||||
// Don't send webhook if dispatched by a user.
|
||||
if (filled($result->dispatched_by)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if mail notifications are enabled.
|
||||
if (! $this->notificationSettings->mail_enabled || ! $this->notificationSettings->mail_on_threshold_failure) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if mail recipients are configured.
|
||||
if (! count($this->notificationSettings->mail_recipients)) {
|
||||
Log::warning('Mail recipients not found, check mail notification channel settings.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->notificationSettings->mail_recipients as $recipient) {
|
||||
Mail::to($recipient)
|
||||
->send(new UnhealthySpeedtestMail($result));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify webhook channels.
|
||||
*/
|
||||
private function notifyWebhookChannels(Result $result): void
|
||||
{
|
||||
// Don't send webhook if dispatched by a user.
|
||||
if (filled($result->dispatched_by)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if webhook notifications are enabled.
|
||||
if (! $this->notificationSettings->webhook_enabled || ! $this->notificationSettings->webhook_on_threshold_failure) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if webhook urls are configured.
|
||||
if (! count($this->notificationSettings->webhook_urls)) {
|
||||
Log::warning('Webhook urls not found, check webhook notification channel settings.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->notificationSettings->webhook_urls as $url) {
|
||||
WebhookCall::create()
|
||||
->url($url['url'])
|
||||
->payload([
|
||||
'result_id' => $result->id,
|
||||
'site_name' => config('app.name'),
|
||||
'isp' => $result->isp,
|
||||
'benchmarks' => $result->benchmarks,
|
||||
'speedtest_url' => $result->result_url,
|
||||
'url' => url('/admin/results'),
|
||||
])
|
||||
->doNotSign()
|
||||
->dispatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners\Webhook;
|
||||
|
||||
use App\Events\SpeedtestCompleted;
|
||||
use App\Settings\NotificationSettings;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Spatie\WebhookServer\WebhookCall;
|
||||
|
||||
class SendSpeedtestCompletedNotification
|
||||
{
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(SpeedtestCompleted $event): void
|
||||
{
|
||||
$notificationSettings = new NotificationSettings;
|
||||
|
||||
if (! $notificationSettings->webhook_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $notificationSettings->webhook_on_speedtest_run) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! count($notificationSettings->webhook_urls)) {
|
||||
Log::warning('Webhook urls not found, check webhook notification channel settings.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($notificationSettings->webhook_urls as $url) {
|
||||
WebhookCall::create()
|
||||
->url($url['url'])
|
||||
->payload([
|
||||
'result_id' => $event->result->id,
|
||||
'site_name' => config('app.name'),
|
||||
'server_name' => Arr::get($event->result->data, 'server.name'),
|
||||
'server_id' => Arr::get($event->result->data, 'server.id'),
|
||||
'isp' => Arr::get($event->result->data, 'isp'),
|
||||
'ping' => $event->result->ping,
|
||||
'download' => $event->result->downloadBits,
|
||||
'upload' => $event->result->uploadBits,
|
||||
'packet_loss' => Arr::get($event->result->data, 'packetLoss'),
|
||||
'speedtest_url' => Arr::get($event->result->data, 'result.url'),
|
||||
'url' => url('/admin/results'),
|
||||
])
|
||||
->doNotSign()
|
||||
->dispatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners\Webhook;
|
||||
|
||||
use App\Events\SpeedtestCompleted;
|
||||
use App\Helpers\Number;
|
||||
use App\Settings\NotificationSettings;
|
||||
use App\Settings\ThresholdSettings;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Spatie\WebhookServer\WebhookCall;
|
||||
|
||||
class SendSpeedtestThresholdNotification
|
||||
{
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(SpeedtestCompleted $event): void
|
||||
{
|
||||
$notificationSettings = new NotificationSettings;
|
||||
|
||||
if (! $notificationSettings->webhook_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $notificationSettings->webhook_on_threshold_failure) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! count($notificationSettings->webhook_urls)) {
|
||||
Log::warning('Webhook urls not found, check webhook 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 webhook thresholds not found, won\'t send notification.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($notificationSettings->webhook_urls as $url) {
|
||||
WebhookCall::create()
|
||||
->url($url['url'])
|
||||
->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 webhook 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 webhook 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 webhook 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',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SpeedtestCompletedMail extends Mailable implements ShouldQueue
|
||||
class CompletedSpeedtestMail extends Mailable implements ShouldQueue
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
@@ -41,7 +41,7 @@ class SpeedtestCompletedMail extends Mailable implements ShouldQueue
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
markdown: 'emails.speedtest-completed',
|
||||
markdown: 'mail.speedtest.completed',
|
||||
with: [
|
||||
'id' => $this->result->id,
|
||||
'service' => Str::title($this->result->service->getLabel()),
|
||||
@@ -1,57 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\Result;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SpeedtestThresholdMail extends Mailable implements ShouldQueue
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
public Result $result,
|
||||
public array $metrics,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*/
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
return new Envelope(
|
||||
subject: 'Speedtest Threshold Breached - #'.$this->result->id,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*/
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
markdown: 'emails.speedtest-threshold',
|
||||
with: [
|
||||
'id' => $this->result->id,
|
||||
'service' => Str::title($this->result->service->getLabel()),
|
||||
'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,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Test extends Mailable implements ShouldQueue
|
||||
class TestMail extends Mailable implements ShouldQueue
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
@@ -29,7 +29,7 @@ class Test extends Mailable implements ShouldQueue
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
markdown: 'emails.test',
|
||||
markdown: 'mail.test',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Helpers\Number;
|
||||
use App\Models\Result;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class UnhealthySpeedtestMail extends Mailable implements ShouldQueue
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public Result $result,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*/
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
return new Envelope(
|
||||
subject: 'Speedtest Threshold Breached - #'.$this->result->id,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*/
|
||||
public function content(): Content
|
||||
{
|
||||
$benchmarks = [];
|
||||
|
||||
foreach ($this->result->benchmarks as $metric => $benchmark) {
|
||||
$benchmarks[] = $this->formatBenchmark($metric, $benchmark);
|
||||
}
|
||||
|
||||
return new Content(
|
||||
markdown: 'mail.speedtest.unhealthy',
|
||||
with: [
|
||||
'id' => $this->result->id,
|
||||
'service' => str($this->result->service->getLabel())->title(),
|
||||
'isp' => $this->result->isp,
|
||||
'url' => url('/admin/results'),
|
||||
'benchmarks' => $benchmarks,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a benchmark for display in the email.
|
||||
*/
|
||||
private function formatBenchmark(string $metric, array $benchmark): array
|
||||
{
|
||||
$metricName = str($metric)->title();
|
||||
$type = str($benchmark['type'])->title();
|
||||
$thresholdValue = $benchmark['value'].' '.str($benchmark['unit'])->title();
|
||||
|
||||
// Get the actual result value
|
||||
$resultValue = match ($metric) {
|
||||
'download' => Number::toBitRate($this->result->download_bits, 2),
|
||||
'upload' => Number::toBitRate($this->result->upload_bits, 2),
|
||||
'ping' => round(Number::castToType($this->result->ping, 'float'), 2).' ms',
|
||||
default => 'N/A',
|
||||
};
|
||||
|
||||
return [
|
||||
'metric' => $metricName,
|
||||
'type' => $type,
|
||||
'threshold_value' => $thresholdValue,
|
||||
'result_value' => $resultValue,
|
||||
'passed' => $benchmark['passed'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ class AdminPanelProvider extends PanelProvider
|
||||
->colors([
|
||||
'primary' => Color::Amber,
|
||||
])
|
||||
->viteTheme('resources/css/filament/admin/theme.css')
|
||||
->favicon(asset('img/speedtest-tracker-icon.png'))
|
||||
->sidebarCollapsibleOnDesktop()
|
||||
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
|
||||
@@ -40,6 +41,7 @@ class AdminPanelProvider extends PanelProvider
|
||||
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
|
||||
->widgets([])
|
||||
->databaseNotifications()
|
||||
->databaseNotificationsPolling('5s')
|
||||
->maxContentWidth(config('speedtest.content_width'))
|
||||
->middleware([
|
||||
EncryptCookies::class,
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"chrisullyott/php-filesize": "^4.2.1",
|
||||
"codewithdennis/filament-simple-alert": "^4.0.2",
|
||||
"dragonmantank/cron-expression": "^3.6.0",
|
||||
"filament/filament": "4.1.0",
|
||||
"filament/spatie-laravel-settings-plugin": "^4.1",
|
||||
|
||||
Generated
+75
-2
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "39020dcee9d9965e781ef550aca663ac",
|
||||
"content-hash": "3aff9923fe99afc6088082ec8c3be834",
|
||||
"packages": [
|
||||
{
|
||||
"name": "anourvalar/eloquent-serialize",
|
||||
@@ -625,6 +625,79 @@
|
||||
],
|
||||
"time": "2023-12-20T15:40:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "codewithdennis/filament-simple-alert",
|
||||
"version": "v4.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/CodeWithDennis/filament-simple-alert.git",
|
||||
"reference": "d30b0cad908f3ade1bed153d486fd564ac312ffd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/CodeWithDennis/filament-simple-alert/zipball/d30b0cad908f3ade1bed153d486fd564ac312ffd",
|
||||
"reference": "d30b0cad908f3ade1bed153d486fd564ac312ffd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"filament/filament": "^4.0",
|
||||
"php": "^8.1",
|
||||
"spatie/laravel-package-tools": "^1.15.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "^1.16",
|
||||
"nunomaduro/collision": "^7.9",
|
||||
"orchestra/testbench": "^8.0",
|
||||
"pestphp/pest": "^2.1",
|
||||
"pestphp/pest-plugin-arch": "^2.0",
|
||||
"pestphp/pest-plugin-laravel": "^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"aliases": {
|
||||
"SimpleAlert": "CodeWithDennis\\SimpleAlert\\Facades\\SimpleAlert"
|
||||
},
|
||||
"providers": [
|
||||
"CodeWithDennis\\SimpleAlert\\SimpleAlertServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"CodeWithDennis\\SimpleAlert\\": "src/",
|
||||
"CodeWithDennis\\SimpleAlert\\Database\\Factories\\": "database/factories/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "CodeWithDennis",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "A plugin for adding straightforward alerts to your filament pages",
|
||||
"homepage": "https://github.com/codewithdennis/filament-simple-alert",
|
||||
"keywords": [
|
||||
"CodeWithDennis",
|
||||
"filament-simple-alert",
|
||||
"laravel"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/codewithdennis/filament-simple-alert/issues",
|
||||
"source": "https://github.com/codewithdennis/filament-simple-alert"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/CodeWithDennis",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-06-21T18:43:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "danharrin/date-format-converter",
|
||||
"version": "v0.3.1",
|
||||
@@ -13714,5 +13787,5 @@
|
||||
"platform-overrides": {
|
||||
"php": "8.4"
|
||||
},
|
||||
"plugin-api-version": "2.6.0"
|
||||
"plugin-api-version": "2.9.0"
|
||||
}
|
||||
|
||||
@@ -61,6 +61,8 @@ return [
|
||||
'view_on_speedtest_net' => 'View on Speedtest.net',
|
||||
|
||||
// Notifications
|
||||
'speedtest_benchmark_passed' => 'Speedtest benchmark passed',
|
||||
'speedtest_benchmark_failed' => 'Speedtest benchmark failed',
|
||||
'speedtest_started' => 'Speedtest started',
|
||||
'speedtest_completed' => 'Speedtest completed',
|
||||
'speedtest_failed' => 'Speedtest failed',
|
||||
|
||||
@@ -7,14 +7,12 @@ return [
|
||||
// Database notifications
|
||||
'database' => 'Database',
|
||||
'database_description' => 'Notifications sent to this channel will show up under the 🔔 icon in the header.',
|
||||
'enable_database_notifications' => 'Enable database notifications',
|
||||
'database_on_speedtest_run' => 'Notify on every speedtest run',
|
||||
'database_on_threshold_failure' => 'Notify on threshold failures',
|
||||
'test_database_channel' => 'Test database channel',
|
||||
|
||||
// Mail notifications
|
||||
'mail' => 'Mail',
|
||||
'enable_mail_notifications' => 'Enable mail notifications',
|
||||
'recipients' => 'Recipients',
|
||||
'mail_on_speedtest_run' => 'Notify on every speedtest run',
|
||||
'mail_on_threshold_failure' => 'Notify on threshold failures',
|
||||
@@ -23,7 +21,6 @@ return [
|
||||
// Webhook
|
||||
'webhook' => 'Webhook',
|
||||
'webhooks' => 'Webhooks',
|
||||
'enable_webhook_notifications' => 'Enable webhook notifications',
|
||||
'webhook_on_speedtest_run' => 'Notify on every speedtest run',
|
||||
'webhook_on_threshold_failure' => 'Notify on threshold failures',
|
||||
'test_webhook_channel' => 'Test webhook channel',
|
||||
|
||||
Generated
+2
-2
@@ -6,10 +6,10 @@
|
||||
"": {
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"@tailwindcss/vite": "^4.1.16",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"autoprefixer": "^10.4.15",
|
||||
"laravel-vite-plugin": "^1.0.0",
|
||||
"tailwindcss": "^4.1.16",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"vite": "^6.4.1"
|
||||
}
|
||||
},
|
||||
|
||||
+2
-2
@@ -7,10 +7,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"@tailwindcss/vite": "^4.1.16",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"autoprefixer": "^10.4.15",
|
||||
"laravel-vite-plugin": "^1.0.0",
|
||||
"tailwindcss": "^4.1.16",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"vite": "^6.4.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
@import '../../../../vendor/filament/filament/resources/css/theme.css';
|
||||
|
||||
@source '../../../../app/Filament/**/*';
|
||||
@source '../../../../resources/views/filament/**/*';
|
||||
|
||||
/* Filament Plugins */
|
||||
@source '../../../../vendor/codewithdennis/filament-simple-alert/resources/**/*.blade.php';
|
||||
@source inline('animate-{spin,pulse,bounce}');
|
||||
|
||||
/* Additional styles */
|
||||
.fi-topbar #dashboardAction .fi-btn-label,
|
||||
.fi-topbar #speedtestAction .fi-btn-label {
|
||||
display: none;
|
||||
@@ -1,24 +0,0 @@
|
||||
<x-mail::message>
|
||||
# Speedtest Threshold Breached - #{{ $id }}
|
||||
|
||||
A new speedtest was completed using **{{ $service }}** on **{{ $isp }}** but a threshold was breached.
|
||||
|
||||
<x-mail::table>
|
||||
| **Metric** | **Threshold** | **Value** |
|
||||
|:-----------|:--------------|----------:|
|
||||
@foreach ($metrics as $item)
|
||||
| {{ $item['name'] }} | {{ $item['threshold'] }} | {{ $item['value'] }} |
|
||||
@endforeach
|
||||
</x-mail::table>
|
||||
|
||||
<x-mail::button :url="$url">
|
||||
View Results
|
||||
</x-mail::button>
|
||||
|
||||
<x-mail::button :url="$speedtest_url">
|
||||
View Results on Ookla
|
||||
</x-mail::button>
|
||||
|
||||
Thanks,<br>
|
||||
{{ config('app.name') }}
|
||||
</x-mail::message>
|
||||
@@ -0,0 +1,17 @@
|
||||
<x-mail::message>
|
||||
# Speedtest Threshold Breached - #{{ $id }}
|
||||
|
||||
A new speedtest was completed using **{{ $service }}** on **{{ $isp }}** but a threshold was breached.
|
||||
|
||||
<x-mail::table>
|
||||
| **Metric** | **Type** | **Threshold Value** | **Result Value** | **Status** |
|
||||
|:-----------|:---------|:--------------------|:-----------------|:---------:|
|
||||
@foreach ($benchmarks as $benchmark)
|
||||
| {{ $benchmark['metric'] }} | {{ $benchmark['type'] }} | {{ $benchmark['threshold_value'] }} | {{ $benchmark['result_value'] }} | {{ $benchmark['passed'] ? '✅' : '❌' }} |
|
||||
@endforeach
|
||||
</x-mail::table>
|
||||
|
||||
<x-mail::button :url="$url">
|
||||
{{ __('general.view') }}
|
||||
</x-mail::button>
|
||||
</x-mail::message>
|
||||
+1
-1
@@ -7,7 +7,7 @@ import tailwindcss from "@tailwindcss/vite";
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
laravel({
|
||||
input: ['resources/css/app.css'],
|
||||
input: ['resources/css/app.css', 'resources/css/filament/admin/theme.css'],
|
||||
refresh: [`resources/views/**/*`],
|
||||
}),
|
||||
tailwindcss(),
|
||||
|
||||
Reference in New Issue
Block a user