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 () {