Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions app/Actions/Notifications/SendAppriseTestNotification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace App\Actions\Notifications;

use App\Notifications\Apprise\TestNotification;
use Filament\Notifications\Notification;
use Illuminate\Support\Facades\Notification as FacadesNotification;
use Lorisleiva\Actions\Concerns\AsAction;

class SendAppriseTestNotification
{
use AsAction;

public function handle(array $channel_urls)
{
if (! count($channel_urls)) {
Notification::make()
->title('You need to add Apprise channel URLs!')
->warning()
->send();

return;
}

foreach ($channel_urls as $row) {
$channelUrl = $row['channel_url'] ?? null;
if (! $channelUrl) {
Notification::make()
->title('Skipping missing channel URL!')
->warning()
->send();

continue;
}

FacadesNotification::route('apprise_urls', $channelUrl)
->notify(new TestNotification);
}

Notification::make()
->title('Test Apprise notification sent.')
->success()
->send();
}
}
40 changes: 40 additions & 0 deletions app/Console/Commands/TestNotification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace App\Console\Commands;

use App\Jobs\Notifications\Apprise\SendSpeedtestCompletedNotification as AppriseCompleted;
use App\Jobs\Notifications\Apprise\SendSpeedtestThresholdNotification as AppriseThreshold;
use App\Models\Result;
use Illuminate\Console\Command;

class TestNotification extends Command
{
protected $signature = 'notification:test
{type=completed : Notification type (completed or threshold)}
{--channel=apprise : Notification channel (apprise)}';

protected $description = 'Send a test notification using a fake result';

public function handle(): int
{
$type = $this->argument('type');
$channel = $this->option('channel');

$this->info("Creating fake result for type: {$type}");

$result = Result::factory()->create([
'status' => 'completed',
]);

$this->info("Dispatching {$channel} notification...");

match ("{$channel}-{$type}") {
'apprise-completed' => AppriseCompleted::dispatch($result),
'apprise-threshold' => AppriseThreshold::dispatch($result),
};

$this->info('βœ… Notification dispatched!');

return self::SUCCESS;
}
}
926 changes: 503 additions & 423 deletions app/Filament/Pages/Settings/NotificationPage.php

Large diffs are not rendered by default.

54 changes: 54 additions & 0 deletions app/Listeners/Apprise/SendSpeedtestCompletedNotification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace App\Listeners\Apprise;

use App\Events\SpeedtestCompleted;
use App\Notifications\Apprise\SpeedtestNotification;
use App\Services\Notifications\SpeedtestNotificationData;
use App\Settings\NotificationSettings;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;

class SendSpeedtestCompletedNotification
{
/**
* Handle the event.
*/
public function handle(SpeedtestCompleted $event): void
{
$notificationSettings = new NotificationSettings;

if (! $notificationSettings->apprise_enabled) {
return;
}

if (! $notificationSettings->apprise_on_speedtest_run) {
return;
}

if (empty($notificationSettings->apprise_channel_urls) || ! is_array($notificationSettings->apprise_channel_urls)) {
Log::warning('Apprise service URLs not found; check Apprise notification settings.');

return;
}

// Build the speedtest data
$data = SpeedtestNotificationData::make($event->result);

$body = view('apprise.speedtest-completed', $data)->render();
$title = 'Speedtest Completed – #'.$event->result->id;

// Send notification to each configured channel URL
foreach ($notificationSettings->apprise_channel_urls as $row) {
$channelUrl = $row['channel_url'] ?? null;
if (! $channelUrl) {
Log::warning('Skipping entry with missing channel_url.');

continue;
}

Notification::route('apprise_urls', $channelUrl)
->notify(new SpeedtestNotification($title, $body, 'info'));
}
}
}
139 changes: 139 additions & 0 deletions app/Listeners/Apprise/SendSpeedtestThresholdNotification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php

namespace App\Listeners\Apprise;

use App\Events\SpeedtestCompleted;
use App\Helpers\Number;
use App\Notifications\Apprise\SpeedtestNotification;
use App\Settings\NotificationSettings;
use App\Settings\ThresholdSettings;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Str;

class SendSpeedtestThresholdNotification
{
/**
* Handle the event.
*/
public function handle(SpeedtestCompleted $event): void
{
$notificationSettings = new NotificationSettings;

if (! $notificationSettings->apprise_enabled) {
return;
}

if (! $notificationSettings->apprise_on_threshold_failure) {
return;
}

if (empty($notificationSettings->apprise_channel_urls) || ! is_array($notificationSettings->apprise_channel_urls)) {
Log::warning('Apprise service URLs not found; check Apprise notification 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 Apprise thresholds not found, won\'t send notification.');

return;
}

$body = view('apprise.speedtest-threshold', [
'id' => $event->result->id,
'service' => Str::title($event->result->service->getLabel()),
'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();

$title = 'Speedtest Threshold Breach – #'.$event->result->id;

// Send notification to each configured channel URL
foreach ($notificationSettings->apprise_channel_urls as $row) {
$channelUrl = $row['channel_url'] ?? null;
if (! $channelUrl) {
Log::warning('Skipping entry with missing channel_url.');

continue;
}

Notification::route('apprise_urls', $channelUrl)
->notify(new SpeedtestNotification($title, $body, 'warning'));
}
}

/**
* Build Apprise 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 Apprise 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 Apprise 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',
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use App\Models\User;
use App\Settings\NotificationSettings;
use Filament\Notifications\Notification;
use Illuminate\Support\Facades\Log;

class SendSpeedtestCompletedNotification
{
Expand All @@ -25,6 +26,7 @@ public function handle(SpeedtestCompleted $event): void
}

foreach (User::all() as $user) {
Log::info('Notifying user', ['id' => $user->id, 'email' => $user->email]);
Notification::make()
->title('Speedtest completed')
->success()
Expand Down
1 change: 1 addition & 0 deletions app/Listeners/SpeedtestEventSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public function handleSpeedtestCompleted(SpeedtestCompleted $event): void
{
$settings = app(DataIntegrationSettings::class);

// Write to InfluxDB if enabled
if ($settings->influxdb_v2_enabled) {
WriteResult::dispatch($event->result);
}
Expand Down
66 changes: 66 additions & 0 deletions app/Notifications/Apprise/AppriseMessage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace App\Notifications\Apprise;

class AppriseMessage
{
public function __construct(
public string|array $urls,
public string $title,
public string $body,
public string $type = 'info',
public string $format = 'text',
public ?string $tag = null,
) {}

public static function create(): self
{
return new self(
urls: '',
title: '',
body: '',
);
}

public function urls(string|array $urls): self
{
$this->urls = $urls;

return $this;
}

public function title(string $title): self
{
$this->title = $title;

return $this;
}

public function body(string $body): self
{
$this->body = $body;

return $this;
}

public function type(string $type): self
{
$this->type = $type;

return $this;
}

public function format(string $format): self
{
$this->format = $format;

return $this;
}

public function tag(string $tag): self
{
$this->tag = $tag;

return $this;
}
}
Loading