From 27879377a07a88d2070a2939b2856cd0273eac52 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 10 Oct 2025 15:37:00 +0200 Subject: [PATCH 01/14] feat: add custom webhook notification support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add basic infrastructure for custom webhook notifications: - Create webhook_notification_settings table with event toggles - Add WebhookNotificationSettings model with encrypted URL - Integrate webhook settings into Team model and HasNotificationSettings trait - Create Livewire component and Blade view for webhook configuration - Add webhook navigation route and UI This provides the foundation for sending webhook notifications to custom HTTP/HTTPS endpoints when events occur in Coolify. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/Livewire/Notifications/Webhook.php | 180 ++++++++++++++++++ app/Models/Team.php | 6 + app/Models/WebhookNotificationSettings.php | 64 +++++++ app/Traits/HasNotificationSettings.php | 1 + ...te_webhook_notification_settings_table.php | 45 +++++ .../components/notification/navbar.blade.php | 4 + .../livewire/notifications/webhook.blade.php | 83 ++++++++ routes/web.php | 2 + 8 files changed, 385 insertions(+) create mode 100644 app/Livewire/Notifications/Webhook.php create mode 100644 app/Models/WebhookNotificationSettings.php create mode 100644 database/migrations/2025_10_10_120000_create_webhook_notification_settings_table.php create mode 100644 resources/views/livewire/notifications/webhook.blade.php diff --git a/app/Livewire/Notifications/Webhook.php b/app/Livewire/Notifications/Webhook.php new file mode 100644 index 000000000..491fc4b07 --- /dev/null +++ b/app/Livewire/Notifications/Webhook.php @@ -0,0 +1,180 @@ +team = auth()->user()->currentTeam(); + $this->settings = $this->team->webhookNotificationSettings; + $this->authorize('view', $this->settings); + $this->syncData(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function syncData(bool $toModel = false) + { + if ($toModel) { + $this->validate(); + $this->authorize('update', $this->settings); + $this->settings->webhook_enabled = $this->webhookEnabled; + $this->settings->webhook_url = $this->webhookUrl; + + $this->settings->deployment_success_webhook_notifications = $this->deploymentSuccessWebhookNotifications; + $this->settings->deployment_failure_webhook_notifications = $this->deploymentFailureWebhookNotifications; + $this->settings->status_change_webhook_notifications = $this->statusChangeWebhookNotifications; + $this->settings->backup_success_webhook_notifications = $this->backupSuccessWebhookNotifications; + $this->settings->backup_failure_webhook_notifications = $this->backupFailureWebhookNotifications; + $this->settings->scheduled_task_success_webhook_notifications = $this->scheduledTaskSuccessWebhookNotifications; + $this->settings->scheduled_task_failure_webhook_notifications = $this->scheduledTaskFailureWebhookNotifications; + $this->settings->docker_cleanup_success_webhook_notifications = $this->dockerCleanupSuccessWebhookNotifications; + $this->settings->docker_cleanup_failure_webhook_notifications = $this->dockerCleanupFailureWebhookNotifications; + $this->settings->server_disk_usage_webhook_notifications = $this->serverDiskUsageWebhookNotifications; + $this->settings->server_reachable_webhook_notifications = $this->serverReachableWebhookNotifications; + $this->settings->server_unreachable_webhook_notifications = $this->serverUnreachableWebhookNotifications; + $this->settings->server_patch_webhook_notifications = $this->serverPatchWebhookNotifications; + + $this->settings->save(); + refreshSession(); + } else { + $this->webhookEnabled = $this->settings->webhook_enabled; + $this->webhookUrl = $this->settings->webhook_url; + + $this->deploymentSuccessWebhookNotifications = $this->settings->deployment_success_webhook_notifications; + $this->deploymentFailureWebhookNotifications = $this->settings->deployment_failure_webhook_notifications; + $this->statusChangeWebhookNotifications = $this->settings->status_change_webhook_notifications; + $this->backupSuccessWebhookNotifications = $this->settings->backup_success_webhook_notifications; + $this->backupFailureWebhookNotifications = $this->settings->backup_failure_webhook_notifications; + $this->scheduledTaskSuccessWebhookNotifications = $this->settings->scheduled_task_success_webhook_notifications; + $this->scheduledTaskFailureWebhookNotifications = $this->settings->scheduled_task_failure_webhook_notifications; + $this->dockerCleanupSuccessWebhookNotifications = $this->settings->docker_cleanup_success_webhook_notifications; + $this->dockerCleanupFailureWebhookNotifications = $this->settings->docker_cleanup_failure_webhook_notifications; + $this->serverDiskUsageWebhookNotifications = $this->settings->server_disk_usage_webhook_notifications; + $this->serverReachableWebhookNotifications = $this->settings->server_reachable_webhook_notifications; + $this->serverUnreachableWebhookNotifications = $this->settings->server_unreachable_webhook_notifications; + $this->serverPatchWebhookNotifications = $this->settings->server_patch_webhook_notifications; + } + } + + public function instantSaveWebhookEnabled() + { + try { + $original = $this->webhookEnabled; + $this->validate([ + 'webhookUrl' => 'required', + ], [ + 'webhookUrl.required' => 'Webhook URL is required.', + ]); + $this->saveModel(); + } catch (\Throwable $e) { + $this->webhookEnabled = $original; + + return handleError($e, $this); + } + } + + public function instantSave() + { + try { + $this->syncData(true); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function submit() + { + try { + $this->resetErrorBag(); + $this->syncData(true); + $this->saveModel(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function saveModel() + { + $this->syncData(true); + refreshSession(); + $this->dispatch('success', 'Settings saved.'); + } + + public function sendTestNotification() + { + try { + $this->authorize('sendTest', $this->settings); + $this->team->notify(new Test(channel: 'webhook')); + $this->dispatch('success', 'Test notification sent.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function render() + { + return view('livewire.notifications.webhook'); + } +} diff --git a/app/Models/Team.php b/app/Models/Team.php index 51fdeffa4..0bdda9272 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -54,6 +54,7 @@ protected static function booted() $team->slackNotificationSettings()->create(); $team->telegramNotificationSettings()->create(); $team->pushoverNotificationSettings()->create(); + $team->webhookNotificationSettings()->create(); }); static::saving(function ($team) { @@ -307,4 +308,9 @@ public function pushoverNotificationSettings() { return $this->hasOne(PushoverNotificationSettings::class); } + + public function webhookNotificationSettings() + { + return $this->hasOne(WebhookNotificationSettings::class); + } } diff --git a/app/Models/WebhookNotificationSettings.php b/app/Models/WebhookNotificationSettings.php new file mode 100644 index 000000000..4ca89e0d3 --- /dev/null +++ b/app/Models/WebhookNotificationSettings.php @@ -0,0 +1,64 @@ + 'boolean', + 'webhook_url' => 'encrypted', + + 'deployment_success_webhook_notifications' => 'boolean', + 'deployment_failure_webhook_notifications' => 'boolean', + 'status_change_webhook_notifications' => 'boolean', + 'backup_success_webhook_notifications' => 'boolean', + 'backup_failure_webhook_notifications' => 'boolean', + 'scheduled_task_success_webhook_notifications' => 'boolean', + 'scheduled_task_failure_webhook_notifications' => 'boolean', + 'docker_cleanup_webhook_notifications' => 'boolean', + 'server_disk_usage_webhook_notifications' => 'boolean', + 'server_reachable_webhook_notifications' => 'boolean', + 'server_unreachable_webhook_notifications' => 'boolean', + 'server_patch_webhook_notifications' => 'boolean', + ]; + } + + public function team() + { + return $this->belongsTo(Team::class); + } + + public function isEnabled() + { + return $this->webhook_enabled; + } +} diff --git a/app/Traits/HasNotificationSettings.php b/app/Traits/HasNotificationSettings.php index 236e4d97c..d10122386 100644 --- a/app/Traits/HasNotificationSettings.php +++ b/app/Traits/HasNotificationSettings.php @@ -30,6 +30,7 @@ public function getNotificationSettings(string $channel): ?Model 'telegram' => $this->telegramNotificationSettings, 'slack' => $this->slackNotificationSettings, 'pushover' => $this->pushoverNotificationSettings, + 'webhook' => $this->webhookNotificationSettings, default => null, }; } diff --git a/database/migrations/2025_10_10_120000_create_webhook_notification_settings_table.php b/database/migrations/2025_10_10_120000_create_webhook_notification_settings_table.php new file mode 100644 index 000000000..d48b1e4a5 --- /dev/null +++ b/database/migrations/2025_10_10_120000_create_webhook_notification_settings_table.php @@ -0,0 +1,45 @@ +id(); + $table->foreignId('team_id')->constrained()->cascadeOnDelete(); + + $table->boolean('webhook_enabled')->default(false); + $table->text('webhook_url')->nullable(); + + $table->boolean('deployment_success_webhook_notifications')->default(false); + $table->boolean('deployment_failure_webhook_notifications')->default(true); + $table->boolean('status_change_webhook_notifications')->default(false); + $table->boolean('backup_success_webhook_notifications')->default(false); + $table->boolean('backup_failure_webhook_notifications')->default(true); + $table->boolean('scheduled_task_success_webhook_notifications')->default(false); + $table->boolean('scheduled_task_failure_webhook_notifications')->default(true); + $table->boolean('docker_cleanup_success_webhook_notifications')->default(false); + $table->boolean('docker_cleanup_failure_webhook_notifications')->default(true); + $table->boolean('server_disk_usage_webhook_notifications')->default(true); + $table->boolean('server_reachable_webhook_notifications')->default(false); + $table->boolean('server_unreachable_webhook_notifications')->default(true); + + $table->unique(['team_id']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('webhook_notification_settings'); + } +}; diff --git a/resources/views/components/notification/navbar.blade.php b/resources/views/components/notification/navbar.blade.php index c42ec28ec..47e23c6f2 100644 --- a/resources/views/components/notification/navbar.blade.php +++ b/resources/views/components/notification/navbar.blade.php @@ -23,6 +23,10 @@ href="{{ route('notifications.pushover') }}"> + + + diff --git a/resources/views/livewire/notifications/webhook.blade.php b/resources/views/livewire/notifications/webhook.blade.php new file mode 100644 index 000000000..2dea9a395 --- /dev/null +++ b/resources/views/livewire/notifications/webhook.blade.php @@ -0,0 +1,83 @@ +
+ + Notifications | Coolify + + +
+
+

Webhook

+ + Save + + @if ($webhookEnabled) + + Send Test Notification + + @else + + Send Test Notification + + @endif +
+
+ +
+ + +

Notification Settings

+

+ Select events for which you would like to receive webhook notifications. +

+
+
+

Deployments

+
+ + + +
+
+
+

Backups

+
+ + +
+
+
+

Scheduled Tasks

+
+ + +
+
+
+

Server

+
+ + + + + + +
+
+
+
diff --git a/routes/web.php b/routes/web.php index fd2ed8730..04610797c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -14,6 +14,7 @@ use App\Livewire\Notifications\Pushover as NotificationPushover; use App\Livewire\Notifications\Slack as NotificationSlack; use App\Livewire\Notifications\Telegram as NotificationTelegram; +use App\Livewire\Notifications\Webhook as NotificationWebhook; use App\Livewire\Profile\Index as ProfileIndex; use App\Livewire\Project\Application\Configuration as ApplicationConfiguration; use App\Livewire\Project\Application\Deployment\Index as DeploymentIndex; @@ -125,6 +126,7 @@ Route::get('/discord', NotificationDiscord::class)->name('notifications.discord'); Route::get('/slack', NotificationSlack::class)->name('notifications.slack'); Route::get('/pushover', NotificationPushover::class)->name('notifications.pushover'); + Route::get('/webhook', NotificationWebhook::class)->name('notifications.webhook'); }); Route::prefix('storages')->group(function () { From 53d1ad48cddb52cb94e6179575af7647d5ff5fc4 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 10 Oct 2025 17:47:26 +0200 Subject: [PATCH 02/14] fix: populate webhook notification settings for existing teams MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add migration to create webhook notification settings records for all existing teams. This fixes the "unauthorized" error when accessing the webhook notifications page for teams that existed before the webhook feature was added. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- ...tification_settings_for_existing_teams.php | 46 +++++++++++++++++++ templates/service-templates-latest.json | 2 +- templates/service-templates.json | 2 +- 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 database/migrations/2025_10_10_120001_populate_webhook_notification_settings_for_existing_teams.php diff --git a/database/migrations/2025_10_10_120001_populate_webhook_notification_settings_for_existing_teams.php b/database/migrations/2025_10_10_120001_populate_webhook_notification_settings_for_existing_teams.php new file mode 100644 index 000000000..4494ca927 --- /dev/null +++ b/database/migrations/2025_10_10_120001_populate_webhook_notification_settings_for_existing_teams.php @@ -0,0 +1,46 @@ +get(); + + foreach ($teams as $team) { + DB::table('webhook_notification_settings')->updateOrInsert( + ['team_id' => $team->id], + [ + 'webhook_enabled' => false, + 'webhook_url' => null, + 'deployment_success_webhook_notifications' => false, + 'deployment_failure_webhook_notifications' => true, + 'status_change_webhook_notifications' => false, + 'backup_success_webhook_notifications' => false, + 'backup_failure_webhook_notifications' => true, + 'scheduled_task_success_webhook_notifications' => false, + 'scheduled_task_failure_webhook_notifications' => true, + 'docker_cleanup_success_webhook_notifications' => false, + 'docker_cleanup_failure_webhook_notifications' => true, + 'server_disk_usage_webhook_notifications' => true, + 'server_reachable_webhook_notifications' => false, + 'server_unreachable_webhook_notifications' => true, + ] + ); + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // We don't need to do anything in down() since the webhook_notification_settings + // table will be dropped by the create migration's down() method + } +}; diff --git a/templates/service-templates-latest.json b/templates/service-templates-latest.json index bee653063..1d362e953 100644 --- a/templates/service-templates-latest.json +++ b/templates/service-templates-latest.json @@ -973,7 +973,7 @@ "ente-photos-with-s3": { "documentation": "https://help.ente.io/self-hosting/installation/compose?utm_source=coolify.io", "slogan": "Ente Photos is a fully open source, End to End Encrypted alternative to Google Photos and Apple Photos.", - "compose": "c2VydmljZXM6CiAgbXVzZXVtOgogICAgaW1hZ2U6ICdnaGNyLmlvL2VudGUtaW8vc2VydmVyOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfVVJMX01VU0VVTV84MDgwCiAgICAgIC0gJ0VOVEVfSFRUUF9VU0VfVExTPSR7RU5URV9IVFRQX1VTRV9UTFM6LWZhbHNlfScKICAgICAgLSAnRU5URV9BUFBTX1BVQkxJQ19BTEJVTVM9JHtTRVJWSUNFX1VSTF9XRUJfMzAwMn0nCiAgICAgIC0gJ0VOVEVfQVBQU19DQVNUPSR7U0VSVklDRV9VUkxfV0VCXzMwMDR9JwogICAgICAtICdFTlRFX0FQUFNfQUNDT1VOVFM9JHtTRVJWSUNFX1VSTF9XRUJfMzAwMX0nCiAgICAgIC0gJ0VOVEVfREJfSE9TVD0ke0VOVEVfREJfSE9TVDotcG9zdGdyZXN9JwogICAgICAtICdFTlRFX0RCX1BPUlQ9JHtFTlRFX0RCX1BPUlQ6LTU0MzJ9JwogICAgICAtICdFTlRFX0RCX05BTUU9JHtFTlRFX0RCX05BTUU6LWVudGVfZGJ9JwogICAgICAtICdFTlRFX0RCX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVM6LXBndXNlcn0nCiAgICAgIC0gJ0VOVEVfREJfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnRU5URV9LRVlfRU5DUllQVElPTj0ke1NFUlZJQ0VfUkVBTEJBU0U2NF9FTkNSWVBUSU9OfScKICAgICAgLSAnRU5URV9LRVlfSEFTSD0ke1NFUlZJQ0VfUkVBTEJBU0U2NF82NF9IQVNIfScKICAgICAgLSAnRU5URV9KV1RfU0VDUkVUPSR7U0VSVklDRV9SRUFMQkFTRTY0X0pXVH0nCiAgICAgIC0gJ0VOVEVfSU5URVJOQUxfQURNSU49JHtFTlRFX0lOVEVSTkFMX0FETUlOOi0xNTgwNTU5OTYyMzg2NDM4fScKICAgICAgLSAnRU5URV9JTlRFUk5BTF9ESVNBQkxFX1JFR0lTVFJBVElPTj0ke0VOVEVfSU5URVJOQUxfRElTQUJMRV9SRUdJU1RSQVRJT046LWZhbHNlfScKICAgICAgLSBTM19BUkVfTE9DQUxfQlVDS0VUUz10cnVlCiAgICAgIC0gUzNfVVNFX1BBVEhfU1RZTEVfVVJMUz10cnVlCiAgICAgIC0gJ1MzX0IyX0VVX0NFTl9LRVk9JHtTRVJWSUNFX1VTRVJfTUlOSU99JwogICAgICAtICdTM19CMl9FVV9DRU5fU0VDUkVUPSR7U0VSVklDRV9QQVNTV09SRF9NSU5JT30nCiAgICAgIC0gJ1MzX0IyX0VVX0NFTl9FTkRQT0lOVD0ke1NFUlZJQ0VfVVJMX01JTklPXzMyMDB9JwogICAgICAtIFMzX0IyX0VVX0NFTl9SRUdJT049ZXUtY2VudHJhbC0yCiAgICAgIC0gUzNfQjJfRVVfQ0VOX0JVQ0tFVD1iMi1ldS1jZW4KICAgIHZvbHVtZXM6CiAgICAgIC0gJ211c2V1bS1kYXRhOi9kYXRhJwogICAgICAtICdtdXNldW0tY29uZmlnOi9jb25maWcnCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3JlczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBtaW5pbzoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2Vfc3RhcnRlZAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIHdnZXQKICAgICAgICAtICctcU8tJwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6ODA4MC9waW5nJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogMTAKICB3ZWI6CiAgICBpbWFnZTogZ2hjci5pby9lbnRlLWlvL3dlYgogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9VUkxfV0VCXzMwMDAKICAgICAgLSAnRU5URV9BUElfT1JJR0lOPSR7U0VSVklDRV9VUkxfTVVTRVVNfScKICAgICAgLSAnRU5URV9BTEJVTVNfT1JJR0lOPSR7U0VSVklDRV9VUkxfV0VCXzMwMDJ9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctLWZhaWwnCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTozMDAwJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogMTAKICBwb3N0Z3JlczoKICAgIGltYWdlOiAncG9zdGdyZXM6MTUtYWxwaW5lJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTX0RCOi1lbnRlX2RifScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3Bvc3RncmVzLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICR7UE9TVEdSRVNfVVNFUn0gLWQgJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiAxMAogIG1pbmlvOgogICAgaW1hZ2U6ICdxdWF5LmlvL21pbmlvL21pbmlvOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfVVJMX01JTklPXzkwMDAKICAgICAgLSAnTUlOSU9fUk9PVF9VU0VSPSR7U0VSVklDRV9VU0VSX01JTklPfScKICAgICAgLSAnTUlOSU9fUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTUlOSU99JwogICAgY29tbWFuZDogJ3NlcnZlciAvZGF0YSAtLWFkZHJlc3MgIjo5MDAwIiAtLWNvbnNvbGUtYWRkcmVzcyAiOjkwMDEiJwogICAgdm9sdW1lczoKICAgICAgLSAnbWluaW8tZGF0YTovZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBtYwogICAgICAgIC0gcmVhZHkKICAgICAgICAtIGxvY2FsCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICBtaW5pby1pbml0OgogICAgaW1hZ2U6ICdtaW5pby9tYzpsYXRlc3QnCiAgICBleGNsdWRlX2Zyb21faGM6IHRydWUKICAgIHJlc3RhcnQ6ICdubycKICAgIGRlcGVuZHNfb246CiAgICAgIG1pbmlvOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTUlOSU9fUk9PVF9VU0VSPSR7U0VSVklDRV9VU0VSX01JTklPfScKICAgICAgLSAnTUlOSU9fUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTUlOSU99JwogICAgZW50cnlwb2ludDogIi9iaW4vc2ggLWMgXCIgbWMgYWxpYXMgc2V0IG1pbmlvIGh0dHA6Ly9taW5pbzo5MDAwICQke01JTklPX1JPT1RfVVNFUn0gJCR7TUlOSU9fUk9PVF9QQVNTV09SRH07IG1jIG1iIG1pbmlvL2IyLWV1LWNlbiAtLWlnbm9yZS1leGlzdGluZzsgbWMgbWIgbWluaW8vd2FzYWJpLWV1LWNlbnRyYWwtMi12MyAtLWlnbm9yZS1leGlzdGluZzsgbWMgbWIgbWluaW8vc2N3LWV1LWZyLXYzIC0taWdub3JlLWV4aXN0aW5nOyBlY2hvICdNaW5JTyBidWNrZXRzIGNyZWF0ZWQgc3VjY2Vzc2Z1bGx5JzsgXCJcbiIK", + "compose": "c2VydmljZXM6CiAgbXVzZXVtOgogICAgaW1hZ2U6ICdnaGNyLmlvL2VudGUtaW8vc2VydmVyOjYxM2M2YTk2MzkwZDdhNjI0Y2YzMGI5NDY5NTU3MDVkNjMyNDIzY2MnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX1VSTF9NVVNFVU1fODA4MAogICAgICAtIEVOVEVfREJfSE9TVD1wb3N0Z3JlcwogICAgICAtIEVOVEVfREJfUE9SVD01NDMyCiAgICAgIC0gJ0VOVEVfREJfTkFNRT0ke1BPU1RHUkVTX0RCOi1lbnRlX2RifScKICAgICAgLSAnRU5URV9EQl9VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfScKICAgICAgLSAnRU5URV9EQl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdFTlRFX0hUVFBfVVNFX1RMUz0ke0VOVEVfSFRUUF9VU0VfVExTOi1mYWxzZX0nCiAgICAgIC0gRU5URV9TM19BUkVfTE9DQUxfQlVDS0VUUz1mYWxzZQogICAgICAtIEVOVEVfUzNfVVNFX1BBVEhfU1RZTEVfVVJMUz10cnVlCiAgICAgIC0gJ0VOVEVfUzNfQjJfRVVfQ0VOX0tFWT0ke1NFUlZJQ0VfVVNFUl9NSU5JT30nCiAgICAgIC0gJ0VOVEVfUzNfQjJfRVVfQ0VOX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfTUlOSU99JwogICAgICAtICdFTlRFX1MzX0IyX0VVX0NFTl9FTkRQT0lOVD0ke1NFUlZJQ0VfRlFETl9NSU5JT185MDAwfScKICAgICAgLSBFTlRFX1MzX0IyX0VVX0NFTl9SRUdJT049ZXUtY2VudHJhbC0yCiAgICAgIC0gRU5URV9TM19CMl9FVV9DRU5fQlVDS0VUPWIyLWV1LWNlbgogICAgICAtICdFTlRFX0tFWV9FTkNSWVBUSU9OPSR7U0VSVklDRV9SRUFMQkFTRTY0X0VOQ1JZUFRJT059JwogICAgICAtICdFTlRFX0tFWV9IQVNIPSR7U0VSVklDRV9SRUFMQkFTRTY0XzY0X0hBU0h9JwogICAgICAtICdFTlRFX0pXVF9TRUNSRVQ9JHtTRVJWSUNFX1JFQUxCQVNFNjRfSldUfScKICAgICAgLSAnRU5URV9JTlRFUk5BTF9BRE1JTj0ke0VOVEVfSU5URVJOQUxfQURNSU46LTE1ODA1NTk5NjIzODY0Mzh9JwogICAgICAtICdFTlRFX0lOVEVSTkFMX0RJU0FCTEVfUkVHSVNUUkFUSU9OPSR7RU5URV9JTlRFUk5BTF9ESVNBQkxFX1JFR0lTVFJBVElPTjotZmFsc2V9JwogICAgdm9sdW1lczoKICAgICAgLSAnbXVzZXVtLWRhdGE6L2RhdGEnCiAgICAgIC0gJ211c2V1bS1jb25maWc6L2NvbmZpZycKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIG1pbmlvOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9zdGFydGVkCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tc3BpZGVyJwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6ODA4MC9waW5nJwogICAgICBpbnRlcnZhbDogMzBzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAzCiAgd2ViOgogICAgaW1hZ2U6ICdnaGNyLmlvL2VudGUtaW8vd2ViOmNhMDMxNjVmNWU3ZjJhNTAxMDVlNmU0MDAxOWMxN2FlNmNkZDkzNGYnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX1VSTF9XRUJfMzAwMAogICAgICAtICdFTlRFX0FQSV9PUklHSU49JHtTRVJWSUNFX1VSTF9NVVNFVU19JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctLWZhaWwnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDozMDAwJwogICAgICBpbnRlcnZhbDogMzBzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAzCiAgICAgIHN0YXJ0X3BlcmlvZDogMTBzCiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE1LWFscGluZScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotZW50ZV9kYn0nCiAgICB2b2x1bWVzOgogICAgICAtICdwb3N0Z3Jlcy1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30gLWQgJHtQT1NUR1JFU19EQjotZW50ZV9kYn0nCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogNQogIG1pbmlvOgogICAgaW1hZ2U6ICdxdWF5LmlvL21pbmlvL21pbmlvOlJFTEVBU0UuMjAyNS0wOS0wN1QxNi0xMy0wOVonCiAgICBjb21tYW5kOiAnc2VydmVyIC9kYXRhIC0tY29uc29sZS1hZGRyZXNzICI6OTAwMSInCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBNSU5JT19TRVJWRVJfVVJMPSRNSU5JT19TRVJWRVJfVVJMCiAgICAgIC0gTUlOSU9fQlJPV1NFUl9SRURJUkVDVF9VUkw9JE1JTklPX0JST1dTRVJfUkVESVJFQ1RfVVJMCiAgICAgIC0gTUlOSU9fUk9PVF9VU0VSPSRTRVJWSUNFX1VTRVJfTUlOSU8KICAgICAgLSBNSU5JT19ST09UX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX01JTklPCiAgICB2b2x1bWVzOgogICAgICAtICdtaW5pby1kYXRhOi9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIG1jCiAgICAgICAgLSByZWFkeQogICAgICAgIC0gbG9jYWwKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIG1pbmlvLWluaXQ6CiAgICBpbWFnZTogJ21pbmlvL21jOlJFTEVBU0UuMjAyNS0wOC0xM1QwOC0zNS00MVonCiAgICBkZXBlbmRzX29uOgogICAgICBtaW5pbzoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2Vfc3RhcnRlZAogICAgcmVzdGFydDogb24tZmFpbHVyZQogICAgZXhjbHVkZV9mcm9tX2hjOiB0cnVlCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTUlOSU9fUk9PVF9VU0VSPSR7U0VSVklDRV9VU0VSX01JTklPfScKICAgICAgLSAnTUlOSU9fUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTUlOSU99JwogICAgICAtICdNSU5JT19DT1JTX1VSTFM9JFNFUlZJQ0VfVVJMX01VU0VVTSwkU0VSVklDRV9VUkxfV0VCJwogICAgZW50cnlwb2ludDogIi9iaW4vc2ggLWMgXCJcbiAgZWNobyBcXFwiTUlOSU9fQ09SU19VUkxTOiBcXCQke01JTklPX0NPUlNfVVJMU31cXFwiO1xuICBzbGVlcCA1O1xuICB1bnRpbCBtYyBhbGlhcyBzZXQgbWluaW8gaHR0cDovL21pbmlvOjkwMDAgXFwkJHtNSU5JT19ST09UX1VTRVJ9IFxcJCR7TUlOSU9fUk9PVF9QQVNTV09SRH07IGRvXG4gICAgZWNobyAnV2FpdGluZyBmb3IgTWluSU8uLi4nO1xuICAgIHNsZWVwIDI7XG4gIGRvbmU7XG4gIG1jIGFkbWluIGNvbmZpZyBzZXQgbWluaW8gYXBpIGNvcnNfYWxsb3dfb3JpZ2luPSckTUlOSU9fQ09SU19VUkxTJyB8fCB0cnVlO1xuICBtYyBtYiBtaW5pby9iMi1ldS1jZW4gLS1pZ25vcmUtZXhpc3Rpbmc7XG4gIG1jIG1iIG1pbmlvL3dhc2FiaS1ldS1jZW50cmFsLTItdjMgLS1pZ25vcmUtZXhpc3Rpbmc7XG4gIG1jIG1iIG1pbmlvL3Njdy1ldS1mci12MyAtLWlnbm9yZS1leGlzdGluZztcbiAgZWNobyAnTWluSU8gYnVja2V0cyBhbmQgQ09SUyBjb25maWd1cmVkJztcblwiIgo=", "tags": [ "photos", "gallery", diff --git a/templates/service-templates.json b/templates/service-templates.json index e8f4fef80..8a2b2975c 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -973,7 +973,7 @@ "ente-photos-with-s3": { "documentation": "https://help.ente.io/self-hosting/installation/compose?utm_source=coolify.io", "slogan": "Ente Photos is a fully open source, End to End Encrypted alternative to Google Photos and Apple Photos.", - "compose": "c2VydmljZXM6CiAgbXVzZXVtOgogICAgaW1hZ2U6ICdnaGNyLmlvL2VudGUtaW8vc2VydmVyOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9NVVNFVU1fODA4MAogICAgICAtICdFTlRFX0hUVFBfVVNFX1RMUz0ke0VOVEVfSFRUUF9VU0VfVExTOi1mYWxzZX0nCiAgICAgIC0gJ0VOVEVfQVBQU19QVUJMSUNfQUxCVU1TPSR7U0VSVklDRV9GUUROX1dFQl8zMDAyfScKICAgICAgLSAnRU5URV9BUFBTX0NBU1Q9JHtTRVJWSUNFX0ZRRE5fV0VCXzMwMDR9JwogICAgICAtICdFTlRFX0FQUFNfQUNDT1VOVFM9JHtTRVJWSUNFX0ZRRE5fV0VCXzMwMDF9JwogICAgICAtICdFTlRFX0RCX0hPU1Q9JHtFTlRFX0RCX0hPU1Q6LXBvc3RncmVzfScKICAgICAgLSAnRU5URV9EQl9QT1JUPSR7RU5URV9EQl9QT1JUOi01NDMyfScKICAgICAgLSAnRU5URV9EQl9OQU1FPSR7RU5URV9EQl9OQU1FOi1lbnRlX2RifScKICAgICAgLSAnRU5URV9EQl9VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTOi1wZ3VzZXJ9JwogICAgICAtICdFTlRFX0RCX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ0VOVEVfS0VZX0VOQ1JZUFRJT049JHtTRVJWSUNFX1JFQUxCQVNFNjRfRU5DUllQVElPTn0nCiAgICAgIC0gJ0VOVEVfS0VZX0hBU0g9JHtTRVJWSUNFX1JFQUxCQVNFNjRfNjRfSEFTSH0nCiAgICAgIC0gJ0VOVEVfSldUX1NFQ1JFVD0ke1NFUlZJQ0VfUkVBTEJBU0U2NF9KV1R9JwogICAgICAtICdFTlRFX0lOVEVSTkFMX0FETUlOPSR7RU5URV9JTlRFUk5BTF9BRE1JTjotMTU4MDU1OTk2MjM4NjQzOH0nCiAgICAgIC0gJ0VOVEVfSU5URVJOQUxfRElTQUJMRV9SRUdJU1RSQVRJT049JHtFTlRFX0lOVEVSTkFMX0RJU0FCTEVfUkVHSVNUUkFUSU9OOi1mYWxzZX0nCiAgICAgIC0gUzNfQVJFX0xPQ0FMX0JVQ0tFVFM9dHJ1ZQogICAgICAtIFMzX1VTRV9QQVRIX1NUWUxFX1VSTFM9dHJ1ZQogICAgICAtICdTM19CMl9FVV9DRU5fS0VZPSR7U0VSVklDRV9VU0VSX01JTklPfScKICAgICAgLSAnUzNfQjJfRVVfQ0VOX1NFQ1JFVD0ke1NFUlZJQ0VfUEFTU1dPUkRfTUlOSU99JwogICAgICAtICdTM19CMl9FVV9DRU5fRU5EUE9JTlQ9JHtTRVJWSUNFX0ZRRE5fTUlOSU9fMzIwMH0nCiAgICAgIC0gUzNfQjJfRVVfQ0VOX1JFR0lPTj1ldS1jZW50cmFsLTIKICAgICAgLSBTM19CMl9FVV9DRU5fQlVDS0VUPWIyLWV1LWNlbgogICAgdm9sdW1lczoKICAgICAgLSAnbXVzZXVtLWRhdGE6L2RhdGEnCiAgICAgIC0gJ211c2V1bS1jb25maWc6L2NvbmZpZycKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIG1pbmlvOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9zdGFydGVkCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy1xTy0nCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo4MDgwL3BpbmcnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiAxMAogIHdlYjoKICAgIGltYWdlOiBnaGNyLmlvL2VudGUtaW8vd2ViCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fV0VCXzMwMDAKICAgICAgLSAnRU5URV9BUElfT1JJR0lOPSR7U0VSVklDRV9GUUROX01VU0VVTX0nCiAgICAgIC0gJ0VOVEVfQUxCVU1TX09SSUdJTj0ke1NFUlZJQ0VfRlFETl9XRUJfMzAwMn0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy0tZmFpbCcKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjMwMDAnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiAxMAogIHBvc3RncmVzOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNS1hbHBpbmUnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LWVudGVfZGJ9JwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGdyZXMtZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJHtQT1NUR1JFU19VU0VSfSAtZCAke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDEwCiAgbWluaW86CiAgICBpbWFnZTogJ3F1YXkuaW8vbWluaW8vbWluaW86bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX01JTklPXzkwMDAKICAgICAgLSAnTUlOSU9fUk9PVF9VU0VSPSR7U0VSVklDRV9VU0VSX01JTklPfScKICAgICAgLSAnTUlOSU9fUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTUlOSU99JwogICAgY29tbWFuZDogJ3NlcnZlciAvZGF0YSAtLWFkZHJlc3MgIjo5MDAwIiAtLWNvbnNvbGUtYWRkcmVzcyAiOjkwMDEiJwogICAgdm9sdW1lczoKICAgICAgLSAnbWluaW8tZGF0YTovZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBtYwogICAgICAgIC0gcmVhZHkKICAgICAgICAtIGxvY2FsCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICBtaW5pby1pbml0OgogICAgaW1hZ2U6ICdtaW5pby9tYzpsYXRlc3QnCiAgICBleGNsdWRlX2Zyb21faGM6IHRydWUKICAgIHJlc3RhcnQ6ICdubycKICAgIGRlcGVuZHNfb246CiAgICAgIG1pbmlvOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTUlOSU9fUk9PVF9VU0VSPSR7U0VSVklDRV9VU0VSX01JTklPfScKICAgICAgLSAnTUlOSU9fUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTUlOSU99JwogICAgZW50cnlwb2ludDogIi9iaW4vc2ggLWMgXCIgbWMgYWxpYXMgc2V0IG1pbmlvIGh0dHA6Ly9taW5pbzo5MDAwICQke01JTklPX1JPT1RfVVNFUn0gJCR7TUlOSU9fUk9PVF9QQVNTV09SRH07IG1jIG1iIG1pbmlvL2IyLWV1LWNlbiAtLWlnbm9yZS1leGlzdGluZzsgbWMgbWIgbWluaW8vd2FzYWJpLWV1LWNlbnRyYWwtMi12MyAtLWlnbm9yZS1leGlzdGluZzsgbWMgbWIgbWluaW8vc2N3LWV1LWZyLXYzIC0taWdub3JlLWV4aXN0aW5nOyBlY2hvICdNaW5JTyBidWNrZXRzIGNyZWF0ZWQgc3VjY2Vzc2Z1bGx5JzsgXCJcbiIK", + "compose": "c2VydmljZXM6CiAgbXVzZXVtOgogICAgaW1hZ2U6ICdnaGNyLmlvL2VudGUtaW8vc2VydmVyOjYxM2M2YTk2MzkwZDdhNjI0Y2YzMGI5NDY5NTU3MDVkNjMyNDIzY2MnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTVVTRVVNXzgwODAKICAgICAgLSBFTlRFX0RCX0hPU1Q9cG9zdGdyZXMKICAgICAgLSBFTlRFX0RCX1BPUlQ9NTQzMgogICAgICAtICdFTlRFX0RCX05BTUU9JHtQT1NUR1JFU19EQjotZW50ZV9kYn0nCiAgICAgIC0gJ0VOVEVfREJfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ0VOVEVfREJfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnRU5URV9IVFRQX1VTRV9UTFM9JHtFTlRFX0hUVFBfVVNFX1RMUzotZmFsc2V9JwogICAgICAtIEVOVEVfUzNfQVJFX0xPQ0FMX0JVQ0tFVFM9ZmFsc2UKICAgICAgLSBFTlRFX1MzX1VTRV9QQVRIX1NUWUxFX1VSTFM9dHJ1ZQogICAgICAtICdFTlRFX1MzX0IyX0VVX0NFTl9LRVk9JHtTRVJWSUNFX1VTRVJfTUlOSU99JwogICAgICAtICdFTlRFX1MzX0IyX0VVX0NFTl9TRUNSRVQ9JHtTRVJWSUNFX1BBU1NXT1JEX01JTklPfScKICAgICAgLSAnRU5URV9TM19CMl9FVV9DRU5fRU5EUE9JTlQ9JHtTRVJWSUNFX0ZRRE5fTUlOSU9fOTAwMH0nCiAgICAgIC0gRU5URV9TM19CMl9FVV9DRU5fUkVHSU9OPWV1LWNlbnRyYWwtMgogICAgICAtIEVOVEVfUzNfQjJfRVVfQ0VOX0JVQ0tFVD1iMi1ldS1jZW4KICAgICAgLSAnRU5URV9LRVlfRU5DUllQVElPTj0ke1NFUlZJQ0VfUkVBTEJBU0U2NF9FTkNSWVBUSU9OfScKICAgICAgLSAnRU5URV9LRVlfSEFTSD0ke1NFUlZJQ0VfUkVBTEJBU0U2NF82NF9IQVNIfScKICAgICAgLSAnRU5URV9KV1RfU0VDUkVUPSR7U0VSVklDRV9SRUFMQkFTRTY0X0pXVH0nCiAgICAgIC0gJ0VOVEVfSU5URVJOQUxfQURNSU49JHtFTlRFX0lOVEVSTkFMX0FETUlOOi0xNTgwNTU5OTYyMzg2NDM4fScKICAgICAgLSAnRU5URV9JTlRFUk5BTF9ESVNBQkxFX1JFR0lTVFJBVElPTj0ke0VOVEVfSU5URVJOQUxfRElTQUJMRV9SRUdJU1RSQVRJT046LWZhbHNlfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ211c2V1bS1kYXRhOi9kYXRhJwogICAgICAtICdtdXNldW0tY29uZmlnOi9jb25maWcnCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3JlczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBtaW5pbzoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2Vfc3RhcnRlZAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIHdnZXQKICAgICAgICAtICctLXNwaWRlcicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjgwODAvcGluZycKICAgICAgaW50ZXJ2YWw6IDMwcwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMwogIHdlYjoKICAgIGltYWdlOiAnZ2hjci5pby9lbnRlLWlvL3dlYjpjYTAzMTY1ZjVlN2YyYTUwMTA1ZTZlNDAwMTljMTdhZTZjZGQ5MzRmJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX1dFQl8zMDAwCiAgICAgIC0gJ0VOVEVfQVBJX09SSUdJTj0ke1NFUlZJQ0VfRlFETl9NVVNFVU19JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctLWZhaWwnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDozMDAwJwogICAgICBpbnRlcnZhbDogMzBzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAzCiAgICAgIHN0YXJ0X3BlcmlvZDogMTBzCiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE1LWFscGluZScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotZW50ZV9kYn0nCiAgICB2b2x1bWVzOgogICAgICAtICdwb3N0Z3Jlcy1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30gLWQgJHtQT1NUR1JFU19EQjotZW50ZV9kYn0nCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogNQogIG1pbmlvOgogICAgaW1hZ2U6ICdxdWF5LmlvL21pbmlvL21pbmlvOlJFTEVBU0UuMjAyNS0wOS0wN1QxNi0xMy0wOVonCiAgICBjb21tYW5kOiAnc2VydmVyIC9kYXRhIC0tY29uc29sZS1hZGRyZXNzICI6OTAwMSInCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBNSU5JT19TRVJWRVJfVVJMPSRNSU5JT19TRVJWRVJfVVJMCiAgICAgIC0gTUlOSU9fQlJPV1NFUl9SRURJUkVDVF9VUkw9JE1JTklPX0JST1dTRVJfUkVESVJFQ1RfVVJMCiAgICAgIC0gTUlOSU9fUk9PVF9VU0VSPSRTRVJWSUNFX1VTRVJfTUlOSU8KICAgICAgLSBNSU5JT19ST09UX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX01JTklPCiAgICB2b2x1bWVzOgogICAgICAtICdtaW5pby1kYXRhOi9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIG1jCiAgICAgICAgLSByZWFkeQogICAgICAgIC0gbG9jYWwKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIG1pbmlvLWluaXQ6CiAgICBpbWFnZTogJ21pbmlvL21jOlJFTEVBU0UuMjAyNS0wOC0xM1QwOC0zNS00MVonCiAgICBkZXBlbmRzX29uOgogICAgICBtaW5pbzoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2Vfc3RhcnRlZAogICAgcmVzdGFydDogb24tZmFpbHVyZQogICAgZXhjbHVkZV9mcm9tX2hjOiB0cnVlCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTUlOSU9fUk9PVF9VU0VSPSR7U0VSVklDRV9VU0VSX01JTklPfScKICAgICAgLSAnTUlOSU9fUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTUlOSU99JwogICAgICAtICdNSU5JT19DT1JTX1VSTFM9JFNFUlZJQ0VfRlFETl9NVVNFVU0sJFNFUlZJQ0VfRlFETl9XRUInCiAgICBlbnRyeXBvaW50OiAiL2Jpbi9zaCAtYyBcIlxuICBlY2hvIFxcXCJNSU5JT19DT1JTX1VSTFM6IFxcJCR7TUlOSU9fQ09SU19VUkxTfVxcXCI7XG4gIHNsZWVwIDU7XG4gIHVudGlsIG1jIGFsaWFzIHNldCBtaW5pbyBodHRwOi8vbWluaW86OTAwMCBcXCQke01JTklPX1JPT1RfVVNFUn0gXFwkJHtNSU5JT19ST09UX1BBU1NXT1JEfTsgZG9cbiAgICBlY2hvICdXYWl0aW5nIGZvciBNaW5JTy4uLic7XG4gICAgc2xlZXAgMjtcbiAgZG9uZTtcbiAgbWMgYWRtaW4gY29uZmlnIHNldCBtaW5pbyBhcGkgY29yc19hbGxvd19vcmlnaW49JyRNSU5JT19DT1JTX1VSTFMnIHx8IHRydWU7XG4gIG1jIG1iIG1pbmlvL2IyLWV1LWNlbiAtLWlnbm9yZS1leGlzdGluZztcbiAgbWMgbWIgbWluaW8vd2FzYWJpLWV1LWNlbnRyYWwtMi12MyAtLWlnbm9yZS1leGlzdGluZztcbiAgbWMgbWIgbWluaW8vc2N3LWV1LWZyLXYzIC0taWdub3JlLWV4aXN0aW5nO1xuICBlY2hvICdNaW5JTyBidWNrZXRzIGFuZCBDT1JTIGNvbmZpZ3VyZWQnO1xuXCIiCg==", "tags": [ "photos", "gallery", From eea372d702fae8b99574dd2e690db21634348676 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 10 Oct 2025 17:48:14 +0200 Subject: [PATCH 03/14] fix: register WebhookNotificationSettings with NotificationPolicy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add WebhookNotificationSettings to the policy mappings in AuthServiceProvider to enable authorization checks for the webhook notification settings. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/Providers/AuthServiceProvider.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index c017a580e..5d3347936 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -45,6 +45,7 @@ class AuthServiceProvider extends ServiceProvider \App\Models\TelegramNotificationSettings::class => \App\Policies\NotificationPolicy::class, \App\Models\SlackNotificationSettings::class => \App\Policies\NotificationPolicy::class, \App\Models\PushoverNotificationSettings::class => \App\Policies\NotificationPolicy::class, + \App\Models\WebhookNotificationSettings::class => \App\Policies\NotificationPolicy::class, // API Token policy \Laravel\Sanctum\PersonalAccessToken::class => \App\Policies\ApiTokenPolicy::class, From 22ef6c8c9a257b0f7bf3ef9e9458348428e226e2 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 10 Oct 2025 17:52:07 +0200 Subject: [PATCH 04/14] fix: add missing server_patch_webhook_notifications field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the server_patch_webhook_notifications column to both the create and populate migrations to match the model and Livewire component expectations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- ...5_10_10_120000_create_webhook_notification_settings_table.php | 1 + ...populate_webhook_notification_settings_for_existing_teams.php | 1 + 2 files changed, 2 insertions(+) diff --git a/database/migrations/2025_10_10_120000_create_webhook_notification_settings_table.php b/database/migrations/2025_10_10_120000_create_webhook_notification_settings_table.php index d48b1e4a5..a3edacbf9 100644 --- a/database/migrations/2025_10_10_120000_create_webhook_notification_settings_table.php +++ b/database/migrations/2025_10_10_120000_create_webhook_notification_settings_table.php @@ -30,6 +30,7 @@ public function up(): void $table->boolean('server_disk_usage_webhook_notifications')->default(true); $table->boolean('server_reachable_webhook_notifications')->default(false); $table->boolean('server_unreachable_webhook_notifications')->default(true); + $table->boolean('server_patch_webhook_notifications')->default(false); $table->unique(['team_id']); }); diff --git a/database/migrations/2025_10_10_120001_populate_webhook_notification_settings_for_existing_teams.php b/database/migrations/2025_10_10_120001_populate_webhook_notification_settings_for_existing_teams.php index 4494ca927..de2707557 100644 --- a/database/migrations/2025_10_10_120001_populate_webhook_notification_settings_for_existing_teams.php +++ b/database/migrations/2025_10_10_120001_populate_webhook_notification_settings_for_existing_teams.php @@ -30,6 +30,7 @@ public function up(): void 'server_disk_usage_webhook_notifications' => true, 'server_reachable_webhook_notifications' => false, 'server_unreachable_webhook_notifications' => true, + 'server_patch_webhook_notifications' => false, ] ); } From 22153c419d4a07e96336ae34a6457b477f60f19e Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 10 Oct 2025 17:55:11 +0200 Subject: [PATCH 05/14] feat: add webhook placeholder to Test notification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add webhook case to the Test notification's via() method to prepare for future WebhookChannel implementation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/Notifications/Test.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Notifications/Test.php b/app/Notifications/Test.php index 0b1d8d6b1..430eb7b48 100644 --- a/app/Notifications/Test.php +++ b/app/Notifications/Test.php @@ -36,6 +36,7 @@ public function via(object $notifiable): array 'telegram' => [TelegramChannel::class], 'slack' => [SlackChannel::class], 'pushover' => [PushoverChannel::class], + 'webhook' => [], // WebhookChannel will be implemented later default => [], }; } else { From 729c891542df36eae5d957190fa570a6a87ca4a0 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 10 Oct 2025 17:57:10 +0200 Subject: [PATCH 06/14] feat: add WebhookChannel placeholder implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add basic WebhookChannel infrastructure: - Create SendsWebhook interface - Create WebhookChannel with placeholder implementation (logs instead of sending) - Update Test notification to support webhook channel - Add WebhookChannel to HasNotificationSettings trait - Add toWebhook() method to Test notification This provides a working foundation that won't break test notifications. The actual HTTP webhook delivery will be implemented in a follow-up. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/Notifications/Channels/SendsWebhook.php | 8 +++++ app/Notifications/Channels/WebhookChannel.php | 33 +++++++++++++++++++ app/Notifications/Test.php | 12 ++++++- app/Traits/HasNotificationSettings.php | 2 ++ 4 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 app/Notifications/Channels/SendsWebhook.php create mode 100644 app/Notifications/Channels/WebhookChannel.php diff --git a/app/Notifications/Channels/SendsWebhook.php b/app/Notifications/Channels/SendsWebhook.php new file mode 100644 index 000000000..9301a4ff0 --- /dev/null +++ b/app/Notifications/Channels/SendsWebhook.php @@ -0,0 +1,8 @@ +webhookNotificationSettings; + + if (! $webhookSettings || ! $webhookSettings->isEnabled() || ! $webhookSettings->webhook_url) { + return; + } + + // TODO: Implement actual webhook delivery + // This is a placeholder implementation + // You'll need to: + // 1. Get the webhook payload from $notification->toWebhook() + // 2. Create a job to send the HTTP POST request to $webhookSettings->webhook_url + // 3. Handle retries and errors appropriately + + Log::info('Webhook notification would be sent', [ + 'url' => $webhookSettings->webhook_url, + 'notification' => get_class($notification), + ]); + } +} diff --git a/app/Notifications/Test.php b/app/Notifications/Test.php index 430eb7b48..5a635a601 100644 --- a/app/Notifications/Test.php +++ b/app/Notifications/Test.php @@ -7,6 +7,7 @@ use App\Notifications\Channels\PushoverChannel; use App\Notifications\Channels\SlackChannel; use App\Notifications\Channels\TelegramChannel; +use App\Notifications\Channels\WebhookChannel; use App\Notifications\Dto\DiscordMessage; use App\Notifications\Dto\PushoverMessage; use App\Notifications\Dto\SlackMessage; @@ -36,7 +37,7 @@ public function via(object $notifiable): array 'telegram' => [TelegramChannel::class], 'slack' => [SlackChannel::class], 'pushover' => [PushoverChannel::class], - 'webhook' => [], // WebhookChannel will be implemented later + 'webhook' => [WebhookChannel::class], default => [], }; } else { @@ -111,4 +112,13 @@ public function toSlack(): SlackMessage description: 'This is a test Slack notification from Coolify.' ); } + + public function toWebhook(): array + { + return [ + 'event' => 'test', + 'message' => 'This is a test webhook notification from Coolify.', + 'url' => base_url(), + ]; + } } diff --git a/app/Traits/HasNotificationSettings.php b/app/Traits/HasNotificationSettings.php index d10122386..297b44a06 100644 --- a/app/Traits/HasNotificationSettings.php +++ b/app/Traits/HasNotificationSettings.php @@ -7,6 +7,7 @@ use App\Notifications\Channels\PushoverChannel; use App\Notifications\Channels\SlackChannel; use App\Notifications\Channels\TelegramChannel; +use App\Notifications\Channels\WebhookChannel; use Illuminate\Database\Eloquent\Model; trait HasNotificationSettings @@ -78,6 +79,7 @@ public function getEnabledChannels(string $event): array 'telegram' => TelegramChannel::class, 'slack' => SlackChannel::class, 'pushover' => PushoverChannel::class, + 'webhook' => WebhookChannel::class, ]; if ($event === 'general') { From 413dee5d8c97edefd4b359831d6db766b1235c9c Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 10 Oct 2025 17:59:17 +0200 Subject: [PATCH 07/14] feat: implement actual webhook delivery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement full webhook delivery functionality: - Create SendWebhookJob to handle HTTP POST requests - Update WebhookChannel to dispatch webhook jobs - Configure retry logic (5 attempts, 10s backoff) - Update Test notification payload with success/message structure Webhook payload structure: { "success": true/false, "message": "notification message", "event": "event_type", "url": "coolify_dashboard_url" } 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/Jobs/SendWebhookJob.php | 45 +++++++++++++++++++ app/Notifications/Channels/WebhookChannel.php | 14 ++---- app/Notifications/Test.php | 3 +- 3 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 app/Jobs/SendWebhookJob.php diff --git a/app/Jobs/SendWebhookJob.php b/app/Jobs/SendWebhookJob.php new file mode 100644 index 000000000..865dd9493 --- /dev/null +++ b/app/Jobs/SendWebhookJob.php @@ -0,0 +1,45 @@ +onQueue('high'); + } + + /** + * Execute the job. + */ + public function handle(): void + { + Http::post($this->webhookUrl, $this->payload); + } +} diff --git a/app/Notifications/Channels/WebhookChannel.php b/app/Notifications/Channels/WebhookChannel.php index 53524e4f5..757e5e934 100644 --- a/app/Notifications/Channels/WebhookChannel.php +++ b/app/Notifications/Channels/WebhookChannel.php @@ -2,8 +2,8 @@ namespace App\Notifications\Channels; +use App\Jobs\SendWebhookJob; use Illuminate\Notifications\Notification; -use Illuminate\Support\Facades\Log; class WebhookChannel { @@ -18,16 +18,8 @@ public function send(SendsWebhook $notifiable, Notification $notification): void return; } - // TODO: Implement actual webhook delivery - // This is a placeholder implementation - // You'll need to: - // 1. Get the webhook payload from $notification->toWebhook() - // 2. Create a job to send the HTTP POST request to $webhookSettings->webhook_url - // 3. Handle retries and errors appropriately + $payload = $notification->toWebhook(); - Log::info('Webhook notification would be sent', [ - 'url' => $webhookSettings->webhook_url, - 'notification' => get_class($notification), - ]); + SendWebhookJob::dispatch($payload, $webhookSettings->webhook_url); } } diff --git a/app/Notifications/Test.php b/app/Notifications/Test.php index 5a635a601..60bc8a0ee 100644 --- a/app/Notifications/Test.php +++ b/app/Notifications/Test.php @@ -116,8 +116,9 @@ public function toSlack(): SlackMessage public function toWebhook(): array { return [ - 'event' => 'test', + 'success' => true, 'message' => 'This is a test webhook notification from Coolify.', + 'event' => 'test', 'url' => base_url(), ]; } From dc15bee980ed823960b3ce97e6ed21191ab79e28 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 10 Oct 2025 18:07:04 +0200 Subject: [PATCH 08/14] feat: implement actual webhook delivery with Ray debugging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added actual HTTP POST delivery for webhook notifications and comprehensive Ray debugging for development. Changes: - Updated Team model to implement SendsWebhook interface - Added routeNotificationForWebhook() method to Team - Enhanced SendWebhookJob with Ray logging for request/response - Added Ray debugging to WebhookChannel for dispatch tracking - Added Ray debugging to Webhook Livewire component 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/Jobs/SendWebhookJob.php | 17 +++++++++++++- app/Livewire/Notifications/Webhook.php | 16 ++++++++++++++ app/Models/Team.php | 8 ++++++- app/Notifications/Channels/WebhookChannel.php | 12 ++++++++++ package-lock.json | 22 ++++++++----------- 5 files changed, 60 insertions(+), 15 deletions(-) diff --git a/app/Jobs/SendWebhookJob.php b/app/Jobs/SendWebhookJob.php index 865dd9493..607fda3fe 100644 --- a/app/Jobs/SendWebhookJob.php +++ b/app/Jobs/SendWebhookJob.php @@ -40,6 +40,21 @@ public function __construct( */ public function handle(): void { - Http::post($this->webhookUrl, $this->payload); + if (isDev()) { + ray('Sending webhook notification', [ + 'url' => $this->webhookUrl, + 'payload' => $this->payload, + ]); + } + + $response = Http::post($this->webhookUrl, $this->payload); + + if (isDev()) { + ray('Webhook response', [ + 'status' => $response->status(), + 'body' => $response->body(), + 'successful' => $response->successful(), + ]); + } } } diff --git a/app/Livewire/Notifications/Webhook.php b/app/Livewire/Notifications/Webhook.php index 491fc4b07..cf4e71105 100644 --- a/app/Livewire/Notifications/Webhook.php +++ b/app/Livewire/Notifications/Webhook.php @@ -159,6 +159,14 @@ public function saveModel() { $this->syncData(true); refreshSession(); + + if (isDev()) { + ray('Webhook settings saved', [ + 'webhook_enabled' => $this->settings->webhook_enabled, + 'webhook_url' => $this->settings->webhook_url, + ]); + } + $this->dispatch('success', 'Settings saved.'); } @@ -166,6 +174,14 @@ public function sendTestNotification() { try { $this->authorize('sendTest', $this->settings); + + if (isDev()) { + ray('Sending test webhook notification', [ + 'team_id' => $this->team->id, + 'webhook_url' => $this->settings->webhook_url, + ]); + } + $this->team->notify(new Test(channel: 'webhook')); $this->dispatch('success', 'Test notification sent.'); } catch (\Throwable $e) { diff --git a/app/Models/Team.php b/app/Models/Team.php index 0bdda9272..211ea8ade 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -7,6 +7,7 @@ use App\Notifications\Channels\SendsEmail; use App\Notifications\Channels\SendsPushover; use App\Notifications\Channels\SendsSlack; +use App\Notifications\Channels\SendsWebhook; use App\Traits\HasNotificationSettings; use App\Traits\HasSafeStringAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; @@ -36,7 +37,7 @@ ] )] -class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, SendsSlack +class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, SendsSlack, SendsWebhook { use HasFactory, HasNotificationSettings, HasSafeStringAttribute, Notifiable; @@ -166,6 +167,11 @@ public function routeNotificationForPushover() ]; } + public function routeNotificationForWebhook() + { + return data_get($this, 'webhook_url', null); + } + public function getRecipients(): array { $recipients = $this->members()->pluck('email')->toArray(); diff --git a/app/Notifications/Channels/WebhookChannel.php b/app/Notifications/Channels/WebhookChannel.php index 757e5e934..129e0628b 100644 --- a/app/Notifications/Channels/WebhookChannel.php +++ b/app/Notifications/Channels/WebhookChannel.php @@ -15,11 +15,23 @@ public function send(SendsWebhook $notifiable, Notification $notification): void $webhookSettings = $notifiable->webhookNotificationSettings; if (! $webhookSettings || ! $webhookSettings->isEnabled() || ! $webhookSettings->webhook_url) { + if (isDev()) { + ray('Webhook notification skipped - not enabled or no URL configured'); + } + return; } $payload = $notification->toWebhook(); + if (isDev()) { + ray('Dispatching webhook notification', [ + 'notification' => get_class($notification), + 'url' => $webhookSettings->webhook_url, + 'payload' => $payload, + ]); + } + SendWebhookJob::dispatch($payload, $webhookSettings->webhook_url); } } diff --git a/package-lock.json b/package-lock.json index 56e48288c..210747b4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -888,8 +888,7 @@ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@tailwindcss/forms": { "version": "0.5.10", @@ -1404,7 +1403,8 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/asynckit": { "version": "0.4.0", @@ -1567,7 +1567,6 @@ "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", @@ -1582,7 +1581,6 @@ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ms": "^2.1.3" }, @@ -1601,7 +1599,6 @@ "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" } @@ -2376,6 +2373,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -2452,6 +2450,7 @@ "integrity": "sha512-wp3HqIIUc1GRyu1XrP6m2dgyE9MoCsXVsWNlohj0rjSkLf+a0jLvEyVubdg58oMk7bhjBWnFClgp8jfAa6Ak4Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "tweetnacl": "^1.0.3" } @@ -2534,7 +2533,6 @@ "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", @@ -2551,7 +2549,6 @@ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ms": "^2.1.3" }, @@ -2570,7 +2567,6 @@ "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" @@ -2585,7 +2581,6 @@ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ms": "^2.1.3" }, @@ -2634,7 +2629,8 @@ "version": "4.1.10", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.10.tgz", "integrity": "sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { "version": "2.2.2", @@ -2700,6 +2696,7 @@ "integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -2799,6 +2796,7 @@ "integrity": "sha512-rjOV2ecxMd5SiAmof2xzh2WxntRcigkX/He4YFJ6WdRvVUrbt6DxC1Iujh10XLl8xCDRDtGKMeO3D+pRQ1PP9w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.16", "@vue/compiler-sfc": "3.5.16", @@ -2821,7 +2819,6 @@ "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, @@ -2843,7 +2840,6 @@ "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", "dev": true, - "peer": true, "engines": { "node": ">=0.4.0" } From 556d93ecb88fa29ed0530a68f16edfaf69f26b42 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 10 Oct 2025 18:08:37 +0200 Subject: [PATCH 09/14] refactor: remove SendsWebhook interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplified webhook channel implementation to match TelegramChannel pattern without typed interface. Changes: - Removed SendsWebhook interface file - Removed interface from Team model - Removed routeNotificationForWebhook() method - WebhookChannel now uses untyped $notifiable like TelegramChannel 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/Models/Team.php | 8 +------- app/Notifications/Channels/SendsWebhook.php | 8 -------- app/Notifications/Channels/WebhookChannel.php | 2 +- 3 files changed, 2 insertions(+), 16 deletions(-) delete mode 100644 app/Notifications/Channels/SendsWebhook.php diff --git a/app/Models/Team.php b/app/Models/Team.php index 211ea8ade..0bdda9272 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -7,7 +7,6 @@ use App\Notifications\Channels\SendsEmail; use App\Notifications\Channels\SendsPushover; use App\Notifications\Channels\SendsSlack; -use App\Notifications\Channels\SendsWebhook; use App\Traits\HasNotificationSettings; use App\Traits\HasSafeStringAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; @@ -37,7 +36,7 @@ ] )] -class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, SendsSlack, SendsWebhook +class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, SendsSlack { use HasFactory, HasNotificationSettings, HasSafeStringAttribute, Notifiable; @@ -167,11 +166,6 @@ public function routeNotificationForPushover() ]; } - public function routeNotificationForWebhook() - { - return data_get($this, 'webhook_url', null); - } - public function getRecipients(): array { $recipients = $this->members()->pluck('email')->toArray(); diff --git a/app/Notifications/Channels/SendsWebhook.php b/app/Notifications/Channels/SendsWebhook.php deleted file mode 100644 index 9301a4ff0..000000000 --- a/app/Notifications/Channels/SendsWebhook.php +++ /dev/null @@ -1,8 +0,0 @@ -webhookNotificationSettings; From 769d2eca35e4aa01bb5cfcf14c583007efdfd6e8 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 10 Oct 2025 18:19:10 +0200 Subject: [PATCH 10/14] feat: improve webhook URL field UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made webhook URL a password field for security and added POST badge indicator. Changes: - Changed webhook URL input type from "url" to "password" - Added POST badge to indicate HTTP method used for webhook delivery - Improved layout with flex container for badge and input 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../views/livewire/notifications/webhook.blade.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/resources/views/livewire/notifications/webhook.blade.php b/resources/views/livewire/notifications/webhook.blade.php index 2dea9a395..451769c54 100644 --- a/resources/views/livewire/notifications/webhook.blade.php +++ b/resources/views/livewire/notifications/webhook.blade.php @@ -23,9 +23,14 @@
- +
+ POST +
+ +
+

Notification Settings

From 4b5c641d1b72b14ef905a443dcc08e031b16cda1 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 10 Oct 2025 18:22:18 +0200 Subject: [PATCH 11/14] refactor: reposition POST badge as button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moved POST badge to align at the end of the input field and styled it as a button. Changes: - Changed flex container from items-start to items-end - Removed nested div wrapper around input - Styled POST badge as a button (btn btn-sm btn-warning) - Used proper button padding and sizing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../views/livewire/notifications/webhook.blade.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/resources/views/livewire/notifications/webhook.blade.php b/resources/views/livewire/notifications/webhook.blade.php index 451769c54..8f2dea1ac 100644 --- a/resources/views/livewire/notifications/webhook.blade.php +++ b/resources/views/livewire/notifications/webhook.blade.php @@ -23,13 +23,11 @@

-
- POST -
- -
+
+ + POST

Notification Settings

From 0603acfc4396762ada05445b72cbf5189a6d7e9d Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 10 Oct 2025 18:23:11 +0200 Subject: [PATCH 12/14] fix: move POST badge before input field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Repositioned POST badge to appear before the webhook URL input field. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- resources/views/livewire/notifications/webhook.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/livewire/notifications/webhook.blade.php b/resources/views/livewire/notifications/webhook.blade.php index 8f2dea1ac..296593927 100644 --- a/resources/views/livewire/notifications/webhook.blade.php +++ b/resources/views/livewire/notifications/webhook.blade.php @@ -24,10 +24,10 @@
+ POST - POST

Notification Settings

From bdffa4704d1d1cf5c0ffd395035f2eccf9d85074 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 10 Oct 2025 18:24:00 +0200 Subject: [PATCH 13/14] fix: use btn-primary for POST badge background MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed POST badge from btn-warning to btn-primary to match other button styling. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- resources/views/livewire/notifications/webhook.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/livewire/notifications/webhook.blade.php b/resources/views/livewire/notifications/webhook.blade.php index 296593927..29391a755 100644 --- a/resources/views/livewire/notifications/webhook.blade.php +++ b/resources/views/livewire/notifications/webhook.blade.php @@ -24,7 +24,7 @@
- POST + POST From 0303f529d310df25eece67ee6a5d01a2e8efcf9d Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 10 Oct 2025 18:41:46 +0200 Subject: [PATCH 14/14] feat: add UUIDs and URLs to webhook notifications MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add resource UUIDs (application_uuid, database_uuid, server_uuid, task_uuid) to all webhook notifications - Standardize URL field naming from various formats (resource_url, task_url, server_url) to consistent 'url' field - Include parent resource UUIDs for scheduled tasks (application_uuid or service_uuid) - Add direct URLs to Coolify resources for all notification types - Update UI to show "Webhook URL (POST)" label for clarity This enables webhook consumers to: - Uniquely identify resources using UUIDs used throughout Coolify UI - Directly link back to Coolify resource pages via the url field 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Application/DeploymentFailed.php | 26 ++++++++ .../Application/DeploymentSuccess.php | 26 ++++++++ .../Application/StatusChanged.php | 15 +++++ .../Container/ContainerRestarted.php | 18 ++++++ .../Container/ContainerStopped.php | 18 ++++++ app/Notifications/Database/BackupFailed.php | 17 ++++++ app/Notifications/Database/BackupSuccess.php | 16 +++++ .../Database/BackupSuccessWithS3Warning.php | 23 +++++++ .../ScheduledTask/TaskFailed.php | 24 ++++++++ .../ScheduledTask/TaskSuccess.php | 24 ++++++++ .../Server/DockerCleanupFailed.php | 15 +++++ .../Server/DockerCleanupSuccess.php | 15 +++++ app/Notifications/Server/HighDiskUsage.php | 14 +++++ app/Notifications/Server/Reachable.php | 14 +++++ app/Notifications/Server/ServerPatchCheck.php | 43 +++++++++++++ app/Notifications/Server/Unreachable.php | 14 +++++ .../livewire/notifications/webhook.blade.php | 61 ++++++++++--------- 17 files changed, 354 insertions(+), 29 deletions(-) diff --git a/app/Notifications/Application/DeploymentFailed.php b/app/Notifications/Application/DeploymentFailed.php index dec361e78..8fff7f03b 100644 --- a/app/Notifications/Application/DeploymentFailed.php +++ b/app/Notifications/Application/DeploymentFailed.php @@ -185,4 +185,30 @@ public function toSlack(): SlackMessage color: SlackMessage::errorColor() ); } + + public function toWebhook(): array + { + $data = [ + 'success' => false, + 'message' => 'Deployment failed', + 'event' => 'deployment_failed', + 'application_name' => $this->application_name, + 'application_uuid' => $this->application->uuid, + 'deployment_uuid' => $this->deployment_uuid, + 'deployment_url' => $this->deployment_url, + 'project' => data_get($this->application, 'environment.project.name'), + 'environment' => $this->environment_name, + ]; + + if ($this->preview) { + $data['pull_request_id'] = $this->preview->pull_request_id; + $data['preview_fqdn'] = $this->preview->fqdn; + } + + if ($this->fqdn) { + $data['fqdn'] = $this->fqdn; + } + + return $data; + } } diff --git a/app/Notifications/Application/DeploymentSuccess.php b/app/Notifications/Application/DeploymentSuccess.php index 9b59d9162..415df5831 100644 --- a/app/Notifications/Application/DeploymentSuccess.php +++ b/app/Notifications/Application/DeploymentSuccess.php @@ -205,4 +205,30 @@ public function toSlack(): SlackMessage color: SlackMessage::successColor() ); } + + public function toWebhook(): array + { + $data = [ + 'success' => true, + 'message' => 'New version successfully deployed', + 'event' => 'deployment_success', + 'application_name' => $this->application_name, + 'application_uuid' => $this->application->uuid, + 'deployment_uuid' => $this->deployment_uuid, + 'deployment_url' => $this->deployment_url, + 'project' => data_get($this->application, 'environment.project.name'), + 'environment' => $this->environment_name, + ]; + + if ($this->preview) { + $data['pull_request_id'] = $this->preview->pull_request_id; + $data['preview_fqdn'] = $this->preview->fqdn; + } + + if ($this->fqdn) { + $data['fqdn'] = $this->fqdn; + } + + return $data; + } } diff --git a/app/Notifications/Application/StatusChanged.php b/app/Notifications/Application/StatusChanged.php index fab5487ef..ef61b7e6a 100644 --- a/app/Notifications/Application/StatusChanged.php +++ b/app/Notifications/Application/StatusChanged.php @@ -113,4 +113,19 @@ public function toSlack(): SlackMessage color: SlackMessage::errorColor() ); } + + public function toWebhook(): array + { + return [ + 'success' => false, + 'message' => 'Application stopped', + 'event' => 'status_changed', + 'application_name' => $this->resource_name, + 'application_uuid' => $this->resource->uuid, + 'url' => $this->resource_url, + 'project' => data_get($this->resource, 'environment.project.name'), + 'environment' => $this->environment_name, + 'fqdn' => $this->fqdn, + ]; + } } diff --git a/app/Notifications/Container/ContainerRestarted.php b/app/Notifications/Container/ContainerRestarted.php index f6ae69481..2d7eb58b5 100644 --- a/app/Notifications/Container/ContainerRestarted.php +++ b/app/Notifications/Container/ContainerRestarted.php @@ -102,4 +102,22 @@ public function toSlack(): SlackMessage color: SlackMessage::warningColor() ); } + + public function toWebhook(): array + { + $data = [ + 'success' => true, + 'message' => 'Resource restarted automatically', + 'event' => 'container_restarted', + 'container_name' => $this->name, + 'server_name' => $this->server->name, + 'server_uuid' => $this->server->uuid, + ]; + + if ($this->url) { + $data['url'] = $this->url; + } + + return $data; + } } diff --git a/app/Notifications/Container/ContainerStopped.php b/app/Notifications/Container/ContainerStopped.php index fc9410a85..f518cd2fd 100644 --- a/app/Notifications/Container/ContainerStopped.php +++ b/app/Notifications/Container/ContainerStopped.php @@ -102,4 +102,22 @@ public function toSlack(): SlackMessage color: SlackMessage::errorColor() ); } + + public function toWebhook(): array + { + $data = [ + 'success' => false, + 'message' => 'Resource stopped unexpectedly', + 'event' => 'container_stopped', + 'container_name' => $this->name, + 'server_name' => $this->server->name, + 'server_uuid' => $this->server->uuid, + ]; + + if ($this->url) { + $data['url'] = $this->url; + } + + return $data; + } } diff --git a/app/Notifications/Database/BackupFailed.php b/app/Notifications/Database/BackupFailed.php index a19fb0431..c2b21b1d5 100644 --- a/app/Notifications/Database/BackupFailed.php +++ b/app/Notifications/Database/BackupFailed.php @@ -88,4 +88,21 @@ public function toSlack(): SlackMessage color: SlackMessage::errorColor() ); } + + public function toWebhook(): array + { + $url = base_url().'/project/'.data_get($this->database, 'environment.project.uuid').'/environment/'.data_get($this->database, 'environment.uuid').'/database/'.$this->database->uuid; + + return [ + 'success' => false, + 'message' => 'Database backup failed', + 'event' => 'backup_failed', + 'database_name' => $this->name, + 'database_uuid' => $this->database->uuid, + 'database_type' => $this->database_name, + 'frequency' => $this->frequency, + 'error_output' => $this->output, + 'url' => $url, + ]; + } } diff --git a/app/Notifications/Database/BackupSuccess.php b/app/Notifications/Database/BackupSuccess.php index 78bcfafe3..3d2d8ece3 100644 --- a/app/Notifications/Database/BackupSuccess.php +++ b/app/Notifications/Database/BackupSuccess.php @@ -85,4 +85,20 @@ public function toSlack(): SlackMessage color: SlackMessage::successColor() ); } + + public function toWebhook(): array + { + $url = base_url().'/project/'.data_get($this->database, 'environment.project.uuid').'/environment/'.data_get($this->database, 'environment.uuid').'/database/'.$this->database->uuid; + + return [ + 'success' => true, + 'message' => 'Database backup successful', + 'event' => 'backup_success', + 'database_name' => $this->name, + 'database_uuid' => $this->database->uuid, + 'database_type' => $this->database_name, + 'frequency' => $this->frequency, + 'url' => $url, + ]; + } } diff --git a/app/Notifications/Database/BackupSuccessWithS3Warning.php b/app/Notifications/Database/BackupSuccessWithS3Warning.php index 75ae2824c..ee24ef17d 100644 --- a/app/Notifications/Database/BackupSuccessWithS3Warning.php +++ b/app/Notifications/Database/BackupSuccessWithS3Warning.php @@ -113,4 +113,27 @@ public function toSlack(): SlackMessage color: SlackMessage::warningColor() ); } + + public function toWebhook(): array + { + $url = base_url().'/project/'.data_get($this->database, 'environment.project.uuid').'/environment/'.data_get($this->database, 'environment.uuid').'/database/'.$this->database->uuid; + + $data = [ + 'success' => true, + 'message' => 'Database backup succeeded locally, S3 upload failed', + 'event' => 'backup_success_with_s3_warning', + 'database_name' => $this->name, + 'database_uuid' => $this->database->uuid, + 'database_type' => $this->database_name, + 'frequency' => $this->frequency, + 's3_error' => $this->s3_error, + 'url' => $url, + ]; + + if ($this->s3_storage_url) { + $data['s3_storage_url'] = $this->s3_storage_url; + } + + return $data; + } } diff --git a/app/Notifications/ScheduledTask/TaskFailed.php b/app/Notifications/ScheduledTask/TaskFailed.php index eb4fc7e79..bd060112a 100644 --- a/app/Notifications/ScheduledTask/TaskFailed.php +++ b/app/Notifications/ScheduledTask/TaskFailed.php @@ -114,4 +114,28 @@ public function toSlack(): SlackMessage color: SlackMessage::errorColor() ); } + + public function toWebhook(): array + { + $data = [ + 'success' => false, + 'message' => 'Scheduled task failed', + 'event' => 'task_failed', + 'task_name' => $this->task->name, + 'task_uuid' => $this->task->uuid, + 'output' => $this->output, + ]; + + if ($this->task->application) { + $data['application_uuid'] = $this->task->application->uuid; + } elseif ($this->task->service) { + $data['service_uuid'] = $this->task->service->uuid; + } + + if ($this->url) { + $data['url'] = $this->url; + } + + return $data; + } } diff --git a/app/Notifications/ScheduledTask/TaskSuccess.php b/app/Notifications/ScheduledTask/TaskSuccess.php index c45784db2..58c959bd8 100644 --- a/app/Notifications/ScheduledTask/TaskSuccess.php +++ b/app/Notifications/ScheduledTask/TaskSuccess.php @@ -105,4 +105,28 @@ public function toSlack(): SlackMessage color: SlackMessage::successColor() ); } + + public function toWebhook(): array + { + $data = [ + 'success' => true, + 'message' => 'Scheduled task succeeded', + 'event' => 'task_success', + 'task_name' => $this->task->name, + 'task_uuid' => $this->task->uuid, + 'output' => $this->output, + ]; + + if ($this->task->application) { + $data['application_uuid'] = $this->task->application->uuid; + } elseif ($this->task->service) { + $data['service_uuid'] = $this->task->service->uuid; + } + + if ($this->url) { + $data['url'] = $this->url; + } + + return $data; + } } diff --git a/app/Notifications/Server/DockerCleanupFailed.php b/app/Notifications/Server/DockerCleanupFailed.php index 0291eed19..9cbdeb488 100644 --- a/app/Notifications/Server/DockerCleanupFailed.php +++ b/app/Notifications/Server/DockerCleanupFailed.php @@ -66,4 +66,19 @@ public function toSlack(): SlackMessage color: SlackMessage::errorColor() ); } + + public function toWebhook(): array + { + $url = base_url().'/server/'.$this->server->uuid; + + return [ + 'success' => false, + 'message' => 'Docker cleanup job failed', + 'event' => 'docker_cleanup_failed', + 'server_name' => $this->server->name, + 'server_uuid' => $this->server->uuid, + 'error_message' => $this->message, + 'url' => $url, + ]; + } } diff --git a/app/Notifications/Server/DockerCleanupSuccess.php b/app/Notifications/Server/DockerCleanupSuccess.php index 1a652d189..d28f25c6c 100644 --- a/app/Notifications/Server/DockerCleanupSuccess.php +++ b/app/Notifications/Server/DockerCleanupSuccess.php @@ -66,4 +66,19 @@ public function toSlack(): SlackMessage color: SlackMessage::successColor() ); } + + public function toWebhook(): array + { + $url = base_url().'/server/'.$this->server->uuid; + + return [ + 'success' => true, + 'message' => 'Docker cleanup job succeeded', + 'event' => 'docker_cleanup_success', + 'server_name' => $this->server->name, + 'server_uuid' => $this->server->uuid, + 'cleanup_message' => $this->message, + 'url' => $url, + ]; + } } diff --git a/app/Notifications/Server/HighDiskUsage.php b/app/Notifications/Server/HighDiskUsage.php index 983e6d81e..149d1bbc8 100644 --- a/app/Notifications/Server/HighDiskUsage.php +++ b/app/Notifications/Server/HighDiskUsage.php @@ -88,4 +88,18 @@ public function toSlack(): SlackMessage color: SlackMessage::errorColor() ); } + + public function toWebhook(): array + { + return [ + 'success' => false, + 'message' => 'High disk usage detected', + 'event' => 'high_disk_usage', + 'server_name' => $this->server->name, + 'server_uuid' => $this->server->uuid, + 'disk_usage' => $this->disk_usage, + 'threshold' => $this->server_disk_usage_notification_threshold, + 'url' => base_url().'/server/'.$this->server->uuid, + ]; + } } diff --git a/app/Notifications/Server/Reachable.php b/app/Notifications/Server/Reachable.php index e03aef6b7..e64b0af2a 100644 --- a/app/Notifications/Server/Reachable.php +++ b/app/Notifications/Server/Reachable.php @@ -74,4 +74,18 @@ public function toSlack(): SlackMessage color: SlackMessage::successColor() ); } + + public function toWebhook(): array + { + $url = base_url().'/server/'.$this->server->uuid; + + return [ + 'success' => true, + 'message' => 'Server revived', + 'event' => 'server_reachable', + 'server_name' => $this->server->name, + 'server_uuid' => $this->server->uuid, + 'url' => $url, + ]; + } } diff --git a/app/Notifications/Server/ServerPatchCheck.php b/app/Notifications/Server/ServerPatchCheck.php index 1686a6f37..4d3053569 100644 --- a/app/Notifications/Server/ServerPatchCheck.php +++ b/app/Notifications/Server/ServerPatchCheck.php @@ -345,4 +345,47 @@ public function toSlack(): SlackMessage color: SlackMessage::errorColor() ); } + + public function toWebhook(): array + { + // Handle error case + if (isset($this->patchData['error'])) { + return [ + 'success' => false, + 'message' => 'Failed to check patches', + 'event' => 'server_patch_check_error', + 'server_name' => $this->server->name, + 'server_uuid' => $this->server->uuid, + 'os_id' => $this->patchData['osId'] ?? 'unknown', + 'package_manager' => $this->patchData['package_manager'] ?? 'unknown', + 'error' => $this->patchData['error'], + 'url' => $this->serverUrl, + ]; + } + + $totalUpdates = $this->patchData['total_updates'] ?? 0; + $updates = $this->patchData['updates'] ?? []; + + // Check for critical packages + $criticalPackages = collect($updates)->filter(function ($update) { + return str_contains(strtolower($update['package']), 'docker') || + str_contains(strtolower($update['package']), 'kernel') || + str_contains(strtolower($update['package']), 'openssh') || + str_contains(strtolower($update['package']), 'ssl'); + }); + + return [ + 'success' => false, + 'message' => 'Server patches available', + 'event' => 'server_patch_check', + 'server_name' => $this->server->name, + 'server_uuid' => $this->server->uuid, + 'total_updates' => $totalUpdates, + 'os_id' => $this->patchData['osId'] ?? 'unknown', + 'package_manager' => $this->patchData['package_manager'] ?? 'unknown', + 'updates' => $updates, + 'critical_packages_count' => $criticalPackages->count(), + 'url' => $this->serverUrl, + ]; + } } diff --git a/app/Notifications/Server/Unreachable.php b/app/Notifications/Server/Unreachable.php index fe90cc610..99742f3b7 100644 --- a/app/Notifications/Server/Unreachable.php +++ b/app/Notifications/Server/Unreachable.php @@ -82,4 +82,18 @@ public function toSlack(): SlackMessage color: SlackMessage::errorColor() ); } + + public function toWebhook(): array + { + $url = base_url().'/server/'.$this->server->uuid; + + return [ + 'success' => false, + 'message' => 'Server unreachable', + 'event' => 'server_unreachable', + 'server_name' => $this->server->name, + 'server_uuid' => $this->server->uuid, + 'url' => $url, + ]; + } } diff --git a/resources/views/livewire/notifications/webhook.blade.php b/resources/views/livewire/notifications/webhook.blade.php index 29391a755..4646aaccd 100644 --- a/resources/views/livewire/notifications/webhook.blade.php +++ b/resources/views/livewire/notifications/webhook.blade.php @@ -10,24 +10,27 @@ Save @if ($webhookEnabled) - Send Test Notification @else - + Send Test Notification @endif
- +
- POST + + required id="webhookUrl" label="Webhook URL (POST)" />

Notification Settings

@@ -38,10 +41,10 @@

Deployments

- - + + @@ -50,36 +53,36 @@

Backups

- - + +

Scheduled Tasks

- - + +

Server

- - - - - - + + + + + +