Chore: Improve Apprise Logic & UI Text (#2579)

This commit is contained in:
Sven van Ginkel
2025-12-19 17:26:43 +01:00
committed by GitHub
parent dc4dd2ff66
commit eb9a3e676e
5 changed files with 118 additions and 34 deletions
@@ -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 @@ class SendAppriseTestNotification
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);
FacadesNotification::route('apprise_urls', $channelUrl)
->notify(new TestNotification);
Notification::make()
->title('Failed to send Apprise test notification')
->body($errorMessage)
->danger()
->send();
return;
}
Notification::make()
@@ -42,4 +66,35 @@ class SendAppriseTestNotification
->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;
}
}
+4 -1
View File
@@ -14,6 +14,7 @@ use App\Actions\Notifications\SendSlackTestNotification;
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 @@ class Notification extends SettingsPage
->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'))
+17 -16
View File
@@ -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 @@ class AppriseChannel
}
$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 @@ class AppriseChannel
$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 @@ class AppriseChannel
'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;
}
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class ContainsString implements ValidationRule
{
public function __construct(
protected string $needle,
protected bool $caseSensitive = false
) {}
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$haystack = $this->caseSensitive ? $value : strtolower($value);
$needle = $this->caseSensitive ? $this->needle : strtolower($this->needle);
if (! str_contains($haystack, $needle)) {
$fail("The :attribute must contain '{$this->needle}'.");
}
}
}
+7 -6
View File
@@ -19,19 +19,20 @@ return [
'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',