Merge pull request #4264 from marvinvr/main

feat: add slack notifications
This commit is contained in:
Andras Bacsai
2024-12-06 14:35:55 +01:00
committed by GitHub
27 changed files with 807 additions and 167 deletions

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Jobs;
use App\Notifications\Dto\SlackMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
class SendMessageToSlackJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
private SlackMessage $message,
private string $webhookUrl
) {
$this->onQueue('high');
}
public function handle(): void
{
Http::post($this->webhookUrl, [
'blocks' => [
[
'type' => 'section',
'text' => [
'type' => 'plain_text',
'text' => 'Coolify Notification',
],
],
],
'attachments' => [
[
'color' => $this->message->color,
'blocks' => [
[
'type' => 'header',
'text' => [
'type' => 'plain_text',
'text' => $this->message->title,
],
],
[
'type' => 'section',
'text' => [
'type' => 'mrkdwn',
'text' => $this->message->description,
],
],
],
],
],
]);
}
}

View File

@@ -0,0 +1,130 @@
<?php
namespace App\Livewire\Notifications;
use App\Models\Team;
use App\Notifications\Test;
use Livewire\Attributes\Validate;
use Livewire\Component;
class Slack extends Component
{
public Team $team;
#[Validate(['boolean'])]
public bool $slackEnabled = false;
#[Validate(['url', 'nullable'])]
public ?string $slackWebhookUrl = null;
#[Validate(['boolean'])]
public bool $slackNotificationsTest = false;
#[Validate(['boolean'])]
public bool $slackNotificationsDeployments = false;
#[Validate(['boolean'])]
public bool $slackNotificationsStatusChanges = false;
#[Validate(['boolean'])]
public bool $slackNotificationsDatabaseBackups = false;
#[Validate(['boolean'])]
public bool $slackNotificationsScheduledTasks = false;
#[Validate(['boolean'])]
public bool $slackNotificationsServerDiskUsage = false;
public function mount()
{
try {
$this->team = auth()->user()->currentTeam();
$this->syncData();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function syncData(bool $toModel = false)
{
if ($toModel) {
$this->validate();
$this->team->slack_enabled = $this->slackEnabled;
$this->team->slack_webhook_url = $this->slackWebhookUrl;
$this->team->slack_notifications_test = $this->slackNotificationsTest;
$this->team->slack_notifications_deployments = $this->slackNotificationsDeployments;
$this->team->slack_notifications_status_changes = $this->slackNotificationsStatusChanges;
$this->team->slack_notifications_database_backups = $this->slackNotificationsDatabaseBackups;
$this->team->slack_notifications_scheduled_tasks = $this->slackNotificationsScheduledTasks;
$this->team->slack_notifications_server_disk_usage = $this->slackNotificationsServerDiskUsage;
$this->team->save();
refreshSession();
} else {
$this->slackEnabled = $this->team->slack_enabled;
$this->slackWebhookUrl = $this->team->slack_webhook_url;
$this->slackNotificationsTest = $this->team->slack_notifications_test;
$this->slackNotificationsDeployments = $this->team->slack_notifications_deployments;
$this->slackNotificationsStatusChanges = $this->team->slack_notifications_status_changes;
$this->slackNotificationsDatabaseBackups = $this->team->slack_notifications_database_backups;
$this->slackNotificationsScheduledTasks = $this->team->slack_notifications_scheduled_tasks;
$this->slackNotificationsServerDiskUsage = $this->team->slack_notifications_server_disk_usage;
}
}
public function instantSaveSlackEnabled()
{
try {
$this->validate([
'slackWebhookUrl' => 'required',
], [
'slackWebhookUrl.required' => 'Slack Webhook URL is required.',
]);
$this->saveModel();
} catch (\Throwable $e) {
$this->slackEnabled = false;
return handleError($e, $this);
}
}
public function instantSave()
{
try {
$this->syncData(true);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function submit()
{
try {
$this->resetErrorBag();
$this->syncData(true);
$this->saveModel();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function saveModel()
{
$this->syncData(true);
refreshSession();
$this->dispatch('success', 'Settings saved.');
}
public function sendTestNotification()
{
try {
$this->team->notify(new Test);
$this->dispatch('success', 'Test notification sent.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.notifications.slack');
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Models;
use App\Notifications\Channels\SendsDiscord;
use App\Notifications\Channels\SendsEmail;
use App\Notifications\Channels\SendsSlack;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
@@ -70,7 +71,7 @@ use OpenApi\Attributes as OA;
),
]
)]
class Team extends Model implements SendsDiscord, SendsEmail
class Team extends Model implements SendsDiscord, SendsEmail, SendsSlack
{
use Notifiable;
@@ -127,6 +128,11 @@ class Team extends Model implements SendsDiscord, SendsEmail
];
}
public function routeNotificationForSlack()
{
return data_get($this, 'slack_webhook_url', null);
}
public function getRecepients($notification)
{
$recipients = data_get($notification, 'emails', null);
@@ -161,7 +167,7 @@ class Team extends Model implements SendsDiscord, SendsEmail
return 9999999;
}
$team = Team::find(currentTeam()->id);
if (! $team) {
if (!$team) {
return 0;
}

View File

@@ -6,6 +6,7 @@ use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
use App\Notifications\Dto\SlackMessage;
use Illuminate\Notifications\Messages\MailMessage;
class DeploymentFailed extends CustomEmailNotification
@@ -39,7 +40,7 @@ class DeploymentFailed extends CustomEmailNotification
if (str($this->fqdn)->explode(',')->count() > 1) {
$this->fqdn = str($this->fqdn)->explode(',')->first();
}
$this->deployment_url = base_url()."/project/{$this->project_uuid}/".urlencode($this->environment_name)."/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
$this->deployment_url = base_url() . "/project/{$this->project_uuid}/" . urlencode($this->environment_name) . "/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
}
public function via(object $notifiable): array
@@ -53,10 +54,10 @@ class DeploymentFailed extends CustomEmailNotification
$pull_request_id = data_get($this->preview, 'pull_request_id', 0);
$fqdn = $this->fqdn;
if ($pull_request_id === 0) {
$mail->subject('Coolify: Deployment failed of '.$this->application_name.'.');
$mail->subject('Coolify: Deployment failed of ' . $this->application_name . '.');
} else {
$fqdn = $this->preview->fqdn;
$mail->subject('Coolify: Deployment failed of pull request #'.$this->preview->pull_request_id.' of '.$this->application_name.'.');
$mail->subject('Coolify: Deployment failed of pull request #' . $this->preview->pull_request_id . ' of ' . $this->application_name . '.');
}
$mail->view('emails.application-deployment-failed', [
'name' => $this->application_name,
@@ -73,7 +74,7 @@ class DeploymentFailed extends CustomEmailNotification
if ($this->preview) {
$message = new DiscordMessage(
title: ':cross_mark: Deployment failed',
description: 'Pull request: '.$this->preview->pull_request_id,
description: 'Pull request: ' . $this->preview->pull_request_id,
color: DiscordMessage::errorColor(),
isCritical: true,
);
@@ -82,13 +83,13 @@ class DeploymentFailed extends CustomEmailNotification
$message->addField('Environment', $this->environment_name, true);
$message->addField('Name', $this->application_name, true);
$message->addField('Deployment Logs', '[Link]('.$this->deployment_url.')');
$message->addField('Deployment Logs', '[Link](' . $this->deployment_url . ')');
if ($this->fqdn) {
$message->addField('Domain', $this->fqdn, true);
}
} else {
if ($this->fqdn) {
$description = '[Open application]('.$this->fqdn.')';
$description = '[Open application](' . $this->fqdn . ')';
} else {
$description = '';
}
@@ -103,7 +104,7 @@ class DeploymentFailed extends CustomEmailNotification
$message->addField('Environment', $this->environment_name, true);
$message->addField('Name', $this->application_name, true);
$message->addField('Deployment Logs', '[Link]('.$this->deployment_url.')');
$message->addField('Deployment Logs', '[Link](' . $this->deployment_url . ')');
}
return $message;
@@ -112,9 +113,9 @@ class DeploymentFailed extends CustomEmailNotification
public function toTelegram(): array
{
if ($this->preview) {
$message = 'Coolify: Pull request #'.$this->preview->pull_request_id.' of '.$this->application_name.' ('.$this->preview->fqdn.') deployment failed: ';
$message = 'Coolify: Pull request #' . $this->preview->pull_request_id . ' of ' . $this->application_name . ' (' . $this->preview->fqdn . ') deployment failed: ';
} else {
$message = 'Coolify: Deployment failed of '.$this->application_name.' ('.$this->fqdn.'): ';
$message = 'Coolify: Deployment failed of ' . $this->application_name . ' (' . $this->fqdn . '): ';
}
$buttons[] = [
'text' => 'Deployment logs',
@@ -128,4 +129,31 @@ class DeploymentFailed extends CustomEmailNotification
],
];
}
public function toSlack(): SlackMessage
{
if ($this->preview) {
$title = "Pull request #{$this->preview->pull_request_id} deployment failed";
$description = "Pull request deployment failed for {$this->application_name}";
if ($this->preview->fqdn) {
$description .= "\nPreview URL: {$this->preview->fqdn}";
}
} else {
$title = "Deployment failed";
$description = "Deployment failed for {$this->application_name}";
if ($this->fqdn) {
$description .= "\nApplication URL: {$this->fqdn}";
}
}
$description .= "\n\n**Project:** " . data_get($this->application, 'environment.project.name');
$description .= "\n**Environment:** {$this->environment_name}";
$description .= "\n**Deployment Logs:** {$this->deployment_url}";
return new SlackMessage(
title: $title,
description: $description,
color: SlackMessage::errorColor()
);
}
}

View File

@@ -7,6 +7,7 @@ use App\Models\ApplicationPreview;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
use Illuminate\Notifications\Messages\MailMessage;
use App\Notifications\Dto\SlackMessage;
class DeploymentSuccess extends CustomEmailNotification
{
@@ -39,7 +40,7 @@ class DeploymentSuccess extends CustomEmailNotification
if (str($this->fqdn)->explode(',')->count() > 1) {
$this->fqdn = str($this->fqdn)->explode(',')->first();
}
$this->deployment_url = base_url()."/project/{$this->project_uuid}/".urlencode($this->environment_name)."/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
$this->deployment_url = base_url() . "/project/{$this->project_uuid}/" . urlencode($this->environment_name) . "/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
}
public function via(object $notifiable): array
@@ -79,21 +80,21 @@ class DeploymentSuccess extends CustomEmailNotification
if ($this->preview) {
$message = new DiscordMessage(
title: ':white_check_mark: Preview deployment successful',
description: 'Pull request: '.$this->preview->pull_request_id,
description: 'Pull request: ' . $this->preview->pull_request_id,
color: DiscordMessage::successColor(),
);
if ($this->preview->fqdn) {
$message->addField('Application', '[Link]('.$this->preview->fqdn.')');
$message->addField('Application', '[Link](' . $this->preview->fqdn . ')');
}
$message->addField('Project', data_get($this->application, 'environment.project.name'), true);
$message->addField('Environment', $this->environment_name, true);
$message->addField('Name', $this->application_name, true);
$message->addField('Deployment logs', '[Link]('.$this->deployment_url.')');
$message->addField('Deployment logs', '[Link](' . $this->deployment_url . ')');
} else {
if ($this->fqdn) {
$description = '[Open application]('.$this->fqdn.')';
$description = '[Open application](' . $this->fqdn . ')';
} else {
$description = '';
}
@@ -106,7 +107,7 @@ class DeploymentSuccess extends CustomEmailNotification
$message->addField('Environment', $this->environment_name, true);
$message->addField('Name', $this->application_name, true);
$message->addField('Deployment logs', '[Link]('.$this->deployment_url.')');
$message->addField('Deployment logs', '[Link](' . $this->deployment_url . ')');
}
return $message;
@@ -115,7 +116,7 @@ class DeploymentSuccess extends CustomEmailNotification
public function toTelegram(): array
{
if ($this->preview) {
$message = 'Coolify: New PR'.$this->preview->pull_request_id.' version successfully deployed of '.$this->application_name.'';
$message = 'Coolify: New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . '';
if ($this->preview->fqdn) {
$buttons[] = [
'text' => 'Open Application',
@@ -123,7 +124,7 @@ class DeploymentSuccess extends CustomEmailNotification
];
}
} else {
$message = '✅ New version successfully deployed of '.$this->application_name.'';
$message = '✅ New version successfully deployed of ' . $this->application_name . '';
if ($this->fqdn) {
$buttons[] = [
'text' => 'Open Application',
@@ -143,4 +144,32 @@ class DeploymentSuccess extends CustomEmailNotification
],
];
}
public function toSlack(): SlackMessage
{
if ($this->preview) {
$title = "Pull request #{$this->preview->pull_request_id} successfully deployed";
$description = "New version successfully deployed for {$this->application_name}";
if ($this->preview->fqdn) {
$description .= "\nPreview URL: {$this->preview->fqdn}";
}
} else {
$title = "New version successfully deployed";
$description = "New version successfully deployed for {$this->application_name}";
if ($this->fqdn) {
$description .= "\nApplication URL: {$this->fqdn}";
}
}
$description .= "\n\n**Project:** " . data_get($this->application, 'environment.project.name');
$description .= "\n**Environment:** {$this->environment_name}";
$description .= "\n**Deployment Logs:** {$this->deployment_url}";
return new SlackMessage(
title: $title,
description: $description,
color: SlackMessage::successColor()
);
}
}

View File

@@ -6,6 +6,7 @@ use App\Models\Application;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
use Illuminate\Notifications\Messages\MailMessage;
use App\Notifications\Dto\SlackMessage;
class StatusChanged extends CustomEmailNotification
{
@@ -29,7 +30,7 @@ class StatusChanged extends CustomEmailNotification
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
@@ -55,7 +56,7 @@ class StatusChanged extends CustomEmailNotification
{
return new DiscordMessage(
title: ':cross_mark: Application stopped',
description: '[Open Application in Coolify]('.$this->resource_url.')',
description: '[Open Application in Coolify](' . $this->resource_url . ')',
color: DiscordMessage::errorColor(),
isCritical: true,
);
@@ -63,7 +64,7 @@ class StatusChanged extends CustomEmailNotification
public function toTelegram(): array
{
$message = 'Coolify: '.$this->resource_name.' has been stopped.';
$message = 'Coolify: ' . $this->resource_name . ' has been stopped.';
return [
'message' => $message,
@@ -75,4 +76,20 @@ class StatusChanged extends CustomEmailNotification
],
];
}
public function toSlack(): SlackMessage
{
$title = "Application stopped";
$description = "{$this->resource_name} has been stopped";
$description .= "\n\n**Project:** " . data_get($this->resource, 'environment.project.name');
$description .= "\n**Environment:** {$this->environment_name}";
$description .= "\n**Application URL:** {$this->resource_url}";
return new SlackMessage(
title: $title,
description: $description,
color: SlackMessage::errorColor()
);
}
}

View File

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

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Notifications\Channels;
use App\Jobs\SendMessageToSlackJob;
use Illuminate\Notifications\Notification;
class SlackChannel
{
/**
* Send the given notification.
*/
public function send(SendsSlack $notifiable, Notification $notification): void
{
$message = $notification->toSlack();
$webhookUrl = $notifiable->routeNotificationForSlack();
if (! $webhookUrl) {
return;
}
SendMessageToSlackJob::dispatch($message, $webhookUrl);
}
}

View File

@@ -6,6 +6,7 @@ use App\Models\Server;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
use Illuminate\Notifications\Messages\MailMessage;
use App\Notifications\Dto\SlackMessage;
class ContainerRestarted extends CustomEmailNotification
{
@@ -41,7 +42,7 @@ class ContainerRestarted extends CustomEmailNotification
);
if ($this->url) {
$message->addField('Resource', '[Link]('.$this->url.')');
$message->addField('Resource', '[Link](' . $this->url . ')');
}
return $message;
@@ -66,4 +67,20 @@ class ContainerRestarted extends CustomEmailNotification
return $payload;
}
public function toSlack(): SlackMessage
{
$title = "Resource restarted";
$description = "A resource ({$this->name}) has been restarted automatically on {$this->server->name}";
if ($this->url) {
$description .= "\n**Resource URL:** {$this->url}";
}
return new SlackMessage(
title: $title,
description: $description,
color: SlackMessage::warningColor()
);
}
}

View File

@@ -6,6 +6,7 @@ use App\Models\Server;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
use Illuminate\Notifications\Messages\MailMessage;
use App\Notifications\Dto\SlackMessage;
class ContainerStopped extends CustomEmailNotification
{
@@ -41,7 +42,7 @@ class ContainerStopped extends CustomEmailNotification
);
if ($this->url) {
$message->addField('Resource', '[Link]('.$this->url.')');
$message->addField('Resource', '[Link](' . $this->url . ')');
}
return $message;
@@ -66,4 +67,20 @@ class ContainerStopped extends CustomEmailNotification
return $payload;
}
public function toSlack(): SlackMessage
{
$title = "Resource stopped";
$description = "A resource ({$this->name}) has been stopped unexpectedly on {$this->server->name}";
if ($this->url) {
$description .= "\n**Resource URL:** {$this->url}";
}
return new SlackMessage(
title: $title,
description: $description,
color: SlackMessage::errorColor()
);
}
}

View File

@@ -6,6 +6,7 @@ use App\Models\ScheduledDatabaseBackup;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
use Illuminate\Notifications\Messages\MailMessage;
use App\Notifications\Dto\SlackMessage;
class BackupFailed extends CustomEmailNotification
{
@@ -62,4 +63,19 @@ class BackupFailed extends CustomEmailNotification
'message' => $message,
];
}
public function toSlack(): SlackMessage
{
$title = "Database backup failed";
$description = "Database backup for {$this->name} (db:{$this->database_name}) has FAILED.";
$description .= "\n\n**Frequency:** {$this->frequency}";
$description .= "\n\n**Error Output:**\n{$this->output}";
return new SlackMessage(
title: $title,
description: $description,
color: SlackMessage::errorColor()
);
}
}

View File

@@ -6,6 +6,7 @@ use App\Models\ScheduledDatabaseBackup;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
use Illuminate\Notifications\Messages\MailMessage;
use App\Notifications\Dto\SlackMessage;
class BackupSuccess extends CustomEmailNotification
{
@@ -60,4 +61,18 @@ class BackupSuccess extends CustomEmailNotification
'message' => $message,
];
}
public function toSlack(): SlackMessage
{
$title = "Database backup successful";
$description = "Database backup for {$this->name} (db:{$this->database_name}) was successful.";
$description .= "\n\n**Frequency:** {$this->frequency}";
return new SlackMessage(
title: $title,
description: $description,
color: SlackMessage::successColor()
);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Notifications\Dto;
class SlackMessage
{
public function __construct(
public string $title,
public string $description,
public string $color = '#0099ff'
) {
}
public static function infoColor(): string
{
return '#0099ff';
}
public static function errorColor(): string
{
return '#ff0000';
}
public static function successColor(): string
{
return '#00ff00';
}
public static function warningColor(): string
{
return '#ffa500';
}
}

View File

@@ -4,7 +4,9 @@ namespace App\Notifications\Internal;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\TelegramChannel;
use App\Notifications\Channels\SlackChannel;
use App\Notifications\Dto\DiscordMessage;
use App\Notifications\Dto\SlackMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
@@ -25,6 +27,7 @@ class GeneralNotification extends Notification implements ShouldQueue
$channels = [];
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
$isSlackEnabled = data_get($notifiable, 'slack_enabled');
if ($isDiscordEnabled) {
$channels[] = DiscordChannel::class;
@@ -32,6 +35,9 @@ class GeneralNotification extends Notification implements ShouldQueue
if ($isTelegramEnabled) {
$channels[] = TelegramChannel::class;
}
if ($isSlackEnabled) {
$channels[] = SlackChannel::class;
}
return $channels;
}
@@ -51,4 +57,13 @@ class GeneralNotification extends Notification implements ShouldQueue
'message' => $this->message,
];
}
public function toSlack(): SlackMessage
{
return new SlackMessage(
title: 'Coolify: General Notification',
description: $this->message,
color: SlackMessage::infoColor(),
);
}
}

View File

@@ -6,6 +6,7 @@ use App\Models\ScheduledTask;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
use Illuminate\Notifications\Messages\MailMessage;
use App\Notifications\Dto\SlackMessage;
class TaskFailed extends CustomEmailNotification
{
@@ -48,7 +49,7 @@ class TaskFailed extends CustomEmailNotification
);
if ($this->url) {
$message->addField('Scheduled task', '[Link]('.$this->url.')');
$message->addField('Scheduled task', '[Link](' . $this->url . ')');
}
return $message;
@@ -68,4 +69,24 @@ class TaskFailed extends CustomEmailNotification
'message' => $message,
];
}
public function toSlack(): SlackMessage
{
$title = "Scheduled task failed";
$description = "Scheduled task ({$this->task->name}) failed.";
if ($this->output) {
$description .= "\n\n**Error Output:**\n{$this->output}";
}
if ($this->url) {
$description .= "\n\n**Task URL:** {$this->url}";
}
return new SlackMessage(
title: $title,
description: $description,
color: SlackMessage::errorColor()
);
}
}

View File

@@ -7,6 +7,7 @@ use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\TelegramChannel;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
use App\Notifications\Dto\SlackMessage;
class DockerCleanup extends CustomEmailNotification
{
@@ -21,7 +22,7 @@ class DockerCleanup extends CustomEmailNotification
// $isEmailEnabled = isEmailEnabled($notifiable);
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
$isSlackEnabled = data_get($notifiable, 'slack_enabled');
if ($isDiscordEnabled) {
$channels[] = DiscordChannel::class;
}
@@ -31,6 +32,9 @@ class DockerCleanup extends CustomEmailNotification
if ($isTelegramEnabled) {
$channels[] = TelegramChannel::class;
}
if ($isSlackEnabled) {
$channels[] = SlackChannel::class;
}
return $channels;
}
@@ -62,4 +66,13 @@ class DockerCleanup extends CustomEmailNotification
'message' => "Coolify: Server '{$this->server->name}' cleanup job done!\n\n{$this->message}",
];
}
public function toSlack(): SlackMessage
{
return new SlackMessage(
title: 'Server cleanup job done',
description: "Server '{$this->server->name}' cleanup job done!\n\n{$this->message}",
color: SlackMessage::successColor()
);
}
}

View File

@@ -6,6 +6,8 @@ use App\Models\Server;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\EmailChannel;
use App\Notifications\Channels\TelegramChannel;
use App\Notifications\Channels\SlackChannel;
use App\Notifications\Dto\SlackMessage;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
use Illuminate\Notifications\Messages\MailMessage;
@@ -23,7 +25,7 @@ class ForceDisabled extends CustomEmailNotification
$isEmailEnabled = isEmailEnabled($notifiable);
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
$isSlackEnabled = data_get($notifiable, 'slack_enabled');
if ($isDiscordEnabled) {
$channels[] = DiscordChannel::class;
}
@@ -33,6 +35,9 @@ class ForceDisabled extends CustomEmailNotification
if ($isTelegramEnabled) {
$channels[] = TelegramChannel::class;
}
if ($isSlackEnabled) {
$channels[] = SlackChannel::class;
}
return $channels;
}
@@ -67,4 +72,19 @@ class ForceDisabled extends CustomEmailNotification
'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/subscriptions).",
];
}
public function toSlack(): SlackMessage
{
$title = "Server disabled";
$description = "Server ({$this->server->name}) disabled because it is not paid!\n";
$description .= "All automations and integrations are stopped.\n\n";
$description .= "Please update your subscription to enable the server again: https://app.coolify.io/subscriptions";
return new SlackMessage(
title: $title,
description: $description,
color: SlackMessage::errorColor()
);
}
}

View File

@@ -6,6 +6,8 @@ use App\Models\Server;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\EmailChannel;
use App\Notifications\Channels\TelegramChannel;
use App\Notifications\Channels\SlackChannel;
use App\Notifications\Dto\SlackMessage;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
use Illuminate\Notifications\Messages\MailMessage;
@@ -23,7 +25,7 @@ class ForceEnabled extends CustomEmailNotification
$isEmailEnabled = isEmailEnabled($notifiable);
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
$isSlackEnabled = data_get($notifiable, 'slack_enabled');
if ($isDiscordEnabled) {
$channels[] = DiscordChannel::class;
}
@@ -33,6 +35,9 @@ class ForceEnabled extends CustomEmailNotification
if ($isTelegramEnabled) {
$channels[] = TelegramChannel::class;
}
if ($isSlackEnabled) {
$channels[] = SlackChannel::class;
}
return $channels;
}
@@ -63,4 +68,15 @@ class ForceEnabled extends CustomEmailNotification
'message' => "Coolify: Server ({$this->server->name}) enabled again!",
];
}
public function toSlack(): SlackMessage
{
return new SlackMessage(
title: 'Server enabled',
description: "Server '{$this->server->name}' enabled again!",
color: SlackMessage::successColor()
);
}
}

View File

@@ -5,6 +5,7 @@ namespace App\Notifications\Server;
use App\Models\Server;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
use App\Notifications\Dto\SlackMessage;
use Illuminate\Notifications\Messages\MailMessage;
class HighDiskUsage extends CustomEmailNotification
@@ -44,7 +45,7 @@ class HighDiskUsage extends CustomEmailNotification
$message->addField('Disk usage', "{$this->disk_usage}%", true);
$message->addField('Threshold', "{$this->server_disk_usage_notification_threshold}%", true);
$message->addField('What to do?', '[Link](https://coolify.io/docs/knowledge-base/server/automated-cleanup)', true);
$message->addField('Change Settings', '[Threshold]('.base_url().'/server/'.$this->server->uuid.'#advanced) | [Notification]('.base_url().'/notifications/discord)');
$message->addField('Change Settings', '[Threshold](' . base_url() . '/server/' . $this->server->uuid . '#advanced) | [Notification](' . base_url() . '/notifications/discord)');
return $message;
}
@@ -55,4 +56,22 @@ class HighDiskUsage extends CustomEmailNotification
'message' => "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->server_disk_usage_notification_threshold}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.",
];
}
public function toSlack(): SlackMessage
{
$description = "Server '{$this->server->name}' high disk usage detected!\n";
$description .= "Disk usage: {$this->disk_usage}%\n";
$description .= "Threshold: {$this->server_disk_usage_notification_threshold}%\n\n";
$description .= "Please cleanup your disk to prevent data-loss.\n";
$description .= "Tips for cleanup: https://coolify.io/docs/knowledge-base/server/automated-cleanup\n";
$description .= "Change settings:\n";
$description .= "- Threshold: " . base_url() . "/server/" . $this->server->uuid . "#advanced\n";
$description .= "- Notifications: " . base_url() . "/notifications/discord";
return new SlackMessage(
title: 'High disk usage detected',
description: $description,
color: SlackMessage::errorColor()
);
}
}

View File

@@ -6,6 +6,8 @@ use App\Models\Server;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\EmailChannel;
use App\Notifications\Channels\TelegramChannel;
use App\Notifications\Channels\SlackChannel;
use App\Notifications\Dto\SlackMessage;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
use Illuminate\Notifications\Messages\MailMessage;
@@ -18,7 +20,7 @@ class Reachable extends CustomEmailNotification
{
$this->onQueue('high');
$this->isRateLimited = isEmailRateLimited(
limiterKey: 'server-reachable:'.$this->server->id,
limiterKey: 'server-reachable:' . $this->server->id,
);
}
@@ -32,7 +34,7 @@ class Reachable extends CustomEmailNotification
$isEmailEnabled = isEmailEnabled($notifiable);
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
$isSlackEnabled = data_get($notifiable, 'slack_enabled');
if ($isDiscordEnabled) {
$channels[] = DiscordChannel::class;
}
@@ -42,6 +44,9 @@ class Reachable extends CustomEmailNotification
if ($isTelegramEnabled) {
$channels[] = TelegramChannel::class;
}
if ($isSlackEnabled) {
$channels[] = SlackChannel::class;
}
return $channels;
}
@@ -72,4 +77,16 @@ class Reachable extends CustomEmailNotification
'message' => "Coolify: Server '{$this->server->name}' revived. All automations & integrations are turned on again!",
];
}
public function toSlack(): SlackMessage
{
return new SlackMessage(
title: "Server revived",
description: "Server '{$this->server->name}' revived.\nAll automations & integrations are turned on again!",
color: SlackMessage::successColor()
);
}
}

View File

@@ -6,6 +6,8 @@ use App\Models\Server;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\EmailChannel;
use App\Notifications\Channels\TelegramChannel;
use App\Notifications\Channels\SlackChannel;
use App\Notifications\Dto\SlackMessage;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
use Illuminate\Notifications\Messages\MailMessage;
@@ -18,7 +20,7 @@ class Unreachable extends CustomEmailNotification
{
$this->onQueue('high');
$this->isRateLimited = isEmailRateLimited(
limiterKey: 'server-unreachable:'.$this->server->id,
limiterKey: 'server-unreachable:' . $this->server->id,
);
}
@@ -32,6 +34,7 @@ class Unreachable extends CustomEmailNotification
$isEmailEnabled = isEmailEnabled($notifiable);
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
$isSlackEnabled = data_get($notifiable, 'slack_enabled');
if ($isDiscordEnabled) {
$channels[] = DiscordChannel::class;
@@ -42,6 +45,9 @@ class Unreachable extends CustomEmailNotification
if ($isTelegramEnabled) {
$channels[] = TelegramChannel::class;
}
if ($isSlackEnabled) {
$channels[] = SlackChannel::class;
}
return $channels;
}
@@ -76,4 +82,18 @@ class Unreachable extends CustomEmailNotification
'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.",
];
}
public function toSlack(): SlackMessage
{
$description = "Your server '{$this->server->name}' is unreachable.\n";
$description .= "All automations & integrations are turned off!\n\n";
$description .= "*IMPORTANT:* We automatically try to revive your server and turn on all automations & integrations.";
return new SlackMessage(
title: 'Server unreachable',
description: $description,
color: SlackMessage::errorColor()
);
}
}

View File

@@ -3,6 +3,7 @@
namespace App\Notifications;
use App\Notifications\Dto\DiscordMessage;
use App\Notifications\Dto\SlackMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@@ -67,4 +68,12 @@ class Test extends Notification implements ShouldQueue
],
];
}
public function toSlack(): SlackMessage
{
return new SlackMessage(
title: 'Test Slack Notification',
description: 'This is a test Slack notification from Coolify.'
);
}
}

View File

@@ -27,6 +27,7 @@ use App\Models\Team;
use App\Models\User;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\EmailChannel;
use App\Notifications\Channels\SlackChannel;
use App\Notifications\Channels\TelegramChannel;
use App\Notifications\Internal\GeneralNotification;
use Carbon\CarbonImmutable;
@@ -67,27 +68,27 @@ function base_configuration_dir(): string
}
function application_configuration_dir(): string
{
return base_configuration_dir().'/applications';
return base_configuration_dir() . '/applications';
}
function service_configuration_dir(): string
{
return base_configuration_dir().'/services';
return base_configuration_dir() . '/services';
}
function database_configuration_dir(): string
{
return base_configuration_dir().'/databases';
return base_configuration_dir() . '/databases';
}
function database_proxy_dir($uuid): string
{
return base_configuration_dir()."/databases/$uuid/proxy";
return base_configuration_dir() . "/databases/$uuid/proxy";
}
function backup_dir(): string
{
return base_configuration_dir().'/backups';
return base_configuration_dir() . '/backups';
}
function metrics_dir(): string
{
return base_configuration_dir().'/metrics';
return base_configuration_dir() . '/metrics';
}
function sanitize_string(?string $input = null): ?string
@@ -138,15 +139,15 @@ function showBoarding(): bool
}
function refreshSession(?Team $team = null): void
{
if (! $team) {
if (!$team) {
if (Auth::user()->currentTeam()) {
$team = Team::find(Auth::user()->currentTeam()->id);
} else {
$team = User::find(Auth::id())->teams->first();
}
}
Cache::forget('team:'.Auth::id());
Cache::remember('team:'.Auth::id(), 3600, function () use ($team) {
Cache::forget('team:' . Auth::id());
Cache::remember('team:' . Auth::id(), 3600, function () use ($team) {
return $team;
});
session(['currentTeam' => $team]);
@@ -178,7 +179,7 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n
$message = null;
}
if ($customErrorMessage) {
$message = $customErrorMessage.' '.$message;
$message = $customErrorMessage . ' ' . $message;
}
if (isset($livewire)) {
@@ -251,7 +252,7 @@ function generateSSHKey(string $type = 'rsa')
function formatPrivateKey(string $privateKey)
{
$privateKey = trim($privateKey);
if (! str_ends_with($privateKey, "\n")) {
if (!str_ends_with($privateKey, "\n")) {
$privateKey .= "\n";
}
@@ -273,7 +274,7 @@ function is_transactional_emails_active(): bool
function set_transanctional_email_settings(?InstanceSettings $settings = null): ?string
{
if (! $settings) {
if (!$settings) {
$settings = instanceSettings();
}
config()->set('mail.from.address', data_get($settings, 'smtp_from_address'));
@@ -373,7 +374,7 @@ function isSubscribed()
function isProduction(): bool
{
return ! isDev();
return !isDev();
}
function isDev(): bool
{
@@ -426,14 +427,14 @@ function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null
{
$settings = instanceSettings();
$type = set_transanctional_email_settings($settings);
if (! $type) {
if (!$type) {
throw new Exception('No email settings found.');
}
if ($cc) {
Mail::send(
[],
[],
fn (Message $message) => $message
fn(Message $message) => $message
->to($email)
->replyTo($email)
->cc($cc)
@@ -444,7 +445,7 @@ function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null
Mail::send(
[],
[],
fn (Message $message) => $message
fn(Message $message) => $message
->to($email)
->subject($mail->subject)
->html((string) $mail->render())
@@ -469,11 +470,13 @@ function setNotificationChannels($notifiable, $event)
{
$channels = [];
$isEmailEnabled = isEmailEnabled($notifiable);
$isSlackEnabled = data_get($notifiable, 'slack_enabled');
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
$isSubscribedToEmailEvent = data_get($notifiable, "smtp_notifications_$event");
$isSubscribedToDiscordEvent = data_get($notifiable, "discord_notifications_$event");
$isSubscribedToTelegramEvent = data_get($notifiable, "telegram_notifications_$event");
$isSubscribedToSlackEvent = data_get($notifiable, "slack_notifications_$event");
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
$channels[] = DiscordChannel::class;
@@ -484,6 +487,9 @@ function setNotificationChannels($notifiable, $event)
if ($isTelegramEnabled && $isSubscribedToTelegramEvent) {
$channels[] = TelegramChannel::class;
}
if ($isSlackEnabled && $isSubscribedToSlackEvent) {
$channels[] = SlackChannel::class;
}
return $channels;
}
@@ -588,7 +594,7 @@ function getResourceByUuid(string $uuid, ?int $teamId = null)
return null;
}
$resource = queryResourcesByUuid($uuid);
if (! is_null($resource) && $resource->environment->project->team_id === $teamId) {
if (!is_null($resource) && $resource->environment->project->team_id === $teamId) {
return $resource;
}
@@ -680,29 +686,29 @@ function queryResourcesByUuid(string $uuid)
function generateTagDeployWebhook($tag_name)
{
$baseUrl = base_url();
$api = Url::fromString($baseUrl).'/api/v1';
$api = Url::fromString($baseUrl) . '/api/v1';
$endpoint = "/deploy?tag=$tag_name";
return $api.$endpoint;
return $api . $endpoint;
}
function generateDeployWebhook($resource)
{
$baseUrl = base_url();
$api = Url::fromString($baseUrl).'/api/v1';
$api = Url::fromString($baseUrl) . '/api/v1';
$endpoint = '/deploy';
$uuid = data_get($resource, 'uuid');
return $api.$endpoint."?uuid=$uuid&force=false";
return $api . $endpoint . "?uuid=$uuid&force=false";
}
function generateGitManualWebhook($resource, $type)
{
if ($resource->source_id !== 0 && ! is_null($resource->source_id)) {
if ($resource->source_id !== 0 && !is_null($resource->source_id)) {
return null;
}
if ($resource->getMorphClass() === \App\Models\Application::class) {
$baseUrl = base_url();
return Url::fromString($baseUrl)."/webhooks/source/$type/events/manual";
return Url::fromString($baseUrl) . "/webhooks/source/$type/events/manual";
}
return null;
@@ -729,7 +735,7 @@ function getTopLevelNetworks(Service|Application $resource)
$hasHostNetworkMode = data_get($service, 'network_mode') === 'host' ? true : false;
// Only add 'networks' key if 'network_mode' is not 'host'
if (! $hasHostNetworkMode) {
if (!$hasHostNetworkMode) {
// Collect/create/update networks
if ($serviceNetworks->count() > 0) {
foreach ($serviceNetworks as $networkName => $networkDetails) {
@@ -743,7 +749,7 @@ function getTopLevelNetworks(Service|Application $resource)
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
if (!$networkExists) {
if (is_string($networkDetails) || is_int($networkDetails)) {
$topLevelNetworks->put($networkDetails, null);
}
@@ -754,7 +760,7 @@ function getTopLevelNetworks(Service|Application $resource)
$definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
return $value == $definedNetwork;
});
if (! $definedNetworkExists) {
if (!$definedNetworkExists) {
foreach ($definedNetwork as $network) {
$topLevelNetworks->put($network, [
'name' => $network,
@@ -795,7 +801,7 @@ function getTopLevelNetworks(Service|Application $resource)
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
if (!$networkExists) {
if (is_string($networkDetails) || is_int($networkDetails)) {
$topLevelNetworks->put($networkDetails, null);
}
@@ -805,7 +811,7 @@ function getTopLevelNetworks(Service|Application $resource)
$definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
return $value == $definedNetwork;
});
if (! $definedNetworkExists) {
if (!$definedNetworkExists) {
foreach ($definedNetwork as $network) {
$topLevelNetworks->put($network, [
'name' => $network,
@@ -941,7 +947,7 @@ function generateEnvValue(string $command, Service|Application|null $service = n
case 'PASSWORD_64':
$generatedValue = Str::password(length: 64, symbols: false);
break;
// This is not base64, it's just a random string
// This is not base64, it's just a random string
case 'BASE64_64':
$generatedValue = Str::random(64);
break;
@@ -952,7 +958,7 @@ function generateEnvValue(string $command, Service|Application|null $service = n
case 'BASE64_32':
$generatedValue = Str::random(32);
break;
// This is base64,
// This is base64,
case 'REALBASE64_64':
$generatedValue = base64_encode(Str::random(64));
break;
@@ -1052,7 +1058,7 @@ function validate_dns_entry(string $fqdn, Server $server)
}
$settings = instanceSettings();
$is_dns_validation_enabled = data_get($settings, 'is_dns_validation_enabled');
if (! $is_dns_validation_enabled) {
if (!$is_dns_validation_enabled) {
return true;
}
$dns_servers = data_get($settings, 'custom_dns_servers');
@@ -1070,7 +1076,7 @@ function validate_dns_entry(string $fqdn, Server $server)
$query = new DNSQuery($dns_server);
$results = $query->query($host, $type);
if ($results === false || $query->hasError()) {
ray('Error: '.$query->getLasterror());
ray('Error: ' . $query->getLasterror());
} else {
foreach ($results as $result) {
if ($result->getType() == $type) {
@@ -1080,7 +1086,7 @@ function validate_dns_entry(string $fqdn, Server $server)
break;
}
if ($result->getData() === $ip) {
ray($host.' has IP address '.$result->getData());
ray($host . ' has IP address ' . $result->getData());
ray($result->getString());
$found_matching_ip = true;
break;
@@ -1128,15 +1134,15 @@ function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId =
$applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']);
$serviceApplications = ServiceApplication::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']);
if ($uuid) {
$applications = $applications->filter(fn ($app) => $app->uuid !== $uuid);
$serviceApplications = $serviceApplications->filter(fn ($app) => $app->uuid !== $uuid);
$applications = $applications->filter(fn($app) => $app->uuid !== $uuid);
$serviceApplications = $serviceApplications->filter(fn($app) => $app->uuid !== $uuid);
}
$domainFound = false;
foreach ($applications as $app) {
if (is_null($app->fqdn)) {
continue;
}
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== '');
foreach ($list_of_domains as $domain) {
if (str($domain)->endsWith('/')) {
$domain = str($domain)->beforeLast('/');
@@ -1155,7 +1161,7 @@ function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId =
if (str($app->fqdn)->isEmpty()) {
continue;
}
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== '');
foreach ($list_of_domains as $domain) {
if (str($domain)->endsWith('/')) {
$domain = str($domain)->beforeLast('/');
@@ -1205,7 +1211,7 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
});
$apps = Application::all();
foreach ($apps as $app) {
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== '');
foreach ($list_of_domains as $domain) {
if (str($domain)->endsWith('/')) {
$domain = str($domain)->beforeLast('/');
@@ -1224,7 +1230,7 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
}
$apps = ServiceApplication::all();
foreach ($apps as $app) {
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== '');
foreach ($list_of_domains as $domain) {
if (str($domain)->endsWith('/')) {
$domain = str($domain)->beforeLast('/');
@@ -1260,7 +1266,7 @@ function parseCommandsByLineForSudo(Collection $commands, Server $server): array
{
$commands = $commands->map(function ($line) {
if (
! str(trim($line))->startsWith([
!str(trim($line))->startsWith([
'cd',
'command',
'echo',
@@ -1281,7 +1287,7 @@ function parseCommandsByLineForSudo(Collection $commands, Server $server): array
$commands = $commands->map(function ($line) use ($server) {
if (Str::startsWith($line, 'sudo mkdir -p')) {
return "$line && sudo chown -R $server->user:$server->user ".Str::after($line, 'sudo mkdir -p').' && sudo chmod -R o-rwx '.Str::after($line, 'sudo mkdir -p');
return "$line && sudo chown -R $server->user:$server->user " . Str::after($line, 'sudo mkdir -p') . ' && sudo chmod -R o-rwx ' . Str::after($line, 'sudo mkdir -p');
}
return $line;
@@ -1309,11 +1315,11 @@ function parseCommandsByLineForSudo(Collection $commands, Server $server): array
}
function parseLineForSudo(string $command, Server $server): string
{
if (! str($command)->startSwith('cd') && ! str($command)->startSwith('command')) {
if (!str($command)->startSwith('cd') && !str($command)->startSwith('command')) {
$command = "sudo $command";
}
if (Str::startsWith($command, 'sudo mkdir -p')) {
$command = "$command && sudo chown -R $server->user:$server->user ".Str::after($command, 'sudo mkdir -p').' && sudo chmod -R o-rwx '.Str::after($command, 'sudo mkdir -p');
$command = "$command && sudo chown -R $server->user:$server->user " . Str::after($command, 'sudo mkdir -p') . ' && sudo chmod -R o-rwx ' . Str::after($command, 'sudo mkdir -p');
}
if (str($command)->contains('$(') || str($command)->contains('`')) {
$command = str($command)->replace('$(', '$(sudo ')->replace('`', '`sudo ')->value();
@@ -1435,7 +1441,7 @@ function parseServiceVolumes($serviceVolumes, $resource, $topLevelVolumes, $pull
$isDirectory = data_get($foundConfig, 'is_directory');
} else {
$isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null);
if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) {
if ((is_null($isDirectory) || !$isDirectory) && is_null($content)) {
// if isDirectory is not set (or false) & content is also not set, we assume it is a directory
ray('setting isDirectory to true');
$isDirectory = true;
@@ -1450,9 +1456,9 @@ function parseServiceVolumes($serviceVolumes, $resource, $topLevelVolumes, $pull
return $volume;
}
if (get_class($resource) === \App\Models\Application::class) {
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
$dir = base_configuration_dir() . '/applications/' . $resource->uuid;
} else {
$dir = base_configuration_dir().'/services/'.$resource->service->uuid;
$dir = base_configuration_dir() . '/services/' . $resource->service->uuid;
}
if ($source->startsWith('.')) {
@@ -1462,9 +1468,9 @@ function parseServiceVolumes($serviceVolumes, $resource, $topLevelVolumes, $pull
$source = $source->replaceFirst('~', $dir);
}
if ($pull_request_id !== 0) {
$source = $source."-pr-$pull_request_id";
$source = $source . "-pr-$pull_request_id";
}
if (! $resource?->settings?->is_preserve_repository_enabled || $foundConfig?->is_based_on_git) {
if (!$resource?->settings?->is_preserve_repository_enabled || $foundConfig?->is_based_on_git) {
LocalFileVolume::updateOrCreate(
[
'mount_path' => $target,
@@ -1593,7 +1599,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if ($serviceLabels->count() > 0) {
$removedLabels = collect([]);
$serviceLabels = $serviceLabels->filter(function ($serviceLabel, $serviceLabelName) use ($removedLabels) {
if (! str($serviceLabel)->contains('=')) {
if (!str($serviceLabel)->contains('=')) {
$removedLabels->put($serviceLabelName, $serviceLabel);
return false;
@@ -1675,7 +1681,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
if (!$networkExists) {
if (is_string($networkDetails) || is_int($networkDetails)) {
$topLevelNetworks->put($networkDetails, null);
}
@@ -1701,12 +1707,12 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$savedService->ports = $collectedPorts->implode(',');
$savedService->save();
if (! $hasHostNetworkMode) {
if (!$hasHostNetworkMode) {
// Add Coolify specific networks
$definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
return $value == $definedNetwork;
});
if (! $definedNetworkExists) {
if (!$definedNetworkExists) {
foreach ($definedNetwork as $network) {
$topLevelNetworks->put($network, [
'name' => $network,
@@ -1918,9 +1924,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$fqdn = "$fqdn$path";
}
if (! $isDatabase) {
if (!$isDatabase) {
if ($savedService->fqdn) {
data_set($savedService, 'fqdn', $savedService->fqdn.','.$fqdn);
data_set($savedService, 'fqdn', $savedService->fqdn . ',' . $fqdn);
} else {
data_set($savedService, 'fqdn', $fqdn);
}
@@ -1935,7 +1941,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
]);
}
// Caddy needs exact port in some cases.
if ($predefinedPort && ! $key->endsWith("_{$predefinedPort}")) {
if ($predefinedPort && !$key->endsWith("_{$predefinedPort}")) {
$fqdns_exploded = str($savedService->fqdn)->explode(',');
if ($fqdns_exploded->count() > 1) {
continue;
@@ -1975,12 +1981,12 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
'service_id' => $resource->id,
])->first();
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
if (! is_null($command)) {
if (!is_null($command)) {
if ($command?->value() === 'FQDN' || $command?->value() === 'URL') {
if (Str::lower($forService) === $serviceName) {
$fqdn = generateFqdn($resource->server, $containerName);
} else {
$fqdn = generateFqdn($resource->server, Str::lower($forService).'-'.$resource->uuid);
$fqdn = generateFqdn($resource->server, Str::lower($forService) . '-' . $resource->uuid);
}
if ($port) {
$fqdn = "$fqdn:$port";
@@ -2010,13 +2016,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
'is_preview' => false,
]);
}
if (! $isDatabase) {
if ($command->value() === 'FQDN' && is_null($savedService->fqdn) && ! $foundEnv) {
if (!$isDatabase) {
if ($command->value() === 'FQDN' && is_null($savedService->fqdn) && !$foundEnv) {
$savedService->fqdn = $fqdn;
$savedService->save();
}
// Caddy needs exact port in some cases.
if ($predefinedPort && ! $key->endsWith("_{$predefinedPort}") && $command?->value() === 'FQDN' && $resource->server->proxyType() === 'CADDY') {
if ($predefinedPort && !$key->endsWith("_{$predefinedPort}") && $command?->value() === 'FQDN' && $resource->server->proxyType() === 'CADDY') {
$fqdns_exploded = str($savedService->fqdn)->explode(',');
if ($fqdns_exploded->count() > 1) {
continue;
@@ -2038,7 +2044,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
} else {
$generatedValue = generateEnvValue($command, $resource);
if (! $foundEnv) {
if (!$foundEnv) {
EnvironmentVariable::create([
'key' => $key,
'value' => $generatedValue,
@@ -2093,7 +2099,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
$defaultLabels = defaultLabels($resource->id, $containerName, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id);
$serviceLabels = $serviceLabels->merge($defaultLabels);
if (! $isDatabase && $fqdns->count() > 0) {
if (!$isDatabase && $fqdns->count() > 0) {
if ($fqdns) {
$shouldGenerateLabelsExactly = $resource->server->settings->generate_exact_labels;
if ($shouldGenerateLabelsExactly) {
@@ -2161,7 +2167,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
data_set($service, 'labels', $serviceLabels->toArray());
data_forget($service, 'is_database');
if (! data_get($service, 'restart')) {
if (!data_get($service, 'restart')) {
data_set($service, 'restart', RESTART_MODE);
}
if (data_get($service, 'restart') === 'no' || data_get($service, 'exclude_from_hc')) {
@@ -2200,21 +2206,21 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$parsedServiceVariables->put('COOLIFY_CONTAINER_NAME', "$serviceName-{$resource->uuid}");
// TODO: move this in a shared function
if (! $parsedServiceVariables->has('COOLIFY_APP_NAME')) {
if (!$parsedServiceVariables->has('COOLIFY_APP_NAME')) {
$parsedServiceVariables->put('COOLIFY_APP_NAME', "\"{$resource->name}\"");
}
if (! $parsedServiceVariables->has('COOLIFY_SERVER_IP')) {
if (!$parsedServiceVariables->has('COOLIFY_SERVER_IP')) {
$parsedServiceVariables->put('COOLIFY_SERVER_IP', "\"{$resource->destination->server->ip}\"");
}
if (! $parsedServiceVariables->has('COOLIFY_ENVIRONMENT_NAME')) {
if (!$parsedServiceVariables->has('COOLIFY_ENVIRONMENT_NAME')) {
$parsedServiceVariables->put('COOLIFY_ENVIRONMENT_NAME', "\"{$resource->environment->name}\"");
}
if (! $parsedServiceVariables->has('COOLIFY_PROJECT_NAME')) {
if (!$parsedServiceVariables->has('COOLIFY_PROJECT_NAME')) {
$parsedServiceVariables->put('COOLIFY_PROJECT_NAME', "\"{$resource->project()->name}\"");
}
$parsedServiceVariables = $parsedServiceVariables->map(function ($value, $key) use ($envs_from_coolify) {
if (! str($value)->startsWith('$')) {
if (!str($value)->startsWith('$')) {
$found_env = $envs_from_coolify->where('key', $key)->first();
if ($found_env) {
return $found_env->value;
@@ -2298,7 +2304,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if ($serviceLabels->count() > 0) {
$removedLabels = collect([]);
$serviceLabels = $serviceLabels->filter(function ($serviceLabel, $serviceLabelName) use ($removedLabels) {
if (! str($serviceLabel)->contains('=')) {
if (!str($serviceLabel)->contains('=')) {
$removedLabels->put($serviceLabelName, $serviceLabel);
return false;
@@ -2318,11 +2324,11 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) {
if (is_string($volume)) {
$volume = str($volume);
if ($volume->contains(':') && ! $volume->startsWith('/')) {
if ($volume->contains(':') && !$volume->startsWith('/')) {
$name = $volume->before(':');
$mount = $volume->after(':');
if ($name->startsWith('.') || $name->startsWith('~')) {
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
$dir = base_configuration_dir() . '/applications/' . $resource->uuid;
if ($name->startsWith('.')) {
$name = $name->replaceFirst('.', $dir);
}
@@ -2330,12 +2336,12 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$name = $name->replaceFirst('~', $dir);
}
if ($pull_request_id !== 0) {
$name = $name."-pr-$pull_request_id";
$name = $name . "-pr-$pull_request_id";
}
$volume = str("$name:$mount");
} else {
if ($pull_request_id !== 0) {
$name = $name."-pr-$pull_request_id";
$name = $name . "-pr-$pull_request_id";
$volume = str("$name:$mount");
if ($topLevelVolumes->has($name)) {
$v = $topLevelVolumes->get($name);
@@ -2374,7 +2380,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$name = $volume->before(':');
$mount = $volume->after(':');
if ($pull_request_id !== 0) {
$name = $name."-pr-$pull_request_id";
$name = $name . "-pr-$pull_request_id";
}
$volume = str("$name:$mount");
}
@@ -2385,7 +2391,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$read_only = data_get($volume, 'read_only');
if ($source && $target) {
if ((str($source)->startsWith('.') || str($source)->startsWith('~'))) {
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
$dir = base_configuration_dir() . '/applications/' . $resource->uuid;
if (str($source, '.')) {
$source = str($source)->replaceFirst('.', $dir);
}
@@ -2393,23 +2399,23 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$source = str($source)->replaceFirst('~', $dir);
}
if ($pull_request_id !== 0) {
$source = $source."-pr-$pull_request_id";
$source = $source . "-pr-$pull_request_id";
}
if ($read_only) {
data_set($volume, 'source', $source.':'.$target.':ro');
data_set($volume, 'source', $source . ':' . $target . ':ro');
} else {
data_set($volume, 'source', $source.':'.$target);
data_set($volume, 'source', $source . ':' . $target);
}
} else {
if ($pull_request_id !== 0) {
$source = $source."-pr-$pull_request_id";
$source = $source . "-pr-$pull_request_id";
}
if ($read_only) {
data_set($volume, 'source', $source.':'.$target.':ro');
data_set($volume, 'source', $source . ':' . $target . ':ro');
} else {
data_set($volume, 'source', $source.':'.$target);
data_set($volume, 'source', $source . ':' . $target);
}
if (! str($source)->startsWith('/')) {
if (!str($source)->startsWith('/')) {
if ($topLevelVolumes->has($source)) {
$v = $topLevelVolumes->get($source);
if (data_get($v, 'driver_opts.type') === 'cifs') {
@@ -2442,11 +2448,11 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) {
if (is_string($volume)) {
$volume = str($volume);
if ($volume->contains(':') && ! $volume->startsWith('/')) {
if ($volume->contains(':') && !$volume->startsWith('/')) {
$name = $volume->before(':');
$mount = $volume->after(':');
if ($name->startsWith('.') || $name->startsWith('~')) {
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
$dir = base_configuration_dir() . '/applications/' . $resource->uuid;
if ($name->startsWith('.')) {
$name = $name->replaceFirst('.', $dir);
}
@@ -2454,13 +2460,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$name = $name->replaceFirst('~', $dir);
}
if ($pull_request_id !== 0) {
$name = $name."-pr-$pull_request_id";
$name = $name . "-pr-$pull_request_id";
}
$volume = str("$name:$mount");
} else {
if ($pull_request_id !== 0) {
$uuid = $resource->uuid;
$name = $uuid."-$name-pr-$pull_request_id";
$name = $uuid . "-$name-pr-$pull_request_id";
$volume = str("$name:$mount");
if ($topLevelVolumes->has($name)) {
$v = $topLevelVolumes->get($name);
@@ -2479,7 +2485,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
} else {
$uuid = $resource->uuid;
$name = str($uuid."-$name");
$name = str($uuid . "-$name");
$volume = str("$name:$mount");
if ($topLevelVolumes->has($name->value())) {
$v = $topLevelVolumes->get($name->value());
@@ -2502,7 +2508,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$name = $volume->before(':');
$mount = $volume->after(':');
if ($pull_request_id !== 0) {
$name = $name."-pr-$pull_request_id";
$name = $name . "-pr-$pull_request_id";
}
$volume = str("$name:$mount");
}
@@ -2514,7 +2520,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if ($source && $target) {
$uuid = $resource->uuid;
if ((str($source)->startsWith('.') || str($source)->startsWith('~') || str($source)->startsWith('/'))) {
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
$dir = base_configuration_dir() . '/applications/' . $resource->uuid;
if (str($source, '.')) {
$source = str($source)->replaceFirst('.', $dir);
}
@@ -2522,22 +2528,22 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$source = str($source)->replaceFirst('~', $dir);
}
if ($read_only) {
data_set($volume, 'source', $source.':'.$target.':ro');
data_set($volume, 'source', $source . ':' . $target . ':ro');
} else {
data_set($volume, 'source', $source.':'.$target);
data_set($volume, 'source', $source . ':' . $target);
}
} else {
if ($pull_request_id === 0) {
$source = $uuid."-$source";
$source = $uuid . "-$source";
} else {
$source = $uuid."-$source-pr-$pull_request_id";
$source = $uuid . "-$source-pr-$pull_request_id";
}
if ($read_only) {
data_set($volume, 'source', $source.':'.$target.':ro');
data_set($volume, 'source', $source . ':' . $target . ':ro');
} else {
data_set($volume, 'source', $source.':'.$target);
data_set($volume, 'source', $source . ':' . $target);
}
if (! str($source)->startsWith('/')) {
if (!str($source)->startsWith('/')) {
if ($topLevelVolumes->has($source)) {
$v = $topLevelVolumes->get($source);
if (data_get($v, 'driver_opts.type') === 'cifs') {
@@ -2570,7 +2576,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if ($pull_request_id !== 0 && count($serviceDependencies) > 0) {
$serviceDependencies = $serviceDependencies->map(function ($dependency) use ($pull_request_id) {
return $dependency."-pr-$pull_request_id";
return $dependency . "-pr-$pull_request_id";
});
data_set($service, 'depends_on', $serviceDependencies->toArray());
}
@@ -2592,7 +2598,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
if (!$networkExists) {
if (is_string($networkDetails) || is_int($networkDetails)) {
$topLevelNetworks->put($networkDetails, null);
}
@@ -2620,7 +2626,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
return $value == $definedNetwork;
});
if (! $definedNetworkExists) {
if (!$definedNetworkExists) {
foreach ($definedNetwork as $network) {
if ($pull_request_id !== 0) {
$topLevelNetworks->put($network, [
@@ -2738,12 +2744,12 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
'application_id' => $resource->id,
])->first();
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
if (! is_null($command)) {
if (!is_null($command)) {
if ($command?->value() === 'FQDN' || $command?->value() === 'URL') {
if (Str::lower($forService) === $serviceName) {
$fqdn = generateFqdn($server, $containerName);
} else {
$fqdn = generateFqdn($server, Str::lower($forService).'-'.$resource->uuid);
$fqdn = generateFqdn($server, Str::lower($forService) . '-' . $resource->uuid);
}
if ($port) {
$fqdn = "$fqdn:$port";
@@ -2764,7 +2770,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
} else {
$generatedValue = generateEnvValue($command);
if (! $foundEnv) {
if (!$foundEnv) {
EnvironmentVariable::create([
'key' => $key,
'value' => $generatedValue,
@@ -2936,7 +2942,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
data_set($service, 'labels', $serviceLabels->toArray());
data_forget($service, 'is_database');
if (! data_get($service, 'restart')) {
if (!data_get($service, 'restart')) {
data_set($service, 'restart', RESTART_MODE);
}
data_set($service, 'container_name', $containerName);
@@ -2947,7 +2953,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
});
if ($pull_request_id !== 0) {
$services->each(function ($service, $serviceName) use ($pull_request_id, $services) {
$services[$serviceName."-pr-$pull_request_id"] = $service;
$services[$serviceName . "-pr-$pull_request_id"] = $service;
data_forget($services, $serviceName);
});
}
@@ -2975,7 +2981,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$uuid = data_get($resource, 'uuid');
$compose = data_get($resource, 'docker_compose_raw');
if (! $compose) {
if (!$compose) {
return collect([]);
}
@@ -3368,29 +3374,29 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$isDirectory = data_get($foundConfig, 'is_directory');
} else {
// if isDirectory is not set (or false) & content is also not set, we assume it is a directory
if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) {
if ((is_null($isDirectory) || !$isDirectory) && is_null($content)) {
$isDirectory = true;
}
}
}
if ($type->value() === 'bind') {
if ($source->value() === '/var/run/docker.sock') {
$volume = $source->value().':'.$target->value();
$volume = $source->value() . ':' . $target->value();
} elseif ($source->value() === '/tmp' || $source->value() === '/tmp/') {
$volume = $source->value().':'.$target->value();
$volume = $source->value() . ':' . $target->value();
} else {
if ((int) $resource->compose_parsing_version >= 4) {
if ($isApplication) {
$mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
$mainDirectory = str(base_configuration_dir() . '/applications/' . $uuid);
} elseif ($isService) {
$mainDirectory = str(base_configuration_dir().'/services/'.$uuid);
$mainDirectory = str(base_configuration_dir() . '/services/' . $uuid);
}
} else {
$mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
$mainDirectory = str(base_configuration_dir() . '/applications/' . $uuid);
}
$source = replaceLocalSource($source, $mainDirectory);
if ($isApplication && $isPullRequest) {
$source = $source."-pr-$pullRequestId";
$source = $source . "-pr-$pullRequestId";
}
LocalFileVolume::updateOrCreate(
[
@@ -3410,12 +3416,12 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
if (isDev()) {
if ((int) $resource->compose_parsing_version >= 4) {
if ($isApplication) {
$source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid);
$source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/' . $uuid);
} elseif ($isService) {
$source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/services/'.$uuid);
$source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/services/' . $uuid);
}
} else {
$source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid);
$source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/' . $uuid);
}
}
$volume = "$source:$target";
@@ -3482,7 +3488,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$depends_on = $newDependsOn;
}
}
if (! $use_network_mode) {
if (!$use_network_mode) {
if ($topLevel->get('networks')?->count() > 0) {
foreach ($topLevel->get('networks') as $networkName => $network) {
if ($networkName === 'default') {
@@ -3495,7 +3501,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$networkExists = $networks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
if (!$networkExists) {
$networks->put($networkName, null);
}
}
@@ -3503,7 +3509,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$baseNetworkExists = $networks->contains(function ($value, $_) use ($baseNetwork) {
return $value == $baseNetwork;
});
if (! $baseNetworkExists) {
if (!$baseNetworkExists) {
foreach ($baseNetwork as $network) {
$topLevel->get('networks')->put($network, [
'name' => $network,
@@ -3535,7 +3541,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$networks_temp = collect();
if (! $use_network_mode) {
if (!$use_network_mode) {
foreach ($networks as $key => $network) {
if (gettype($network) === 'string') {
// networks:
@@ -3566,7 +3572,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$normalEnvironments = $environment->diffKeys($allMagicEnvironments);
$normalEnvironments = $normalEnvironments->filter(function ($value, $key) {
return ! str($value)->startsWith('SERVICE_');
return !str($value)->startsWith('SERVICE_');
});
foreach ($normalEnvironments as $key => $value) {
@@ -3586,7 +3592,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
continue;
}
if (! $value->startsWith('$')) {
if (!$value->startsWith('$')) {
continue;
}
if ($key->value() === $parsedValue->value()) {
@@ -3717,7 +3723,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$defaultLabels = defaultLabels($resource->id, $containerName, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id);
}
// Add COOLIFY_FQDN & COOLIFY_URL to environment
if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) {
if (!$isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) {
$coolifyEnvironments->put('COOLIFY_URL', $fqdns->implode(','));
$urls = $fqdns->map(function ($fqdn) {
@@ -3729,7 +3735,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
if ($environment->count() > 0) {
$environment = $environment->filter(function ($value, $key) {
return ! str($key)->startsWith('SERVICE_FQDN_');
return !str($key)->startsWith('SERVICE_FQDN_');
})->map(function ($value, $key) use ($resource) {
// if value is empty, set it to null so if you set the environment variable in the .env file (Coolify's UI), it will used
if (str($value)->isEmpty()) {
@@ -3756,7 +3762,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
});
}
}
if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) {
if (!$isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) {
if ($isApplication) {
$shouldGenerateLabelsExactly = $resource->destination->server->settings->generate_exact_labels;
$uuid = $resource->uuid;
@@ -3849,7 +3855,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
'restart' => $restart->value(),
'labels' => $serviceLabels,
]);
if (! $use_network_mode) {
if (!$use_network_mode) {
$payload['networks'] = $networks_temp;
}
if ($ports->count() > 0) {
@@ -3909,7 +3915,7 @@ function isAssociativeArray($array)
$array = $array->toArray();
}
if (! is_array($array)) {
if (!is_array($array)) {
throw new \InvalidArgumentException('Input must be an array or a Collection.');
}
@@ -4022,7 +4028,7 @@ function instanceSettings()
function loadConfigFromGit(string $repository, string $branch, string $base_directory, int $server_id, int $team_id)
{
$server = Server::find($server_id)->where('team_id', $team_id)->first();
if (! $server) {
if (!$server) {
return;
}
$uuid = new Cuid2;
@@ -4049,7 +4055,7 @@ function loadConfigFromGit(string $repository, string $branch, string $base_dire
function loggy($message = null, array $context = [])
{
if (! isDev()) {
if (!isDev()) {
return;
}
if (function_exists('ray') && config('app.debug')) {
@@ -4084,7 +4090,7 @@ function isEmailRateLimited(string $limiterKey, int $decaySeconds = 3600, ?calla
$limiterKey,
$maxAttempts = 0,
function () use (&$rateLimited, &$limiterKey, $callbackOnSuccess) {
isDev() && loggy('Rate limit not reached for '.$limiterKey);
isDev() && loggy('Rate limit not reached for ' . $limiterKey);
$rateLimited = false;
if ($callbackOnSuccess) {
@@ -4093,8 +4099,8 @@ function isEmailRateLimited(string $limiterKey, int $decaySeconds = 3600, ?calla
},
$decaySeconds,
);
if (! $executed) {
isDev() && loggy('Rate limit reached for '.$limiterKey.'. Rate limiter will be disabled for '.$decaySeconds.' seconds.');
if (!$executed) {
isDev() && loggy('Rate limit reached for ' . $limiterKey . '. Rate limiter will be disabled for ' . $decaySeconds . ' seconds.');
$rateLimited = true;
}

View File

@@ -0,0 +1,38 @@
<?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('slack_enabled')->default(false);
$table->string('slack_webhook_url')->nullable();
$table->boolean('slack_notifications_test')->default(true);
$table->boolean('slack_notifications_deployments')->default(true);
$table->boolean('slack_notifications_status_changes')->default(true);
$table->boolean('slack_notifications_database_backups')->default(true);
$table->boolean('slack_notifications_scheduled_tasks')->default(true);
$table->boolean('slack_notifications_server_disk_usage')->default(true);
});
}
public function down(): void
{
Schema::table('teams', function (Blueprint $table) {
$table->dropColumn([
'slack_enabled',
'slack_webhook_url',
'slack_notifications_test',
'slack_notifications_deployments',
'slack_notifications_status_changes',
'slack_notifications_database_backups',
'slack_notifications_scheduled_tasks',
'slack_notifications_server_disk_usage',
]);
});
}
};

View File

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

View File

@@ -0,0 +1,43 @@
<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>Slack</h2>
<x-forms.button type="submit">
Save
</x-forms.button>
@if ($slackEnabled)
<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="instantSaveSlackEnabled" id="slackEnabled" label="Enabled" />
</div>
<x-forms.input type="password"
helper="Generate a webhook in Slack.<br>Example: https://hooks.slack.com/services/...." required
id="slackWebhookUrl" label="Webhook" />
</form>
@if ($slackEnabled)
<h2 class="mt-4">Subscribe to events</h2>
<div class="w-64">
@if (isDev())
<x-forms.checkbox instantSave="saveModel" id="slackNotificationsTest" label="Test" />
@endif
<x-forms.checkbox instantSave="saveModel" id="slackNotificationsStatusChanges"
label="Container Status Changes" />
<x-forms.checkbox instantSave="saveModel" id="slackNotificationsDeployments"
label="Application Deployments" />
<x-forms.checkbox instantSave="saveModel" id="slackNotificationsDatabaseBackups" label="Backup Status" />
<x-forms.checkbox instantSave="saveModel" id="slackNotificationsScheduledTasks"
label="Scheduled Tasks Status" />
<x-forms.checkbox instantSave="saveModel" id="slackNotificationsServerDiskUsage"
label="Server Disk Usage" />
</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\Slack as NotificationSlack;
use App\Livewire\Profile\Index as ProfileIndex;
use App\Livewire\Project\Application\Configuration as ApplicationConfiguration;
use App\Livewire\Project\Application\Deployment\Index as DeploymentIndex;
@@ -132,6 +133,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('/slack', NotificationSlack::class)->name('notifications.slack');
});
Route::prefix('storages')->group(function () {
@@ -285,7 +287,7 @@ Route::middleware(['auth'])->group(function () {
'privateKey' => $privateKeyLocation,
'root' => '/',
]);
if (! $disk->exists($filename)) {
if (!$disk->exists($filename)) {
return response()->json(['message' => 'Backup not found.'], 404);
}
@@ -297,7 +299,7 @@ Route::middleware(['auth'])->group(function () {
if ($stream === false || is_null($stream)) {
abort(500, 'Failed to open stream for the requested file.');
}
while (! feof($stream)) {
while (!feof($stream)) {
echo fread($stream, 2048);
flush();
}
@@ -305,7 +307,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);