add support for ntfy.sh

This commit is contained in:
Daniel 2024-06-27 15:22:36 +02:00
parent e9158b7305
commit b200175df1
27 changed files with 549 additions and 17 deletions

View file

@ -0,0 +1,84 @@
<?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 SendMessageToNtfyJob 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;
/**
* Create a new job instance.
*
* @param string $text The message to send
* @param string|null $buttons The buttons to send. These buttons follow the format as described in the Ntfy documentation: https://docs.ntfy.sh/publish/?h=user#defining-actions
* @param string|null $emoji The emoji to use for the message. We use the shortcodes for emojis. A list of them can be found here: https://docs.ntfy.sh/emojis/
* @param string|null $title The title of the message
* @param string $url The URL of the Ntfy instance
* @param string|null $username The username to use for basic authentication
* @param string|null $password The password to use for basic authentication
* @param string $topic The topic to send the message to
*/
public function __construct(
public string $text,
public ?string $buttons,
public ?string $emoji,
public ?string $title,
public string $url,
public ?string $username,
public ?string $password,
public string $topic
) {
}
/**
* Execute the job.
*/
public function handle(): void
{
$headers = [];
if ($this->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);
}
}

View file

@ -0,0 +1,72 @@
<?php
namespace App\Livewire\Notifications;
use App\Models\Team;
use App\Notifications\Test;
use Livewire\Component;
class Ntfy extends Component
{
public Team $team;
protected $rules = [
'team.ntfy_enabled' => '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');
}
}

View file

@ -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;
}

View file

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

View file

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

View file

@ -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,

View file

@ -0,0 +1,42 @@
<?php
namespace App\Notifications\Channels;
use App\Jobs\SendMessageToDiscordJob;
use App\Jobs\SendMessageToNtfyJob;
use Illuminate\Notifications\Notification;
class NtfyChannel
{
/**
* Send the given notification.
*/
public function send(SendsNtfy $notifiable, Notification $notification): void
{
$content = $notification->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
));
}
}

View file

@ -0,0 +1,8 @@
<?php
namespace App\Notifications\Channels;
interface SendsNtfy
{
public function routeNotificationForNtfy();
}

View file

@ -41,6 +41,16 @@ class ContainerRestarted extends Notification implements ShouldQueue
return $message;
}
public function toNtfy(): array
{
return [
'title' => "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}";

View file

@ -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}";

View file

@ -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}";

View file

@ -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.";

View file

@ -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';

View file

@ -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 [

View file

@ -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}";

View file

@ -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}";

View file

@ -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).";

View file

@ -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 [

View file

@ -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.";

View file

@ -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 [

View file

@ -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.";

View file

@ -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 [

View file

@ -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();

View file

@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('teams', function (Blueprint $table) {
$table->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');
});
}
};

View file

@ -15,6 +15,10 @@
href="{{ route('notifications.discord') }}">
<button>Discord</button>
</a>
<a class="{{ request()->routeIs('notifications.ntfy') ? 'dark:text-white' : '' }}"
href="{{ route('notifications.ntfy') }}">
<button>Ntfy.sh</button>
</a>
</nav>
</div>
</div>

View file

@ -0,0 +1,56 @@
<div>
<x-slot:title>
Notifications | Coolify
</x-slot>
<x-notification.navbar />
<form wire:submit='submit' class="flex flex-col gap-4">
<div class="flex items-center gap-2">
<h2>Ntfy.sh</h2>
<x-forms.button type="submit">
Save
</x-forms.button>
@if ($team->ntfy_enabled)
<x-forms.button class="normal-case dark:text-white btn btn-xs no-animation btn-primary"
wire:click="sendTestNotification">
Send Test Notifications
</x-forms.button>
@endif
</div>
<div class="w-32">
<x-forms.checkbox instantSave id="team.ntfy_enabled" label="Enabled" />
</div>
<x-forms.input type="text"
helper="Enter your preferred ntfy host<br>Example: https://ntfy.sh" required
id="team.ntfy_url" label="Host" />
<x-forms.input type="text"
helper="Ntfy topic you want to subscribe to" required
id="team.ntfy_topic" label="Topic" />
<div class="flex gap-2">
<x-forms.input helper="If you have set up a user please enter its username"
id="team.ntfy_username" label="Username" />
<x-forms.input type="password"
helper="If you have set up a user please enter its password"
id="team.ntfy_password" label="Password" />
</div>
</form>
@if (data_get($team, 'ntfy_enabled'))
<h2 class="mt-4">Subscribe to events</h2>
<div class="w-64">
@if (isDev())
<x-forms.checkbox instantSave="saveModel" id="team.ntfy_notifications_test" label="Test" />
@endif
<x-forms.checkbox instantSave="saveModel" id="team.ntfy_notifications_status_changes"
label="Container Status Changes" />
<x-forms.checkbox instantSave="saveModel" id="team.ntfy_notifications_deployments"
label="Application Deployments" />
<x-forms.checkbox instantSave="saveModel" id="team.ntfy_notifications_database_backups"
label="Backup Status" />
<x-forms.checkbox instantSave="saveModel" id="team.ntfy_notifications_scheduled_tasks"
label="Scheduled Tasks Status" />
</div>
@endif
</div>

View file

@ -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;