diff --git a/app/Jobs/SendMessageToNtfyJob.php b/app/Jobs/SendMessageToNtfyJob.php new file mode 100644 index 000000000..e8c9b7f64 --- /dev/null +++ b/app/Jobs/SendMessageToNtfyJob.php @@ -0,0 +1,84 @@ +username && $this->password) { + $headers['Authorization'] = 'Basic '.base64_encode($this->username.':'.$this->password); + } + + if ($this->buttons) { + $headers['Actions'] = $this->buttons; + } + + $headers['Content-Type'] = 'text/markdown'; + $headers['Tags'] = 'coolify'; + + if ($this->emoji) { + $headers['Tags'] .= ','.$this->emoji; + } + + if ($this->title) { + $headers['Title'] = $this->title; + } + + $payload = $this->text; + Http::withHeaders($headers)->post($this->url.'/'.$this->topic, $payload); + } +} diff --git a/app/Livewire/Notifications/Ntfy.php b/app/Livewire/Notifications/Ntfy.php new file mode 100644 index 000000000..b32f3e0d6 --- /dev/null +++ b/app/Livewire/Notifications/Ntfy.php @@ -0,0 +1,72 @@ + 'nullable|boolean', + 'team.ntfy_url' => 'required|url', + 'team.ntfy_topic' => 'required|string', + 'team.ntfy_username' => 'nullable|string', + 'team.ntfy_password' => 'nullable|string', + 'team.ntfy_notifications_test' => 'nullable|boolean', + 'team.ntfy_notifications_deployments' => 'nullable|boolean', + 'team.ntfy_notifications_status_changes' => 'nullable|boolean', + 'team.ntfy_notifications_database_backups' => 'nullable|boolean', + 'team.ntfy_notifications_scheduled_tasks' => 'nullable|boolean', + ]; + + /* shelll */ + protected $validationAttributes = [ + 'team.ntfy_url' => 'Host', + 'team.ntfy_topic' => 'Topic', + ]; + + public function mount() + { + $this->team = auth()->user()->currentTeam(); + } + + public function instantSave() + { + try { + $this->submit(); + } catch (\Throwable $e) { + ray($e->getMessage()); + $this->team->ntfy_enabled = false; + $this->validate(); + } + } + + public function submit() + { + $this->resetErrorBag(); + $this->validate(); + $this->saveModel(); + } + + public function saveModel() + { + $this->team->save(); + refreshSession(); + $this->dispatch('success', 'Settings saved.'); + } + + public function sendTestNotification() + { + $this->team?->notify(new Test()); + $this->dispatch('success', 'Test notification sent.'); + } + + public function render() + { + return view('livewire.notifications.ntfy'); + } +} diff --git a/app/Models/Team.php b/app/Models/Team.php index fe5995a1b..55b5c301b 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -3,12 +3,13 @@ namespace App\Models; use App\Notifications\Channels\SendsDiscord; +use App\Notifications\Channels\SendsNtfy; use App\Notifications\Channels\SendsEmail; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; -class Team extends Model implements SendsDiscord, SendsEmail +class Team extends Model implements SendsDiscord, SendsEmail, SendsNtfy { use Notifiable; @@ -31,27 +32,27 @@ class Team extends Model implements SendsDiscord, SendsEmail static::deleting(function ($team) { $keys = $team->privateKeys; foreach ($keys as $key) { - ray('Deleting key: '.$key->name); + ray('Deleting key: ' . $key->name); $key->delete(); } $sources = $team->sources(); foreach ($sources as $source) { - ray('Deleting source: '.$source->name); + ray('Deleting source: ' . $source->name); $source->delete(); } $tags = Tag::whereTeamId($team->id)->get(); foreach ($tags as $tag) { - ray('Deleting tag: '.$tag->name); + ray('Deleting tag: ' . $tag->name); $tag->delete(); } $shared_variables = $team->environment_variables(); foreach ($shared_variables as $shared_variable) { - ray('Deleting team shared variable: '.$shared_variable->name); + ray('Deleting team shared variable: ' . $shared_variable->name); $shared_variable->delete(); } $s3s = $team->s3s; foreach ($s3s as $s3) { - ray('Deleting s3: '.$s3->name); + ray('Deleting s3: ' . $s3->name); $s3->delete(); } }); @@ -70,6 +71,16 @@ class Team extends Model implements SendsDiscord, SendsEmail ]; } + public function routeNotificationForNtfy() + { + return [ + 'url' => data_get($this, 'ntfy_url', null), + 'username' => data_get($this, 'ntfy_username', null), + 'password' => data_get($this, 'ntfy_password', null), + 'topic' => data_get($this, 'ntfy_topic', null), + ]; + } + public function getRecepients($notification) { $recipients = data_get($notification, 'emails', null); @@ -225,7 +236,7 @@ class Team extends Model implements SendsDiscord, SendsEmail if (isCloud()) { return true; } - if ($this->smtp_enabled || $this->resend_enabled || $this->discord_enabled || $this->telegram_enabled || $this->use_instance_email_settings) { + if ($this->smtp_enabled || $this->resend_enabled || $this->discord_enabled || $this->telegram_enabled || $this->ntfy_enabled || $this->use_instance_email_settings) { return true; } diff --git a/app/Notifications/Application/DeploymentFailed.php b/app/Notifications/Application/DeploymentFailed.php index a95629087..6f14622b1 100644 --- a/app/Notifications/Application/DeploymentFailed.php +++ b/app/Notifications/Application/DeploymentFailed.php @@ -85,6 +85,16 @@ class DeploymentFailed extends Notification implements ShouldQueue return $message; } + public function toNtfy(): array + { + return [ + 'title' => 'Coolify: Deployment Failed', + 'message' => 'Deployment failed of '.$this->application_name.' ('.$this->fqdn.'): ', + 'buttons' => 'view, View Deployment Logs, '.$this->deployment_url.';', + 'emoji' => 'warning', + ]; + } + public function toTelegram(): array { if ($this->preview) { diff --git a/app/Notifications/Application/DeploymentSuccess.php b/app/Notifications/Application/DeploymentSuccess.php index c06d070d8..5dfccd8c9 100644 --- a/app/Notifications/Application/DeploymentSuccess.php +++ b/app/Notifications/Application/DeploymentSuccess.php @@ -101,6 +101,23 @@ class DeploymentSuccess extends Notification implements ShouldQueue return $message; } + + public function toNtfy(): array + { + if ($this->preview) { + $message = 'Coolify: New PR'.$this->preview->pull_request_id.' version successfully deployed of '.$this->application_name.''; + } else { + $message = 'Coolify: New version successfully deployed of '.$this->application_name.''; + } + + return [ + 'title' => 'Coolify: New version deployed', + 'message' => $message, + 'buttons' => 'view, Open Application, '.$this->fqdn.';view, Deployment logs, '.$this->deployment_url.';', + 'emoji' => 'checkmark', + ]; + } + public function toTelegram(): array { if ($this->preview) { diff --git a/app/Notifications/Application/StatusChanged.php b/app/Notifications/Application/StatusChanged.php index 72442fcb3..8dc8b72bc 100644 --- a/app/Notifications/Application/StatusChanged.php +++ b/app/Notifications/Application/StatusChanged.php @@ -33,7 +33,7 @@ class StatusChanged extends Notification implements ShouldQueue if (str($this->fqdn)->explode(',')->count() > 1) { $this->fqdn = str($this->fqdn)->explode(',')->first(); } - $this->resource_url = base_url()."/project/{$this->project_uuid}/".urlencode($this->environment_name)."/application/{$this->resource->uuid}"; + $this->resource_url = base_url() . "/project/{$this->project_uuid}/" . urlencode($this->environment_name) . "/application/{$this->resource->uuid}"; } public function via(object $notifiable): array @@ -57,17 +57,29 @@ class StatusChanged extends Notification implements ShouldQueue public function toDiscord(): string { - $message = 'Coolify: '.$this->resource_name.' has been stopped. + $message = 'Coolify: ' . $this->resource_name . ' has been stopped. '; - $message .= '[Open Application in Coolify]('.$this->resource_url.')'; + $message .= '[Open Application in Coolify](' . $this->resource_url . ')'; return $message; } + public function toNtfy(): array + { + $message = 'Coolify: ' . $this->resource_name . ' has been stopped.'; + + return [ + 'title' => 'Coolify: Application Status Changed', + 'message' => $message, + 'buttons' => 'view, Go to your dashboard, ' . base_url() . ';', + 'emoji' => 'stop_sign', + ]; + } + public function toTelegram(): array { - $message = 'Coolify: '.$this->resource_name.' has been stopped.'; + $message = 'Coolify: ' . $this->resource_name . ' has been stopped.'; return [ 'message' => $message, diff --git a/app/Notifications/Channels/NtfyChannel.php b/app/Notifications/Channels/NtfyChannel.php new file mode 100644 index 000000000..68b788ea3 --- /dev/null +++ b/app/Notifications/Channels/NtfyChannel.php @@ -0,0 +1,42 @@ +toNtfy($notifiable); + $message = $content['message'] ?? null; + $buttons = $content['buttons'] ?? null; + $emoji = $content['emoji'] ?? null; + $title = $content['title'] ?? null; + + $url_info = $notifiable->routeNotificationForNtfy(); + $topic = $url_info['topic']; + $url = $url_info['url']; + $username = $url_info['username']; + $password = $url_info['password']; + if (! $url_info) { + return; + } + + dispatch(new SendMessageToNtfyJob( + $message, + $buttons, + $emoji, + $title, + $url, + $username, + $password, + $topic + )); + } +} diff --git a/app/Notifications/Channels/SendsNtfy.php b/app/Notifications/Channels/SendsNtfy.php new file mode 100644 index 000000000..4abc23b20 --- /dev/null +++ b/app/Notifications/Channels/SendsNtfy.php @@ -0,0 +1,8 @@ + "Coolify: A resource ({$this->name}) has been restarted", + 'message' => "Coolify: A resource ({$this->name}) has been restarted automatically on {$this->server->name}", + 'buttons' => 'view, Check Proxy in Coolify, '.$this->url.';', + 'emoji' => 'arrows_counterclockwise', + ]; + } + public function toTelegram(): array { $message = "Coolify: A resource ({$this->name}) has been restarted automatically on {$this->server->name}"; diff --git a/app/Notifications/Container/ContainerStopped.php b/app/Notifications/Container/ContainerStopped.php index 75b4872cb..333e9e339 100644 --- a/app/Notifications/Container/ContainerStopped.php +++ b/app/Notifications/Container/ContainerStopped.php @@ -41,6 +41,16 @@ class ContainerStopped extends Notification implements ShouldQueue return $message; } + public function toNtfy(): array + { + return [ + 'title' => "Coolify: A resource ($this->name) has been stopped", + 'message' => "Coolify: A resource ($this->name) has been stopped unexpectedly on {$this->server->name}", + 'buttons' => 'view, Check Proxy in Coolify, '.$this->url.';', + 'emoji' => 'stop_sign', + ]; + } + public function toTelegram(): array { $message = "Coolify: A resource ($this->name) has been stopped unexpectedly on {$this->server->name}"; diff --git a/app/Notifications/Database/BackupFailed.php b/app/Notifications/Database/BackupFailed.php index c6403ab71..3afd9d41d 100644 --- a/app/Notifications/Database/BackupFailed.php +++ b/app/Notifications/Database/BackupFailed.php @@ -50,6 +50,14 @@ class BackupFailed extends Notification implements ShouldQueue return "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was FAILED.\n\nReason:\n{$this->output}"; } + public function toNtfy() + { + return [ + 'title' => 'Coolify: Database backup has FAILED', + 'message' => "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was FAILED.\n\nReason:\n{$this->output}", + ]; + } + public function toTelegram(): array { $message = "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was FAILED.\n\nReason:\n{$this->output}"; diff --git a/app/Notifications/Database/BackupSuccess.php b/app/Notifications/Database/BackupSuccess.php index f3a3d5943..c7d3c8829 100644 --- a/app/Notifications/Database/BackupSuccess.php +++ b/app/Notifications/Database/BackupSuccess.php @@ -49,6 +49,14 @@ class BackupSuccess extends Notification implements ShouldQueue return "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was successful."; } + public function toNtfy(): array + { + return [ + 'title' => 'Coolify: Database backup was successful', + 'message' => "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was successful.", + ]; + } + public function toTelegram(): array { $message = "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was successful."; diff --git a/app/Notifications/Database/DailyBackup.php b/app/Notifications/Database/DailyBackup.php index 90abee8a6..6a5b20a6c 100644 --- a/app/Notifications/Database/DailyBackup.php +++ b/app/Notifications/Database/DailyBackup.php @@ -39,6 +39,14 @@ class DailyBackup extends Notification implements ShouldQueue return 'Coolify: Daily backup statuses'; } + public function toNtfy(): array + { + return [ + 'title' => 'Coolify: Daily backup statuses', + 'message' => 'Coolify: Daily backup statuses', + ]; + } + public function toTelegram(): array { $message = 'Coolify: Daily backup statuses'; diff --git a/app/Notifications/Internal/GeneralNotification.php b/app/Notifications/Internal/GeneralNotification.php index 1d4d648c8..7b1d40314 100644 --- a/app/Notifications/Internal/GeneralNotification.php +++ b/app/Notifications/Internal/GeneralNotification.php @@ -4,6 +4,7 @@ namespace App\Notifications\Internal; use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\TelegramChannel; +use App\Notifications\Channels\NtfyChannel; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Notification; @@ -14,13 +15,16 @@ class GeneralNotification extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public string $message) {} + public function __construct(public string $message) + { + } public function via(object $notifiable): array { $channels = []; $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); + $isNtfyEnabled = data_get($notifiable, 'ntfy_enabled'); if ($isDiscordEnabled) { $channels[] = DiscordChannel::class; @@ -28,6 +32,9 @@ class GeneralNotification extends Notification implements ShouldQueue if ($isTelegramEnabled) { $channels[] = TelegramChannel::class; } + if ($isNtfyEnabled) { + $channels[] = NtfyChannel::class; + } return $channels; } @@ -37,6 +44,14 @@ class GeneralNotification extends Notification implements ShouldQueue return $this->message; } + public function toNtfy(): array + { + return [ + 'message' => $this->message, + 'buttons' => 'view, Go to your dashboard, '.base_url().';', + ]; + } + public function toTelegram(): array { return [ diff --git a/app/Notifications/ScheduledTask/TaskFailed.php b/app/Notifications/ScheduledTask/TaskFailed.php index 3a41fb687..1b25939ab 100644 --- a/app/Notifications/ScheduledTask/TaskFailed.php +++ b/app/Notifications/ScheduledTask/TaskFailed.php @@ -51,6 +51,16 @@ class TaskFailed extends Notification implements ShouldQueue return "Coolify: Scheduled task ({$this->task->name}, [link]({$this->url})) failed with output: {$this->output}"; } + public function toNtfy(): array + { + return [ + 'title' => 'Coolify: Scheduled task failed', + 'message' => "Coolify: Scheduled task ({$this->task->name}) failed with output: {$this->output}", + 'buttons' => 'view, Open task in Coolify, '.$this->url.';', + 'emoji' => 'warning', + ]; + } + public function toTelegram(): array { $message = "Coolify: Scheduled task ({$this->task->name}) failed with output: {$this->output}"; diff --git a/app/Notifications/Server/DockerCleanup.php b/app/Notifications/Server/DockerCleanup.php index f8195ec1d..bc8ac3fa1 100644 --- a/app/Notifications/Server/DockerCleanup.php +++ b/app/Notifications/Server/DockerCleanup.php @@ -5,6 +5,7 @@ namespace App\Notifications\Server; use App\Models\Server; use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\TelegramChannel; +use App\Notifications\Channels\NtfyChannel; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Notification; @@ -23,6 +24,7 @@ class DockerCleanup extends Notification implements ShouldQueue // $isEmailEnabled = isEmailEnabled($notifiable); $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); + $isNtfyEnabled = data_get($notifiable, 'ntfy_enabled'); if ($isDiscordEnabled) { $channels[] = DiscordChannel::class; @@ -34,6 +36,10 @@ class DockerCleanup extends Notification implements ShouldQueue $channels[] = TelegramChannel::class; } + if ($isNtfyEnabled) { + $channels[] = NtfyChannel::class; + } + return $channels; } @@ -49,6 +55,15 @@ class DockerCleanup extends Notification implements ShouldQueue // return $mail; // } + public function toNtfy() + { + return [ + 'title' => "Coolify: Server '{$this->server->name}' cleanup job done!", + 'message' => "Coolify: Server '{$this->server->name}' cleanup job done!\n\n{$this->message}", + 'emoji' => 'wastebasket', + ]; + } + public function toDiscord(): string { $message = "Coolify: Server '{$this->server->name}' cleanup job done!\n\n{$this->message}"; diff --git a/app/Notifications/Server/ForceDisabled.php b/app/Notifications/Server/ForceDisabled.php index 9a76558e2..e04df1be2 100644 --- a/app/Notifications/Server/ForceDisabled.php +++ b/app/Notifications/Server/ForceDisabled.php @@ -6,6 +6,7 @@ use App\Models\Server; use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\EmailChannel; use App\Notifications\Channels\TelegramChannel; +use App\Notifications\Channels\NtfyChannel; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; @@ -17,7 +18,9 @@ class ForceDisabled extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public Server $server) {} + public function __construct(public Server $server) + { + } public function via(object $notifiable): array { @@ -25,6 +28,7 @@ class ForceDisabled extends Notification implements ShouldQueue $isEmailEnabled = isEmailEnabled($notifiable); $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); + $isNtfyEnabled = data_get($notifiable, 'ntfy_enabled'); if ($isDiscordEnabled) { $channels[] = DiscordChannel::class; @@ -35,6 +39,9 @@ class ForceDisabled extends Notification implements ShouldQueue if ($isTelegramEnabled) { $channels[] = TelegramChannel::class; } + if ($isNtfyEnabled) { + $channels[] = NtfyChannel::class; + } return $channels; } @@ -50,6 +57,16 @@ class ForceDisabled extends Notification implements ShouldQueue return $mail; } + public function toNtfy(): array + { + return [ + 'title' => "Coolify: Server ({$this->server->name}) disabled because it is not paid!", + 'message' => "All automations and integrations are stopped.\nPlease update your subscription to enable the server again [here](https://app.coolify.io/subsciprtions", + 'buttons' => 'view, Update subscription, '.base_url().'/subscriptions;', + 'emoji' => 'stop_sign', + ]; + } + public function toDiscord(): string { $message = "Coolify: Server ({$this->server->name}) disabled because it is not paid!\n All automations and integrations are stopped.\nPlease update your subscription to enable the server again [here](https://app.coolify.io/subsciprtions)."; diff --git a/app/Notifications/Server/ForceEnabled.php b/app/Notifications/Server/ForceEnabled.php index a43e30376..8682eb9f6 100644 --- a/app/Notifications/Server/ForceEnabled.php +++ b/app/Notifications/Server/ForceEnabled.php @@ -5,6 +5,7 @@ namespace App\Notifications\Server; use App\Models\Server; use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\EmailChannel; +use App\Notifications\Channels\NtfyChannel; use App\Notifications\Channels\TelegramChannel; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -25,6 +26,7 @@ class ForceEnabled extends Notification implements ShouldQueue $isEmailEnabled = isEmailEnabled($notifiable); $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); + $isNtfyEnabled = data_get($notifiable, 'ntfy_enabled'); if ($isDiscordEnabled) { $channels[] = DiscordChannel::class; @@ -36,6 +38,10 @@ class ForceEnabled extends Notification implements ShouldQueue $channels[] = TelegramChannel::class; } + if ($isNtfyEnabled) { + $channels[] = NtfyChannel::class; + } + return $channels; } @@ -57,6 +63,14 @@ class ForceEnabled extends Notification implements ShouldQueue return $message; } + public function toNtfy(): array + { + return [ + 'title' => "Coolify: Server ({$this->server->name}) enabled again!", + 'message' => "Coolify: Server ({$this->server->name}) enabled again!", + ]; + } + public function toTelegram(): array { return [ diff --git a/app/Notifications/Server/HighDiskUsage.php b/app/Notifications/Server/HighDiskUsage.php index a6e248170..7bd417067 100644 --- a/app/Notifications/Server/HighDiskUsage.php +++ b/app/Notifications/Server/HighDiskUsage.php @@ -5,6 +5,7 @@ namespace App\Notifications\Server; use App\Models\Server; use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\EmailChannel; +use App\Notifications\Channels\NtfyChannel; use App\Notifications\Channels\TelegramChannel; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -25,6 +26,7 @@ class HighDiskUsage extends Notification implements ShouldQueue $isEmailEnabled = isEmailEnabled($notifiable); $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); + $isNtfyEnabled = data_get($notifiable, 'ntfy_enabled'); if ($isDiscordEnabled) { $channels[] = DiscordChannel::class; @@ -35,6 +37,9 @@ class HighDiskUsage extends Notification implements ShouldQueue if ($isTelegramEnabled) { $channels[] = TelegramChannel::class; } + if ($isNtfyEnabled) { + $channels[] = NtfyChannel::class; + } return $channels; } @@ -52,6 +57,15 @@ class HighDiskUsage extends Notification implements ShouldQueue return $mail; } + public function toNtfy(): array + { + return [ + 'title' => "Coolify: Server '{$this->server->name}' high disk usage detected!", + 'message' => "Disk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.", + 'buttons' => 'view, Go to your dashboard, '.base_url().';', + ]; + } + public function toDiscord(): string { $message = "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup."; diff --git a/app/Notifications/Server/Revived.php b/app/Notifications/Server/Revived.php index 8eaadf359..b1f54fad8 100644 --- a/app/Notifications/Server/Revived.php +++ b/app/Notifications/Server/Revived.php @@ -8,6 +8,7 @@ use App\Models\Server; use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\EmailChannel; use App\Notifications\Channels\TelegramChannel; +use App\Notifications\Channels\NtfyChannel; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; @@ -33,6 +34,7 @@ class Revived extends Notification implements ShouldQueue $channels = []; $isEmailEnabled = isEmailEnabled($notifiable); $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); + $isNtfyEnabled = data_get($notifiable, 'ntfy_enabled'); $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); if ($isDiscordEnabled) { @@ -44,6 +46,9 @@ class Revived extends Notification implements ShouldQueue if ($isTelegramEnabled) { $channels[] = TelegramChannel::class; } + if ($isNtfyEnabled) { + $channels[] = NtfyChannel::class; + } return $channels; } @@ -66,6 +71,16 @@ class Revived extends Notification implements ShouldQueue return $message; } + + public function toNtfy(): array + { + return [ + 'title' => "Coolify: Server '{$this->server->name}' revived.", + 'message' => "All automations & integrations are turned on again!", + 'emoji' => 'white_check_mark', + ]; + } + public function toTelegram(): array { return [ diff --git a/app/Notifications/Server/Unreachable.php b/app/Notifications/Server/Unreachable.php index ebbd6af77..443a5d46f 100644 --- a/app/Notifications/Server/Unreachable.php +++ b/app/Notifications/Server/Unreachable.php @@ -5,6 +5,7 @@ namespace App\Notifications\Server; use App\Models\Server; use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\EmailChannel; +use App\Notifications\Channels\NtfyChannel; use App\Notifications\Channels\TelegramChannel; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -25,6 +26,7 @@ class Unreachable extends Notification implements ShouldQueue $isEmailEnabled = isEmailEnabled($notifiable); $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); + $isNtfyEnabled = data_get($notifiable, 'ntfy_enabled'); if ($isDiscordEnabled) { $channels[] = DiscordChannel::class; @@ -36,6 +38,10 @@ class Unreachable extends Notification implements ShouldQueue $channels[] = TelegramChannel::class; } + if ($isNtfyEnabled) { + $channels[] = NtfyChannel::class; + } + return $channels; } @@ -50,6 +56,14 @@ class Unreachable extends Notification implements ShouldQueue return $mail; } + public function toNtfy(): array + { + return [ + 'title' => "Coolify: Your server '{$this->server->name}' is unreachable.", + 'message' => 'All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server and turn on all automations & integrations.', + ]; + } + public function toDiscord(): string { $message = "Coolify: Your server '{$this->server->name}' is unreachable. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server and turn on all automations & integrations."; diff --git a/app/Notifications/Test.php b/app/Notifications/Test.php index f873a95d3..fd4b69e98 100644 --- a/app/Notifications/Test.php +++ b/app/Notifications/Test.php @@ -38,6 +38,16 @@ class Test extends Notification implements ShouldQueue return $message; } + public function toNtfy(): array + { + return [ + 'title' => 'Coolify: Test Ntfy Notification', + 'message' => 'Coolify: This is a test Ntfy notification from Coolify.', + 'buttons' => 'view, Go to your dashboard, '.base_url().';', + 'emoji' => 'rocket', + ]; + } + public function toTelegram(): array { return [ diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index fe51f78ac..626193fdd 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -25,6 +25,7 @@ use App\Models\Team; use App\Models\User; use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\EmailChannel; +use App\Notifications\Channels\NtfyChannel; use App\Notifications\Channels\TelegramChannel; use App\Notifications\Internal\GeneralNotification; use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException; @@ -425,9 +426,11 @@ function setNotificationChannels($notifiable, $event) $isEmailEnabled = isEmailEnabled($notifiable); $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); + $isNtfyEnabled = data_get($notifiable, 'ntfy_enabled'); $isSubscribedToEmailEvent = data_get($notifiable, "smtp_notifications_$event"); $isSubscribedToDiscordEvent = data_get($notifiable, "discord_notifications_$event"); $isSubscribedToTelegramEvent = data_get($notifiable, "telegram_notifications_$event"); + $isSubscribedToNtfyEvent = data_get($notifiable, "ntfy_notifications_$event"); if ($isDiscordEnabled && $isSubscribedToDiscordEvent) { $channels[] = DiscordChannel::class; @@ -439,6 +442,10 @@ function setNotificationChannels($notifiable, $event) $channels[] = TelegramChannel::class; } + if ($isNtfyEnabled && $isSubscribedToNtfyEvent) { + $channels[] = NtfyChannel::class; + } + return $channels; } function parseEnvFormatToArray($env_file_contents) @@ -1322,7 +1329,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal updateCompose($savedService); return $service; - }); $envs_from_coolify = $resource->environment_variables()->get(); diff --git a/database/migrations/2024_06_26_123458_add_nfty_notfications_to_teams.php b/database/migrations/2024_06_26_123458_add_nfty_notfications_to_teams.php new file mode 100644 index 000000000..26f7e5d5c --- /dev/null +++ b/database/migrations/2024_06_26_123458_add_nfty_notfications_to_teams.php @@ -0,0 +1,40 @@ +boolean('ntfy_enabled')->default(false); + $table->text('ntfy_url')->nullable(); + $table->text('ntfy_topic')->nullable(); + $table->text('ntfy_username')->nullable(); + $table->text('ntfy_password')->nullable(); + $table->boolean('ntfy_notifications_test')->default(true); + $table->boolean('ntfy_notifications_deployments')->default(true); + $table->boolean('ntfy_notifications_status_changes')->default(true); + $table->boolean('ntfy_notifications_database_backups')->default(true); + $table->boolean('ntfy_notifications_scheduled_tasks')->default(true); + }); + } + + public function down(): void + { + Schema::table('teams', function (Blueprint $table) { + $table->dropColumn('ntfy_enabled'); + $table->dropColumn('ntfy_url'); + $table->dropColumn('ntfy_topic'); + $table->dropColumn('ntfy_username'); + $table->dropColumn('ntfy_password'); + $table->dropColumn('ntfy_notifications_test'); + $table->dropColumn('ntfy_notifications_deployments'); + $table->dropColumn('ntfy_notifications_status_changes'); + $table->dropColumn('ntfy_notifications_database_backups'); + $table->dropColumn('ntfy_notifications_scheduled_tasks'); + }); + } +}; diff --git a/resources/views/components/notification/navbar.blade.php b/resources/views/components/notification/navbar.blade.php index 0fbbc69a2..e1f037f06 100644 --- a/resources/views/components/notification/navbar.blade.php +++ b/resources/views/components/notification/navbar.blade.php @@ -15,6 +15,10 @@ href="{{ route('notifications.discord') }}"> + + + diff --git a/resources/views/livewire/notifications/ntfy.blade.php b/resources/views/livewire/notifications/ntfy.blade.php new file mode 100644 index 000000000..37bfc8825 --- /dev/null +++ b/resources/views/livewire/notifications/ntfy.blade.php @@ -0,0 +1,56 @@ +
+ + Notifications | Coolify + + +
+
+

Ntfy.sh

+ + Save + + @if ($team->ntfy_enabled) + + Send Test Notifications + + @endif +
+
+ +
+ + + + + +
+ + +
+ + @if (data_get($team, 'ntfy_enabled')) +

Subscribe to events

+
+ @if (isDev()) + + @endif + + + + +
+ @endif +
+ diff --git a/routes/web.php b/routes/web.php index 0c012fd34..5f85348d2 100644 --- a/routes/web.php +++ b/routes/web.php @@ -13,6 +13,7 @@ use App\Livewire\ForcePasswordReset; use App\Livewire\Notifications\Discord as NotificationDiscord; use App\Livewire\Notifications\Email as NotificationEmail; use App\Livewire\Notifications\Telegram as NotificationTelegram; +use App\Livewire\Notifications\Ntfy as NotificationNtfy; use App\Livewire\Profile\Index as ProfileIndex; use App\Livewire\Project\Application\Configuration as ApplicationConfiguration; use App\Livewire\Project\Application\Deployment\Index as DeploymentIndex; @@ -126,6 +127,7 @@ Route::middleware(['auth', 'verified'])->group(function () { Route::get('/email', NotificationEmail::class)->name('notifications.email'); Route::get('/telegram', NotificationTelegram::class)->name('notifications.telegram'); Route::get('/discord', NotificationDiscord::class)->name('notifications.discord'); + Route::get('/ntfy', NotificationNtfy::class)->name('notifications.ntfy'); }); Route::prefix('storages')->group(function () { @@ -274,7 +276,7 @@ Route::middleware(['auth'])->group(function () { if ($stream === false) { abort(500, 'Failed to open stream for the requested file.'); } - while (! feof($stream)) { + while (!feof($stream)) { echo fread($stream, 2048); flush(); } @@ -282,7 +284,7 @@ Route::middleware(['auth'])->group(function () { fclose($stream); }, 200, [ 'Content-Type' => 'application/octet-stream', - 'Content-Disposition' => 'attachment; filename="'.basename($filename).'"', + 'Content-Disposition' => 'attachment; filename="' . basename($filename) . '"', ]); } catch (\Throwable $e) { return response()->json(['message' => $e->getMessage()], 500); @@ -325,7 +327,7 @@ Route::middleware(['auth'])->group(function () { Route::get('/destination/{destination_uuid}', function () { $standalone_dockers = StandaloneDocker::where('uuid', request()->destination_uuid)->first(); $swarm_dockers = SwarmDocker::where('uuid', request()->destination_uuid)->first(); - if (! $standalone_dockers && ! $swarm_dockers) { + if (!$standalone_dockers && !$swarm_dockers) { abort(404); } $destination = $standalone_dockers ? $standalone_dockers : $swarm_dockers;