mirror of
https://github.com/alexjustesen/speedtest-tracker.git
synced 2026-06-23 07:40:08 +00:00
Release v0.21.0 (#1648)
* Develop v0.21.0 (additional notification channels) (#1647) * Add Placeholder text for notifications (#1570) first_commit * Add Gotify Notifications via Webhook (#1561) * first_commit * Ready! * Add_placeholder * change url --------- Co-authored-by: Alex Justesen <alexjustesen@users.noreply.github.com> * Add Support for Slack Notifications via Webhook (#1522) * First Commit * Fix lint * add_url_placeholder * Fix the liniting * Add HealthCheck.io Notifications via Webhooks (#1567) * first_commit * linting * add_descrip_for_threshold * Change name * add_url_placeholder * fix_linting * Add Pushover Notifications via Webhooks (#1574) * add_pushover * add_placeholder * Linting --------- Co-authored-by: Alex Justesen <alexjustesen@users.noreply.github.com> * Add ntfy Notifications via Webhooks (#1579) * first_push * Fix_json_payload * Add_auth_option * fix lint * fix packet_loss_% * added eof line --------- Co-authored-by: Alex Justesen <alexjustesen@users.noreply.github.com> * Add Ookla URL to the notification (#1615) * first commit * added eof line --------- Co-authored-by: Alex Justesen <alexjustesen@users.noreply.github.com> * Fix notifications layouts (#1639) first commit --------- Co-authored-by: Sven van Ginkel <svenvanginkel@icloud.com> * updated npm dependencies --------- Co-authored-by: Sven van Ginkel <svenvanginkel@icloud.com>
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Notifications;
|
||||
|
||||
use Filament\Notifications\Notification;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Spatie\WebhookServer\WebhookCall;
|
||||
|
||||
class SendGotifyTestNotification
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(array $webhooks)
|
||||
{
|
||||
if (! count($webhooks)) {
|
||||
Notification::make()
|
||||
->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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Notifications;
|
||||
|
||||
use Filament\Notifications\Notification;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Spatie\WebhookServer\WebhookCall;
|
||||
|
||||
class SendHealthCheckTestNotification
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(array $webhooks)
|
||||
{
|
||||
if (! count($webhooks)) {
|
||||
Notification::make()
|
||||
->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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Notifications;
|
||||
|
||||
use Filament\Notifications\Notification;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Spatie\WebhookServer\WebhookCall;
|
||||
|
||||
class SendNtfyTestNotification
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(array $webhooks)
|
||||
{
|
||||
if (! count($webhooks)) {
|
||||
Notification::make()
|
||||
->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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Notifications;
|
||||
|
||||
use Filament\Notifications\Notification;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Spatie\WebhookServer\WebhookCall;
|
||||
|
||||
class SendPushoverTestNotification
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(array $webhooks)
|
||||
{
|
||||
if (! count($webhooks)) {
|
||||
Notification::make()
|
||||
->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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Notifications;
|
||||
|
||||
use Filament\Notifications\Notification;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Spatie\WebhookServer\WebhookCall;
|
||||
|
||||
class SendSlackTestNotification
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(array $webhooks)
|
||||
{
|
||||
if (! count($webhooks)) {
|
||||
Notification::make()
|
||||
->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();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,12 @@ namespace App\Filament\Pages\Settings;
|
||||
|
||||
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 @@ class NotificationPage extends SettingsPage
|
||||
'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 @@ class NotificationPage extends SettingsPage
|
||||
->label('Webhooks')
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('url')
|
||||
->placeholder('https://discord.com/api/webhooks/longstringofcharacters')
|
||||
->maxLength(2000)
|
||||
->required()
|
||||
->url(),
|
||||
@@ -127,6 +190,152 @@ class NotificationPage extends SettingsPage
|
||||
'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=<apptoken>')
|
||||
->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 @@ class NotificationPage extends SettingsPage
|
||||
->label('Recipients')
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('email_address')
|
||||
->placeholder('your@email.com')
|
||||
->email()
|
||||
->required(),
|
||||
])
|
||||
@@ -169,6 +379,51 @@ class NotificationPage extends SettingsPage
|
||||
'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 @@ class NotificationPage extends SettingsPage
|
||||
->label('Recipients')
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('telegram_chat_id')
|
||||
->placeholder('12345678910')
|
||||
->label('Telegram Chat ID')
|
||||
->maxLength(50)
|
||||
->required(),
|
||||
@@ -242,6 +498,7 @@ class NotificationPage extends SettingsPage
|
||||
->label('Recipients')
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('url')
|
||||
->placeholder('https://webhook.site/longstringofcharacters')
|
||||
->maxLength(2000)
|
||||
->required()
|
||||
->url(),
|
||||
|
||||
@@ -43,6 +43,7 @@ class SendSpeedtestCompletedNotification
|
||||
'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(),
|
||||
];
|
||||
|
||||
@@ -69,6 +69,7 @@ class SendSpeedtestThresholdNotification
|
||||
'serverId' => $event->result->server_id,
|
||||
'isp' => $event->result->isp,
|
||||
'metrics' => $failed,
|
||||
'speedtest_url' => $event->result->result_url,
|
||||
'url' => url('/admin/results'),
|
||||
])->render(),
|
||||
];
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners\Gotify;
|
||||
|
||||
use App\Events\SpeedtestCompleted;
|
||||
use App\Helpers\Number;
|
||||
use App\Settings\NotificationSettings;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\WebhookServer\WebhookCall;
|
||||
|
||||
class SendSpeedtestCompletedNotification
|
||||
{
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(SpeedtestCompleted $event): void
|
||||
{
|
||||
$notificationSettings = new NotificationSettings();
|
||||
|
||||
if (! $notificationSettings->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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners\Gotify;
|
||||
|
||||
use App\Events\SpeedtestCompleted;
|
||||
use App\Helpers\Number;
|
||||
use App\Settings\NotificationSettings;
|
||||
use App\Settings\ThresholdSettings;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\WebhookServer\WebhookCall;
|
||||
|
||||
class SendSpeedtestThresholdNotification
|
||||
{
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(SpeedtestCompleted $event): void
|
||||
{
|
||||
$notificationSettings = new NotificationSettings();
|
||||
|
||||
if (! $notificationSettings->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',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners\HealthCheck;
|
||||
|
||||
use App\Events\SpeedtestCompleted;
|
||||
use App\Settings\NotificationSettings;
|
||||
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->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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners\HealthCheck;
|
||||
|
||||
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->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',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners\Ntfy;
|
||||
|
||||
use App\Events\SpeedtestCompleted;
|
||||
use App\Helpers\Number;
|
||||
use App\Settings\NotificationSettings;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\WebhookServer\WebhookCall;
|
||||
|
||||
class SendSpeedtestCompletedNotification
|
||||
{
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(SpeedtestCompleted $event): void
|
||||
{
|
||||
$notificationSettings = new NotificationSettings();
|
||||
|
||||
if (! $notificationSettings->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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners\Ntfy;
|
||||
|
||||
use App\Events\SpeedtestCompleted;
|
||||
use App\Helpers\Number;
|
||||
use App\Settings\NotificationSettings;
|
||||
use App\Settings\ThresholdSettings;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\WebhookServer\WebhookCall;
|
||||
|
||||
class SendSpeedtestThresholdNotification
|
||||
{
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(SpeedtestCompleted $event): void
|
||||
{
|
||||
$notificationSettings = new NotificationSettings();
|
||||
|
||||
if (! $notificationSettings->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',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners\Pushover;
|
||||
|
||||
use App\Events\SpeedtestCompleted;
|
||||
use App\Helpers\Number;
|
||||
use App\Settings\NotificationSettings;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\WebhookServer\WebhookCall;
|
||||
|
||||
class SendSpeedtestCompletedNotification
|
||||
{
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(SpeedtestCompleted $event): void
|
||||
{
|
||||
$notificationSettings = new NotificationSettings();
|
||||
|
||||
if (! $notificationSettings->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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners\Pushover;
|
||||
|
||||
use App\Events\SpeedtestCompleted;
|
||||
use App\Helpers\Number;
|
||||
use App\Settings\NotificationSettings;
|
||||
use App\Settings\ThresholdSettings;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\WebhookServer\WebhookCall;
|
||||
|
||||
class SendSpeedtestThresholdNotification
|
||||
{
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(SpeedtestCompleted $event): void
|
||||
{
|
||||
$notificationSettings = new NotificationSettings();
|
||||
|
||||
if (! $notificationSettings->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',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners\Slack;
|
||||
|
||||
use App\Events\SpeedtestCompleted;
|
||||
use App\Helpers\Number;
|
||||
use App\Settings\NotificationSettings;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\WebhookServer\WebhookCall;
|
||||
|
||||
class SendSpeedtestCompletedNotification
|
||||
{
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(SpeedtestCompleted $event): void
|
||||
{
|
||||
$notificationSettings = new NotificationSettings();
|
||||
|
||||
if (! $notificationSettings->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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners\Slack;
|
||||
|
||||
use App\Events\SpeedtestCompleted;
|
||||
use App\Helpers\Number;
|
||||
use App\Settings\NotificationSettings;
|
||||
use App\Settings\ThresholdSettings;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\WebhookServer\WebhookCall;
|
||||
|
||||
class SendSpeedtestThresholdNotification
|
||||
{
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(SpeedtestCompleted $event): void
|
||||
{
|
||||
$notificationSettings = new NotificationSettings();
|
||||
|
||||
if (! $notificationSettings->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',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ class SendSpeedtestCompletedNotification
|
||||
'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();
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ class SendSpeedtestThresholdNotification
|
||||
'serverId' => $event->result->server_id,
|
||||
'isp' => $event->result->isp,
|
||||
'metrics' => $failed,
|
||||
'speedtest_url' => $event->result->result_url,
|
||||
'url' => url('/admin/results'),
|
||||
])->render();
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ class SendSpeedtestCompletedNotification
|
||||
'download' => $event->result->downloadBits,
|
||||
'upload' => $event->result->uploadBits,
|
||||
'packetLoss' => $event->result->packet_loss,
|
||||
'speedtest_url' => $event->result->result_url,
|
||||
'url' => url('/admin/results'),
|
||||
])
|
||||
->doNotSign()
|
||||
|
||||
@@ -68,6 +68,7 @@ class SendSpeedtestThresholdNotification
|
||||
'site_name' => config('app.name'),
|
||||
'isp' => $event->result->isp,
|
||||
'metrics' => $failed,
|
||||
'speedtest_url' => $event->result->result_url,
|
||||
'url' => url('/admin/results'),
|
||||
])
|
||||
->doNotSign()
|
||||
|
||||
@@ -52,6 +52,7 @@ class SpeedtestCompletedMail extends Mailable implements ShouldQueue
|
||||
'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'),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -48,6 +48,7 @@ class SpeedtestThresholdMail extends Mailable implements ShouldQueue
|
||||
'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,
|
||||
],
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
use Spatie\LaravelSettings\Migrations\SettingsMigration;
|
||||
|
||||
return new class extends SettingsMigration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
use Spatie\LaravelSettings\Migrations\SettingsMigration;
|
||||
|
||||
return new class extends SettingsMigration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
use Spatie\LaravelSettings\Migrations\SettingsMigration;
|
||||
|
||||
return new class extends SettingsMigration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
use Spatie\LaravelSettings\Migrations\SettingsMigration;
|
||||
|
||||
return new class extends SettingsMigration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
use Spatie\LaravelSettings\Migrations\SettingsMigration;
|
||||
|
||||
return new class extends SettingsMigration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
};
|
||||
Generated
+105
-101
@@ -539,9 +539,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.1.tgz",
|
||||
"integrity": "sha512-XzqSg714++M+FXhHfXpS1tDnNZNpgxxuGZWlRG/jSj+VEPmZ0yg6jV4E0AL3uyBKxO8mO3xtOsP5mQ+XLfrlww==",
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz",
|
||||
"integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -553,9 +553,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.1.tgz",
|
||||
"integrity": "sha512-thFUbkHteM20BGShD6P08aungq4irbIZKUNbG70LN8RkO7YztcGPiKTTGZS7Kw+x5h8hOXs0i4OaHwFxlpQN6A==",
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz",
|
||||
"integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -567,9 +567,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.1.tgz",
|
||||
"integrity": "sha512-8o6eqeFZzVLia2hKPUZk4jdE3zW7LCcZr+MD18tXkgBBid3lssGVAYuox8x6YHoEPDdDa9ixTaStcmx88lio5Q==",
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz",
|
||||
"integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -581,9 +581,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.1.tgz",
|
||||
"integrity": "sha512-4T42heKsnbjkn7ovYiAdDVRRWZLU9Kmhdt6HafZxFcUdpjlBlxj4wDrt1yFWLk7G4+E+8p2C9tcmSu0KA6auGA==",
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz",
|
||||
"integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -595,9 +595,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.1.tgz",
|
||||
"integrity": "sha512-MXg1xp+e5GhZ3Vit1gGEyoC+dyQUBy2JgVQ+3hUrD9wZMkUw/ywgkpK7oZgnB6kPpGrxJ41clkPPnsknuD6M2Q==",
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz",
|
||||
"integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -609,9 +609,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.1.tgz",
|
||||
"integrity": "sha512-DZNLwIY4ftPSRVkJEaxYkq7u2zel7aah57HESuNkUnz+3bZHxwkCUkrfS2IWC1sxK6F2QNIR0Qr/YXw7nkF3Pw==",
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz",
|
||||
"integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -623,9 +623,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.1.tgz",
|
||||
"integrity": "sha512-C7evongnjyxdngSDRRSQv5GvyfISizgtk9RM+z2biV5kY6S/NF/wta7K+DanmktC5DkuaJQgoKGf7KUDmA7RUw==",
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz",
|
||||
"integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -637,9 +637,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.1.tgz",
|
||||
"integrity": "sha512-89tFWqxfxLLHkAthAcrTs9etAoBFRduNfWdl2xUs/yLV+7XDrJ5yuXMHptNqf1Zw0UCA3cAutkAiAokYCkaPtw==",
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz",
|
||||
"integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -651,9 +651,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.1.tgz",
|
||||
"integrity": "sha512-PromGeV50sq+YfaisG8W3fd+Cl6mnOOiNv2qKKqKCpiiEke2KiKVyDqG/Mb9GWKbYMHj5a01fq/qlUR28PFhCQ==",
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz",
|
||||
"integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -665,9 +665,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.1.tgz",
|
||||
"integrity": "sha512-/1BmHYh+iz0cNCP0oHCuF8CSiNj0JOGf0jRlSo3L/FAyZyG2rGBuKpkZVH9YF+x58r1jgWxvm1aRg3DHrLDt6A==",
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz",
|
||||
"integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -679,9 +679,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.1.tgz",
|
||||
"integrity": "sha512-0cYP5rGkQWRZKy9/HtsWVStLXzCF3cCBTRI+qRL8Z+wkYlqN7zrSYm6FuY5Kd5ysS5aH0q5lVgb/WbG4jqXN1Q==",
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz",
|
||||
"integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -693,9 +693,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.1.tgz",
|
||||
"integrity": "sha512-XUXeI9eM8rMP8aGvii/aOOiMvTs7xlCosq9xCjcqI9+5hBxtjDpD+7Abm1ZhVIFE1J2h2VIg0t2DX/gjespC2Q==",
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz",
|
||||
"integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -707,9 +707,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.1.tgz",
|
||||
"integrity": "sha512-V7cBw/cKXMfEVhpSvVZhC+iGifD6U1zJ4tbibjjN+Xi3blSXaj/rJynAkCFFQfoG6VZrAiP7uGVzL440Q6Me2Q==",
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz",
|
||||
"integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -721,9 +721,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.1.tgz",
|
||||
"integrity": "sha512-88brja2vldW/76jWATlBqHEoGjJLRnP0WOEKAUbMcXaAZnemNhlAHSyj4jIwMoP2T750LE9lblvD4e2jXleZsA==",
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz",
|
||||
"integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -735,9 +735,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.1.tgz",
|
||||
"integrity": "sha512-LdxxcqRVSXi6k6JUrTah1rHuaupoeuiv38du8Mt4r4IPer3kwlTo+RuvfE8KzZ/tL6BhaPlzJ3835i6CxrFIRQ==",
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz",
|
||||
"integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -749,9 +749,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.1.tgz",
|
||||
"integrity": "sha512-2bIrL28PcK3YCqD9anGxDxamxdiJAxA+l7fWIwM5o8UqNy1t3d1NdAweO2XhA0KTDJ5aH1FsuiT5+7VhtHliXg==",
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz",
|
||||
"integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -776,9 +776,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/typography": {
|
||||
"version": "0.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.13.tgz",
|
||||
"integrity": "sha512-ADGcJ8dX21dVVHIwTRgzrcunY6YY9uSlAHHGVKvkA+vLc5qLwEszvKts40lx7z0qc4clpjclwLeK5rVCV2P/uw==",
|
||||
"version": "0.5.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.14.tgz",
|
||||
"integrity": "sha512-ZvOCjUbsJBjL9CxQBn+VEnFpouzuKhxh2dH8xMIWHILL+HfOYtlAkWcyoon8LlzE53d2Yo6YO6pahKKNW3q1YQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -853,9 +853,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.19",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz",
|
||||
"integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==",
|
||||
"version": "10.4.20",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
|
||||
"integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -873,11 +873,11 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.23.0",
|
||||
"caniuse-lite": "^1.0.30001599",
|
||||
"browserslist": "^4.23.3",
|
||||
"caniuse-lite": "^1.0.30001646",
|
||||
"fraction.js": "^4.3.7",
|
||||
"normalize-range": "^0.1.2",
|
||||
"picocolors": "^1.0.0",
|
||||
"picocolors": "^1.0.1",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
"bin": {
|
||||
@@ -934,9 +934,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.23.2",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz",
|
||||
"integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==",
|
||||
"version": "4.23.3",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz",
|
||||
"integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -954,9 +954,9 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001640",
|
||||
"electron-to-chromium": "^1.4.820",
|
||||
"node-releases": "^2.0.14",
|
||||
"caniuse-lite": "^1.0.30001646",
|
||||
"electron-to-chromium": "^1.5.4",
|
||||
"node-releases": "^2.0.18",
|
||||
"update-browserslist-db": "^1.1.0"
|
||||
},
|
||||
"bin": {
|
||||
@@ -977,9 +977,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001643",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz",
|
||||
"integrity": "sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==",
|
||||
"version": "1.0.30001651",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz",
|
||||
"integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -1115,9 +1115,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.2.tgz",
|
||||
"integrity": "sha512-kc4r3U3V3WLaaZqThjYz/Y6z8tJe+7K0bbjUVo3i+LWIypVdMx5nXCkwRe6SWbY6ILqLdc1rKcKmr3HoH7wjSQ==",
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.5.tgz",
|
||||
"integrity": "sha512-QR7/A7ZkMS8tZuoftC/jfqNkZLQO779SSW3YuZHP4eXpj3EffGLFcB/Xu9AAZQzLccTiCV+EmUo3ha4mQ9wnlA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -1231,9 +1231,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/foreground-child": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz",
|
||||
"integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==",
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
|
||||
"integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
@@ -1723,9 +1723,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.40",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz",
|
||||
"integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==",
|
||||
"version": "8.4.41",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz",
|
||||
"integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -1973,9 +1973,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.19.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.19.1.tgz",
|
||||
"integrity": "sha512-K5vziVlg7hTpYfFBI+91zHBEMo6jafYXpkMlqZjg7/zhIG9iHqazBf4xz9AVdjS9BruRn280ROqLI7G3OFRIlw==",
|
||||
"version": "4.20.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz",
|
||||
"integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1989,22 +1989,22 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.19.1",
|
||||
"@rollup/rollup-android-arm64": "4.19.1",
|
||||
"@rollup/rollup-darwin-arm64": "4.19.1",
|
||||
"@rollup/rollup-darwin-x64": "4.19.1",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.19.1",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.19.1",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.19.1",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.19.1",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.19.1",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.19.1",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.19.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.19.1",
|
||||
"@rollup/rollup-linux-x64-musl": "4.19.1",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.19.1",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.19.1",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.19.1",
|
||||
"@rollup/rollup-android-arm-eabi": "4.20.0",
|
||||
"@rollup/rollup-android-arm64": "4.20.0",
|
||||
"@rollup/rollup-darwin-arm64": "4.20.0",
|
||||
"@rollup/rollup-darwin-x64": "4.20.0",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.20.0",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.20.0",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.20.0",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.20.0",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.20.0",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.20.0",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.20.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.20.0",
|
||||
"@rollup/rollup-linux-x64-musl": "4.20.0",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.20.0",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.20.0",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.20.0",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
@@ -2219,9 +2219,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.7",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.7.tgz",
|
||||
"integrity": "sha512-rxWZbe87YJb4OcSopb7up2Ba4U82BoiSGUdoDr3Ydrg9ckxFS/YWsvhN323GMcddgU65QRy7JndC7ahhInhvlQ==",
|
||||
"version": "3.4.9",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.9.tgz",
|
||||
"integrity": "sha512-1SEOvRr6sSdV5IDf9iC+NU4dhwdqzF4zKKq3sAbasUWHEM6lsMhX+eNN5gkPx1BvLFEnZQEUFbXnGj8Qlp83Pg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2352,14 +2352,14 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.3.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz",
|
||||
"integrity": "sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==",
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.0.tgz",
|
||||
"integrity": "sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.21.3",
|
||||
"postcss": "^8.4.39",
|
||||
"postcss": "^8.4.40",
|
||||
"rollup": "^4.13.0"
|
||||
},
|
||||
"bin": {
|
||||
@@ -2379,6 +2379,7 @@
|
||||
"less": "*",
|
||||
"lightningcss": "^1.21.0",
|
||||
"sass": "*",
|
||||
"sass-embedded": "*",
|
||||
"stylus": "*",
|
||||
"sugarss": "*",
|
||||
"terser": "^5.4.0"
|
||||
@@ -2396,6 +2397,9 @@
|
||||
"sass": {
|
||||
"optional": true
|
||||
},
|
||||
"sass-embedded": {
|
||||
"optional": true
|
||||
},
|
||||
"stylus": {
|
||||
"optional": true
|
||||
},
|
||||
|
||||
@@ -9,4 +9,5 @@ A new speedtest was completed using **{{ $service }}**.
|
||||
- **Download:** {{ $download }}
|
||||
- **Upload:** {{ $upload }}
|
||||
- **Packet Loss:** {{ $packetLoss }} **%**
|
||||
- **Ookla Speedtest:** {{ $speedtest_url }}
|
||||
- **URL:** {{ $url }}
|
||||
|
||||
@@ -5,4 +5,5 @@ A new speedtest was completed using **{{ $service }}** on **{{ $isp }}** but a t
|
||||
@foreach ($metrics as $item)
|
||||
- **{{ $item['name'] }}** {{ $item['threshold'] }}: {{ $item['value'] }}
|
||||
@endforeach
|
||||
- **Ookla Speedtest:** {{ $speedtest_url }}
|
||||
- **URL:** {{ $url }}
|
||||
|
||||
@@ -12,7 +12,8 @@ A new speedtest was completed using **{{ $service }}**.
|
||||
| Ping | {{ $ping }} |
|
||||
| Download | {{ $download }} |
|
||||
| Upload | {{ $upload }} |
|
||||
| Packet Loss | {{ $packetLoss }}**%** |
|
||||
| Packet Loss | {{ $packetLoss }} **%** |
|
||||
|
||||
|
||||
</x-mail::table>
|
||||
|
||||
@@ -20,6 +21,10 @@ A new speedtest was completed using **{{ $service }}**.
|
||||
View Results
|
||||
</x-mail::button>
|
||||
|
||||
<x-mail::button :Ookla Speedtest="$speedtest_url">
|
||||
View Results on Ookla
|
||||
</x-mail::button>
|
||||
|
||||
Thanks,<br>
|
||||
{{ config('app.name') }}
|
||||
</x-mail::message>
|
||||
|
||||
@@ -15,6 +15,10 @@ A new speedtest was completed using **{{ $service }}** on **{{ $isp }}** but a t
|
||||
View Results
|
||||
</x-mail::button>
|
||||
|
||||
<x-mail::button :Ookla Speedtest="$speedtest_url">
|
||||
View Results on Ookla
|
||||
</x-mail::button>
|
||||
|
||||
Thanks,<br>
|
||||
{{ config('app.name') }}
|
||||
</x-mail::message>
|
||||
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -9,4 +9,5 @@ A new speedtest was completed using *{{ $service }}*.
|
||||
- *Download:* {{ $download }}
|
||||
- *Upload:* {{ $upload }}
|
||||
- **Packet Loss:** {{ $packetLoss }}**%**
|
||||
- **Ookla Speedtest:** {{ $speedtest_url }}
|
||||
- **URL:** {{ $url }}
|
||||
|
||||
@@ -5,4 +5,5 @@ A new speedtest was completed using **{{ $service }}** on **{{ $isp }}** but a t
|
||||
@foreach ($metrics as $item)
|
||||
- **{{ $item['name'] }}** {{ $item['threshold'] }}: {{ $item['value'] }}
|
||||
@endforeach
|
||||
- **Ookla Speedtest:** {{ $speedtest_url }}
|
||||
- **URL:** {{ $url }}
|
||||
|
||||
Reference in New Issue
Block a user