mirror of
https://github.com/coollabsio/coolify.git
synced 2025-12-28 05:34:50 +00:00
Merge pull request #6837 from coollabsio/andrasbacsai/custom-webhooks
feat: add custom webhook notification support
This commit is contained in:
commit
635af44539
60
app/Jobs/SendWebhookJob.php
Normal file
60
app/Jobs/SendWebhookJob.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class SendWebhookJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 5;
|
||||
|
||||
public $backoff = 10;
|
||||
|
||||
/**
|
||||
* The maximum number of unhandled exceptions to allow before failing.
|
||||
*/
|
||||
public int $maxExceptions = 5;
|
||||
|
||||
public function __construct(
|
||||
public array $payload,
|
||||
public string $webhookUrl
|
||||
) {
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
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(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
196
app/Livewire/Notifications/Webhook.php
Normal file
196
app/Livewire/Notifications/Webhook.php
Normal file
@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Notifications;
|
||||
|
||||
use App\Models\Team;
|
||||
use App\Models\WebhookNotificationSettings;
|
||||
use App\Notifications\Test;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Attributes\Validate;
|
||||
use Livewire\Component;
|
||||
|
||||
class Webhook extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
public Team $team;
|
||||
|
||||
public WebhookNotificationSettings $settings;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $webhookEnabled = false;
|
||||
|
||||
#[Validate(['url', 'nullable'])]
|
||||
public ?string $webhookUrl = null;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $deploymentSuccessWebhookNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $deploymentFailureWebhookNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $statusChangeWebhookNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $backupSuccessWebhookNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $backupFailureWebhookNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $scheduledTaskSuccessWebhookNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $scheduledTaskFailureWebhookNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $dockerCleanupSuccessWebhookNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $dockerCleanupFailureWebhookNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverDiskUsageWebhookNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverReachableWebhookNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverUnreachableWebhookNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverPatchWebhookNotifications = false;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
$this->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();
|
||||
|
||||
if (isDev()) {
|
||||
ray('Webhook settings saved', [
|
||||
'webhook_enabled' => $this->settings->webhook_enabled,
|
||||
'webhook_url' => $this->settings->webhook_url,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->dispatch('success', 'Settings saved.');
|
||||
}
|
||||
|
||||
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) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.notifications.webhook');
|
||||
}
|
||||
}
|
||||
@ -54,6 +54,7 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, Sen
|
||||
$team->slackNotificationSettings()->create();
|
||||
$team->telegramNotificationSettings()->create();
|
||||
$team->pushoverNotificationSettings()->create();
|
||||
$team->webhookNotificationSettings()->create();
|
||||
});
|
||||
|
||||
static::saving(function ($team) {
|
||||
@ -312,4 +313,9 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, Sen
|
||||
{
|
||||
return $this->hasOne(PushoverNotificationSettings::class);
|
||||
}
|
||||
|
||||
public function webhookNotificationSettings()
|
||||
{
|
||||
return $this->hasOne(WebhookNotificationSettings::class);
|
||||
}
|
||||
}
|
||||
|
||||
64
app/Models/WebhookNotificationSettings.php
Normal file
64
app/Models/WebhookNotificationSettings.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
class WebhookNotificationSettings extends Model
|
||||
{
|
||||
use Notifiable;
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'team_id',
|
||||
|
||||
'webhook_enabled',
|
||||
'webhook_url',
|
||||
|
||||
'deployment_success_webhook_notifications',
|
||||
'deployment_failure_webhook_notifications',
|
||||
'status_change_webhook_notifications',
|
||||
'backup_success_webhook_notifications',
|
||||
'backup_failure_webhook_notifications',
|
||||
'scheduled_task_success_webhook_notifications',
|
||||
'scheduled_task_failure_webhook_notifications',
|
||||
'docker_cleanup_webhook_notifications',
|
||||
'server_disk_usage_webhook_notifications',
|
||||
'server_reachable_webhook_notifications',
|
||||
'server_unreachable_webhook_notifications',
|
||||
'server_patch_webhook_notifications',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'webhook_enabled' => '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;
|
||||
}
|
||||
}
|
||||
@ -185,4 +185,30 @@ class DeploymentFailed extends CustomEmailNotification
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,4 +205,30 @@ class DeploymentSuccess extends CustomEmailNotification
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,4 +113,19 @@ class StatusChanged extends CustomEmailNotification
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
37
app/Notifications/Channels/WebhookChannel.php
Normal file
37
app/Notifications/Channels/WebhookChannel.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications\Channels;
|
||||
|
||||
use App\Jobs\SendWebhookJob;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class WebhookChannel
|
||||
{
|
||||
/**
|
||||
* Send the given notification.
|
||||
*/
|
||||
public function send($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);
|
||||
}
|
||||
}
|
||||
@ -102,4 +102,22 @@ class ContainerRestarted extends CustomEmailNotification
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,4 +102,22 @@ class ContainerStopped extends CustomEmailNotification
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,4 +88,21 @@ class BackupFailed extends CustomEmailNotification
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,4 +85,20 @@ class BackupSuccess extends CustomEmailNotification
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,4 +113,27 @@ class BackupSuccessWithS3Warning extends CustomEmailNotification
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,4 +114,28 @@ class TaskFailed extends CustomEmailNotification
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,4 +105,28 @@ class TaskSuccess extends CustomEmailNotification
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,4 +66,19 @@ class DockerCleanupFailed extends CustomEmailNotification
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,4 +66,19 @@ class DockerCleanupSuccess extends CustomEmailNotification
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,4 +88,18 @@ class HighDiskUsage extends CustomEmailNotification
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,4 +74,18 @@ class Reachable extends CustomEmailNotification
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -345,4 +345,47 @@ class ServerPatchCheck extends CustomEmailNotification
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,4 +82,18 @@ class Unreachable extends CustomEmailNotification
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ use App\Notifications\Channels\EmailChannel;
|
||||
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,6 +37,7 @@ class Test extends Notification implements ShouldQueue
|
||||
'telegram' => [TelegramChannel::class],
|
||||
'slack' => [SlackChannel::class],
|
||||
'pushover' => [PushoverChannel::class],
|
||||
'webhook' => [WebhookChannel::class],
|
||||
default => [],
|
||||
};
|
||||
} else {
|
||||
@ -110,4 +112,14 @@ class Test extends Notification implements ShouldQueue
|
||||
description: 'This is a test Slack notification from Coolify.'
|
||||
);
|
||||
}
|
||||
|
||||
public function toWebhook(): array
|
||||
{
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'This is a test webhook notification from Coolify.',
|
||||
'event' => 'test',
|
||||
'url' => base_url(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -7,6 +7,7 @@ use App\Notifications\Channels\EmailChannel;
|
||||
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
|
||||
@ -31,6 +32,7 @@ trait HasNotificationSettings
|
||||
'telegram' => $this->telegramNotificationSettings,
|
||||
'slack' => $this->slackNotificationSettings,
|
||||
'pushover' => $this->pushoverNotificationSettings,
|
||||
'webhook' => $this->webhookNotificationSettings,
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
@ -78,6 +80,7 @@ trait HasNotificationSettings
|
||||
'telegram' => TelegramChannel::class,
|
||||
'slack' => SlackChannel::class,
|
||||
'pushover' => PushoverChannel::class,
|
||||
'webhook' => WebhookChannel::class,
|
||||
];
|
||||
|
||||
if ($event === 'general') {
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('webhook_notification_settings', function (Blueprint $table) {
|
||||
$table->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->boolean('server_patch_webhook_notifications')->default(false);
|
||||
|
||||
$table->unique(['team_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('webhook_notification_settings');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$teams = DB::table('teams')->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,
|
||||
'server_patch_webhook_notifications' => false,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
};
|
||||
@ -23,6 +23,10 @@
|
||||
href="{{ route('notifications.pushover') }}">
|
||||
<button>Pushover</button>
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('notifications.webhook') ? 'dark:text-white' : '' }}"
|
||||
href="{{ route('notifications.webhook') }}">
|
||||
<button>Webhook</button>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
89
resources/views/livewire/notifications/webhook.blade.php
Normal file
89
resources/views/livewire/notifications/webhook.blade.php
Normal file
@ -0,0 +1,89 @@
|
||||
<div>
|
||||
<x-slot:title>
|
||||
Notifications | Coolify
|
||||
</x-slot>
|
||||
<x-notification.navbar />
|
||||
<form wire:submit='submit' class="flex flex-col gap-4 pb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Webhook</h2>
|
||||
<x-forms.button canGate="update" :canResource="$settings" type="submit">
|
||||
Save
|
||||
</x-forms.button>
|
||||
@if ($webhookEnabled)
|
||||
<x-forms.button canGate="sendTest" :canResource="$settings"
|
||||
class="normal-case dark:text-white btn btn-xs no-animation btn-primary"
|
||||
wire:click="sendTestNotification">
|
||||
Send Test Notification
|
||||
</x-forms.button>
|
||||
@else
|
||||
<x-forms.button canGate="sendTest" :canResource="$settings" disabled
|
||||
class="normal-case dark:text-white btn btn-xs no-animation btn-primary">
|
||||
Send Test Notification
|
||||
</x-forms.button>
|
||||
@endif
|
||||
</div>
|
||||
<div class="w-48">
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="instantSaveWebhookEnabled"
|
||||
id="webhookEnabled" label="Enabled" />
|
||||
</div>
|
||||
<div class="flex items-end gap-2">
|
||||
|
||||
<x-forms.input canGate="update" :canResource="$settings" type="password"
|
||||
helper="Enter a valid HTTP or HTTPS URL. Coolify will send POST requests to this endpoint when events occur."
|
||||
required id="webhookUrl" label="Webhook URL (POST)" />
|
||||
</div>
|
||||
</form>
|
||||
<h2 class="mt-4">Notification Settings</h2>
|
||||
<p class="mb-4">
|
||||
Select events for which you would like to receive webhook notifications.
|
||||
</p>
|
||||
<div class="flex flex-col gap-4 max-w-2xl">
|
||||
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
|
||||
<h3 class="font-medium mb-3">Deployments</h3>
|
||||
<div class="flex flex-col gap-1.5 pl-1">
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel"
|
||||
id="deploymentSuccessWebhookNotifications" label="Deployment Success" />
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel"
|
||||
id="deploymentFailureWebhookNotifications" label="Deployment Failure" />
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel"
|
||||
helper="Send a notification when a container status changes. It will notify for Stopped and Restarted events of a container."
|
||||
id="statusChangeWebhookNotifications" label="Container Status Changes" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
|
||||
<h3 class="font-medium mb-3">Backups</h3>
|
||||
<div class="flex flex-col gap-1.5 pl-1">
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel"
|
||||
id="backupSuccessWebhookNotifications" label="Backup Success" />
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel"
|
||||
id="backupFailureWebhookNotifications" label="Backup Failure" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
|
||||
<h3 class="font-medium mb-3">Scheduled Tasks</h3>
|
||||
<div class="flex flex-col gap-1.5 pl-1">
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel"
|
||||
id="scheduledTaskSuccessWebhookNotifications" label="Scheduled Task Success" />
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel"
|
||||
id="scheduledTaskFailureWebhookNotifications" label="Scheduled Task Failure" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
|
||||
<h3 class="font-medium mb-3">Server</h3>
|
||||
<div class="flex flex-col gap-1.5 pl-1">
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel"
|
||||
id="dockerCleanupSuccessWebhookNotifications" label="Docker Cleanup Success" />
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel"
|
||||
id="dockerCleanupFailureWebhookNotifications" label="Docker Cleanup Failure" />
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel"
|
||||
id="serverDiskUsageWebhookNotifications" label="Server Disk Usage" />
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel"
|
||||
id="serverReachableWebhookNotifications" label="Server Reachable" />
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel"
|
||||
id="serverUnreachableWebhookNotifications" label="Server Unreachable" />
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel"
|
||||
id="serverPatchWebhookNotifications" label="Server Patching" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -14,6 +14,7 @@ use App\Livewire\Notifications\Email as NotificationEmail;
|
||||
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;
|
||||
@ -128,6 +129,7 @@ Route::middleware(['auth', 'verified'])->group(function () {
|
||||
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 () {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user