diff --git a/app/Actions/Notifications/SendAppriseTestNotification.php b/app/Actions/Notifications/SendAppriseTestNotification.php index 062202b06..64bba779b 100644 --- a/app/Actions/Notifications/SendAppriseTestNotification.php +++ b/app/Actions/Notifications/SendAppriseTestNotification.php @@ -3,15 +3,17 @@ namespace App\Actions\Notifications; use App\Notifications\Apprise\TestNotification; +use App\Settings\NotificationSettings; use Filament\Notifications\Notification; use Illuminate\Support\Facades\Notification as FacadesNotification; use Lorisleiva\Actions\Concerns\AsAction; +use Throwable; class SendAppriseTestNotification { use AsAction; - public function handle(array $channel_urls) + public function handle(array $channel_urls): void { if (! count($channel_urls)) { Notification::make() @@ -22,19 +24,41 @@ public function handle(array $channel_urls) return; } - foreach ($channel_urls as $row) { - $channelUrl = $row['channel_url'] ?? null; - if (! $channelUrl) { - Notification::make() - ->title('Skipping missing channel URL!') - ->warning() - ->send(); + $settings = app(NotificationSettings::class); + $appriseUrl = rtrim($settings->apprise_server_url ?? '', '/'); - continue; + if (empty($appriseUrl)) { + Notification::make() + ->title('Apprise Server URL is not configured') + ->body('Please configure the Apprise Server URL in the settings above.') + ->danger() + ->send(); + + return; + } + + try { + foreach ($channel_urls as $row) { + $channelUrl = $row['channel_url'] ?? null; + if (! $channelUrl) { + continue; + } + + // Use notifyNow() to send synchronously even though notification implements ShouldQueue + // This allows us to catch exceptions and show them in the UI immediately + FacadesNotification::route('apprise_urls', $channelUrl) + ->notifyNow(new TestNotification); } + } catch (Throwable $e) { + $errorMessage = $this->cleanErrorMessage($e); + + Notification::make() + ->title('Failed to send Apprise test notification') + ->body($errorMessage) + ->danger() + ->send(); - FacadesNotification::route('apprise_urls', $channelUrl) - ->notify(new TestNotification); + return; } Notification::make() @@ -42,4 +66,35 @@ public function handle(array $channel_urls) ->success() ->send(); } + + /** + * Clean up error message for display in UI. + */ + protected function cleanErrorMessage(Throwable $e): string + { + $message = $e->getMessage(); + + // Get the full Apprise server URL for error messages + $settings = app(NotificationSettings::class); + $appriseUrl = rtrim($settings->apprise_server_url ?? '', '/'); + + // Handle connection errors - extract just the important part + if (str_contains($message, 'cURL error')) { + if (str_contains($message, 'Could not resolve host')) { + return "Could not connect to Apprise server at {$appriseUrl}"; + } + + if (str_contains($message, 'Connection refused')) { + return "Connection refused by Apprise server at {$appriseUrl}"; + } + + if (str_contains($message, 'Operation timed out')) { + return "Connection to Apprise server at {$appriseUrl} timed out"; + } + + return "Failed to connect to Apprise server at {$appriseUrl}"; + } + + return $message; + } } diff --git a/app/Filament/Pages/Settings/Notification.php b/app/Filament/Pages/Settings/Notification.php index 9a5decb00..7e1c1a58a 100755 --- a/app/Filament/Pages/Settings/Notification.php +++ b/app/Filament/Pages/Settings/Notification.php @@ -14,6 +14,7 @@ use App\Actions\Notifications\SendTelegramTestNotification; use App\Actions\Notifications\SendWebhookTestNotification; use App\Rules\AppriseScheme; +use App\Rules\ContainsString; use App\Settings\NotificationSettings; use CodeWithDennis\SimpleAlert\Components\SimpleAlert; use Filament\Actions\Action; @@ -233,10 +234,12 @@ public function form(Schema $schema): Schema ->schema([ TextInput::make('apprise_server_url') ->label(__('settings/notifications.apprise_server_url')) - ->placeholder('http://localhost:8000') + ->placeholder('http://localhost:8000/notify') + ->helperText(__('settings/notifications.apprise_server_url_helper')) ->maxLength(2000) ->required() ->url() + ->rule(new ContainsString('/notify')) ->columnSpanFull(), Checkbox::make('apprise_verify_ssl') ->label(__('settings/notifications.apprise_verify_ssl')) diff --git a/app/Livewire/DeprecatedNotificationChannelsBanner.php b/app/Livewire/DeprecatedNotificationChannelsBanner.php new file mode 100644 index 000000000..84533de19 --- /dev/null +++ b/app/Livewire/DeprecatedNotificationChannelsBanner.php @@ -0,0 +1,66 @@ +discord_enabled + || $settings->gotify_enabled + || $settings->healthcheck_enabled + || $settings->ntfy_enabled + || $settings->pushover_enabled + || $settings->slack_enabled + || $settings->telegram_enabled; + } + + #[Computed] + public function deprecatedChannelsList(): array + { + $settings = app(NotificationSettings::class); + $channels = []; + + if ($settings->discord_enabled) { + $channels[] = 'Discord'; + } + + if ($settings->gotify_enabled) { + $channels[] = 'Gotify'; + } + + if ($settings->healthcheck_enabled) { + $channels[] = 'Healthchecks'; + } + + if ($settings->ntfy_enabled) { + $channels[] = 'Ntfy'; + } + + if ($settings->pushover_enabled) { + $channels[] = 'Pushover'; + } + + if ($settings->slack_enabled) { + $channels[] = 'Slack'; + } + + if ($settings->telegram_enabled) { + $channels[] = 'Telegram'; + } + + return $channels; + } + + public function render() + { + return view('livewire.deprecated-notification-channels-banner'); + } +} diff --git a/app/Notifications/AppriseChannel.php b/app/Notifications/AppriseChannel.php index 3cd2592a1..0e9ed1087 100644 --- a/app/Notifications/AppriseChannel.php +++ b/app/Notifications/AppriseChannel.php @@ -3,9 +3,11 @@ namespace App\Notifications; use App\Settings\NotificationSettings; +use Exception; use Illuminate\Notifications\Notification; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; +use Throwable; class AppriseChannel { @@ -22,7 +24,7 @@ public function send(object $notifiable, Notification $notification): void } $settings = app(NotificationSettings::class); - $appriseUrl = rtrim($settings->apprise_server_url ?? '', '/'); + $appriseUrl = $settings->apprise_server_url ?? ''; if (empty($appriseUrl)) { Log::warning('Apprise notification skipped: No Server URL configured'); @@ -41,7 +43,7 @@ public function send(object $notifiable, Notification $notification): void $request = $request->withoutVerifying(); } - $response = $request->post("{$appriseUrl}/notify", [ + $response = $request->post($appriseUrl, [ 'urls' => $message->urls, 'title' => $message->title, 'body' => $message->body, @@ -50,26 +52,25 @@ public function send(object $notifiable, Notification $notification): void 'tag' => $message->tag ?? null, ]); - if ($response->failed()) { - Log::error('Apprise notification failed', [ - 'channel' => $message->urls, - 'instance' => $appriseUrl, - 'status' => $response->status(), - 'body' => $response->body(), - ]); - } else { - Log::info('Apprise notification sent', [ - 'channel' => $message->urls, - 'instance' => $appriseUrl, - ]); + // Only accept 200 OK responses as successful + if ($response->status() !== 200) { + throw new Exception('Apprise returned an error, please check Apprise logs for details'); } - } catch (\Throwable $e) { - Log::error('Apprise notification exception', [ + + Log::info('Apprise notification sent', [ + 'channel' => $message->urls, + 'instance' => $appriseUrl, + ]); + } catch (Throwable $e) { + Log::error('Apprise notification failed', [ 'channel' => $message->urls ?? 'unknown', 'instance' => $appriseUrl, 'message' => $e->getMessage(), 'exception' => get_class($e), ]); + + // Re-throw the exception so it can be handled by the queue + throw $e; } } } diff --git a/app/Rules/ContainsString.php b/app/Rules/ContainsString.php new file mode 100644 index 000000000..370a499c6 --- /dev/null +++ b/app/Rules/ContainsString.php @@ -0,0 +1,24 @@ +caseSensitive ? $value : strtolower($value); + $needle = $this->caseSensitive ? $this->needle : strtolower($this->needle); + + if (! str_contains($haystack, $needle)) { + $fail("The :attribute must contain '{$this->needle}'."); + } + } +} diff --git a/config/speedtest.php b/config/speedtest.php index 946b93f75..94ebcaa84 100644 --- a/config/speedtest.php +++ b/config/speedtest.php @@ -6,9 +6,9 @@ /** * General settings. */ - 'build_date' => Carbon::parse('2025-12-18'), + 'build_date' => Carbon::parse('2025-12-19'), - 'build_version' => 'v1.13.2', + 'build_version' => 'v1.13.3', 'content_width' => env('CONTENT_WIDTH', '7xl'), diff --git a/lang/en/settings/notifications.php b/lang/en/settings/notifications.php index 203590b23..788f31d91 100644 --- a/lang/en/settings/notifications.php +++ b/lang/en/settings/notifications.php @@ -19,19 +19,20 @@ 'enable_apprise_notifications' => 'Enable Apprise notifications', 'apprise_server' => 'Apprise Server', 'apprise_server_url' => 'Apprise Server URL', + 'apprise_server_url_helper' => 'The URL of your Apprise Server. The URL must end on /notify', 'apprise_verify_ssl' => 'Verify SSL', - 'apprise_channels' => 'Apprise Channels', - 'apprise_channel_url' => 'Channel URL', - 'apprise_hint_description' => 'For more information on setting up Apprise, view the documentation.', - 'apprise_channel_url_helper' => 'Provide the service endpoint URL for notifications.', + 'apprise_channels' => 'Notification Channels', + 'apprise_channel_url' => 'Service URL', + 'apprise_hint_description' => 'Apprise allows you to send notifications to 90+ services. You need to run an Apprise server and configure service URLs below.', + 'apprise_channel_url_helper' => 'Use Apprise URL format. Examples: discord://WebhookID/Token, slack://TokenA/TokenB/TokenC', 'test_apprise_channel' => 'Test Apprise', - 'apprise_channel_url_validation_error' => 'The Apprise channel URL must not start with "http" or "https". Please provide a valid Apprise URL scheme.', + 'apprise_channel_url_validation_error' => 'Invalid Apprise URL. Must use Apprise format (e.g., discord://, slack://), not http:// or https://. See the Apprise documentation for more information', // Webhook 'webhook' => 'Webhook', 'webhooks' => 'Webhooks', 'test_webhook_channel' => 'Test webhook channel', - 'webhook_hint_description' => 'These are generic webhooks. For payload examples and implementation details, view the documentation.', + 'webhook_hint_description' => 'These are generic webhooks. For payload examples and implementation details, view the documentation. For services like Discord, Ntfy etc please use Apprise.', // Common notification messages 'notify_on_every_speedtest_run' => 'Notify on every scheduled speedtest run', diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index 8b54445cb..967996d47 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -1,5 +1,7 @@
+ + diff --git a/resources/views/discord/speedtest-completed.blade.php b/resources/views/discord/speedtest-completed.blade.php index 6a8cdf9a5..851a8200a 100644 --- a/resources/views/discord/speedtest-completed.blade.php +++ b/resources/views/discord/speedtest-completed.blade.php @@ -1,3 +1,5 @@ +**Deprecation Notice: The Discord notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Discord and many other services.** + **Speedtest Completed - #{{ $id }}** A new speedtest on **{{ config('app.name') }}** was completed using **{{ $service }}**. diff --git a/resources/views/discord/speedtest-threshold.blade.php b/resources/views/discord/speedtest-threshold.blade.php index 95dc4bf01..b0716b50b 100644 --- a/resources/views/discord/speedtest-threshold.blade.php +++ b/resources/views/discord/speedtest-threshold.blade.php @@ -1,3 +1,5 @@ +**Deprecation Notice: The Discord notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Discord and many other services.** + **Speedtest Threshold Breached - #{{ $id }}** A new speedtest on **{{ config('app.name') }}** was completed using **{{ $service }}** on **{{ $isp }}** but a threshold was breached. diff --git a/resources/views/filament/pages/dashboard.blade.php b/resources/views/filament/pages/dashboard.blade.php index b92cde2a9..2d9d68211 100644 --- a/resources/views/filament/pages/dashboard.blade.php +++ b/resources/views/filament/pages/dashboard.blade.php @@ -1,5 +1,7 @@
+ + diff --git a/resources/views/gotify/speedtest-completed.blade.php b/resources/views/gotify/speedtest-completed.blade.php index 910b9347c..c17d46daf 100644 --- a/resources/views/gotify/speedtest-completed.blade.php +++ b/resources/views/gotify/speedtest-completed.blade.php @@ -1,3 +1,5 @@ +**Deprecation Notice: The Gotify notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Gotify and many other services.** + **Speedtest Completed - #{{ $id }}** A new speedtest on **{{ config('app.name') }}** was completed using **{{ $service }}**. diff --git a/resources/views/gotify/speedtest-threshold.blade.php b/resources/views/gotify/speedtest-threshold.blade.php index dd86bcf8a..e731a0104 100644 --- a/resources/views/gotify/speedtest-threshold.blade.php +++ b/resources/views/gotify/speedtest-threshold.blade.php @@ -1,3 +1,5 @@ +**Deprecation Notice: The Gotify notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Gotify and many other services.** + **Speedtest Threshold Breached - #{{ $id }}** A new speedtest on **{{ config('app.name') }}** was completed using **{{ $service }}** on **{{ $isp }}** but a threshold was breached. diff --git a/resources/views/livewire/deprecated-notification-channels-banner.blade.php b/resources/views/livewire/deprecated-notification-channels-banner.blade.php new file mode 100644 index 000000000..a96ed0441 --- /dev/null +++ b/resources/views/livewire/deprecated-notification-channels-banner.blade.php @@ -0,0 +1,25 @@ +
+ @if ($this->hasDeprecatedChannels) +
+
+
+ +
+ +
+

+ Deprecated Notification Channels +

+
+

+ You are currently using the following deprecated notification channels: {{ implode(', ', $this->deprecatedChannelsList) }}. +

+

+ These channels will be removed at the end of January 2026. As of that moment you will no longer receive notifications. Please migrate to Apprise which supports all these services and more. +

+
+
+
+
+ @endif +
diff --git a/resources/views/ntfy/speedtest-completed.blade.php b/resources/views/ntfy/speedtest-completed.blade.php index 24c67a908..64501e794 100644 --- a/resources/views/ntfy/speedtest-completed.blade.php +++ b/resources/views/ntfy/speedtest-completed.blade.php @@ -1,3 +1,5 @@ +Deprecation Notice: The Ntfy notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Ntfy and many other services. + Speedtest Completed - #{{ $id }} A new speedtest on {{ config('app.name') }} was completed using {{ $service }}. diff --git a/resources/views/ntfy/speedtest-threshold.blade.php b/resources/views/ntfy/speedtest-threshold.blade.php index 8ad956c03..7e9ef3c93 100644 --- a/resources/views/ntfy/speedtest-threshold.blade.php +++ b/resources/views/ntfy/speedtest-threshold.blade.php @@ -1,3 +1,5 @@ +Deprecation Notice: The Ntfy notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Ntfy and many other services. + Speedtest Threshold Breached - #{{ $id }} A new speedtest on **{{ config('app.name') }}** was completed using **{{ $service }}** on **{{ $isp }}** but a threshold was breached. diff --git a/resources/views/pushover/speedtest-completed.blade.php b/resources/views/pushover/speedtest-completed.blade.php index 8760eb5de..dfae02299 100644 --- a/resources/views/pushover/speedtest-completed.blade.php +++ b/resources/views/pushover/speedtest-completed.blade.php @@ -1,3 +1,5 @@ +Deprecation Notice: The Pushover notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Pushover and many other services. + Speedtest Completed - #{{ $id }} A new speedtest on {{ config('app.name') }} was completed using {{ $service }}. diff --git a/resources/views/pushover/speedtest-threshold.blade.php b/resources/views/pushover/speedtest-threshold.blade.php index 8ad956c03..6f222fa19 100644 --- a/resources/views/pushover/speedtest-threshold.blade.php +++ b/resources/views/pushover/speedtest-threshold.blade.php @@ -1,3 +1,5 @@ +Deprecation Notice: The Pushover notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Pushover and many other services. + Speedtest Threshold Breached - #{{ $id }} A new speedtest on **{{ config('app.name') }}** was completed using **{{ $service }}** on **{{ $isp }}** but a threshold was breached. diff --git a/resources/views/slack/speedtest-completed.blade.php b/resources/views/slack/speedtest-completed.blade.php index f93d8daf4..c1abcefc5 100644 --- a/resources/views/slack/speedtest-completed.blade.php +++ b/resources/views/slack/speedtest-completed.blade.php @@ -1,3 +1,5 @@ +*Deprecation Notice: The Slack notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Slack and many other services.* + *Speedtest Completed - #{{ $id }}* A new speedtest on *{{ config('app.name') }}* was completed using *{{ $service }}*. diff --git a/resources/views/slack/speedtest-threshold.blade.php b/resources/views/slack/speedtest-threshold.blade.php index 612475a28..2d4a31fc5 100644 --- a/resources/views/slack/speedtest-threshold.blade.php +++ b/resources/views/slack/speedtest-threshold.blade.php @@ -1,3 +1,5 @@ +*Deprecation Notice: The Slack notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Slack and many other services.* + **Speedtest Threshold Breached - #{{ $id }}** A new speedtest on **{{ config('app.name') }}** was completed using **{{ $service }}** on **{{ $isp }}** but a threshold was breached. diff --git a/resources/views/telegram/speedtest-completed.blade.php b/resources/views/telegram/speedtest-completed.blade.php index 497c3bf12..ba168cb10 100644 --- a/resources/views/telegram/speedtest-completed.blade.php +++ b/resources/views/telegram/speedtest-completed.blade.php @@ -1,3 +1,5 @@ +**Deprecation Notice: The Telegram notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Telegram and many other services.** + *Speedtest Completed - #{{ $id }}* A new speedtest on *{{ config('app.name') }}* was completed using *{{ $service }}*. diff --git a/resources/views/telegram/speedtest-threshold.blade.php b/resources/views/telegram/speedtest-threshold.blade.php index 95dc4bf01..81ef8bb74 100644 --- a/resources/views/telegram/speedtest-threshold.blade.php +++ b/resources/views/telegram/speedtest-threshold.blade.php @@ -1,3 +1,5 @@ +**Deprecation Notice: The Telegram notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Telegram and many other services.** + **Speedtest Threshold Breached - #{{ $id }}** A new speedtest on **{{ config('app.name') }}** was completed using **{{ $service }}** on **{{ $isp }}** but a threshold was breached.