@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Actions\Proxy;
|
||||
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Events\ProxyStarted;
|
||||
use App\Models\Server;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
@@ -37,11 +38,16 @@ class StartProxy
|
||||
"echo 'Successfully started coolify-proxy.'",
|
||||
]);
|
||||
} else {
|
||||
$caddfile = 'import /dynamic/*.caddy';
|
||||
if (isDev()) {
|
||||
if ($proxyType === ProxyTypes::CADDY->value) {
|
||||
$proxy_path = '/data/coolify/proxy/caddy';
|
||||
}
|
||||
}
|
||||
$caddyfile = 'import /dynamic/*.caddy';
|
||||
$commands = $commands->merge([
|
||||
"mkdir -p $proxy_path/dynamic",
|
||||
"cd $proxy_path",
|
||||
"echo '$caddfile' > $proxy_path/dynamic/Caddyfile",
|
||||
"echo '$caddyfile' > $proxy_path/dynamic/Caddyfile",
|
||||
"echo 'Creating required Docker Compose file.'",
|
||||
"echo 'Pulling docker image.'",
|
||||
'docker compose pull',
|
||||
|
||||
59
app/Jobs/SendMessageToSlackJob.php
Normal file
59
app/Jobs/SendMessageToSlackJob.php
Normal 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,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ class ProxyStartedNotification
|
||||
public function handle(ProxyStarted $event): void
|
||||
{
|
||||
$this->server = data_get($event, 'data');
|
||||
$this->server->setupDefault404Redirect();
|
||||
$this->server->setupDefaultRedirect();
|
||||
$this->server->setupDynamicProxyConfiguration();
|
||||
$this->server->proxy->force_stop = false;
|
||||
$this->server->save();
|
||||
|
||||
130
app/Livewire/Notifications/Slack.php
Normal file
130
app/Livewire/Notifications/Slack.php
Normal 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');
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@ class Proxy extends Component
|
||||
|
||||
public $proxy_settings = null;
|
||||
|
||||
public bool $redirect_enabled = true;
|
||||
|
||||
public ?string $redirect_url = null;
|
||||
|
||||
protected $listeners = ['proxyStatusUpdated', 'saveConfiguration' => 'submit'];
|
||||
@@ -26,6 +28,7 @@ class Proxy extends Component
|
||||
public function mount()
|
||||
{
|
||||
$this->selectedProxy = $this->server->proxyType();
|
||||
$this->redirect_enabled = data_get($this->server, 'proxy.redirect_enabled', true);
|
||||
$this->redirect_url = data_get($this->server, 'proxy.redirect_url');
|
||||
}
|
||||
|
||||
@@ -38,7 +41,7 @@ class Proxy extends Component
|
||||
{
|
||||
$this->server->proxy = null;
|
||||
$this->server->save();
|
||||
$this->dispatch('proxyChanged');
|
||||
$this->dispatch('reloadWindow');
|
||||
}
|
||||
|
||||
public function selectProxy($proxy_type)
|
||||
@@ -46,7 +49,7 @@ class Proxy extends Component
|
||||
try {
|
||||
$this->server->changeProxy($proxy_type, async: false);
|
||||
$this->selectedProxy = $this->server->proxy->type;
|
||||
$this->dispatch('proxyStatusUpdated');
|
||||
$this->dispatch('reloadWindow');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
@@ -63,13 +66,25 @@ class Proxy extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSaveRedirect()
|
||||
{
|
||||
try {
|
||||
$this->server->proxy->redirect_enabled = $this->redirect_enabled;
|
||||
$this->server->save();
|
||||
$this->server->setupDefaultRedirect();
|
||||
$this->dispatch('success', 'Proxy configuration saved.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
SaveConfiguration::run($this->server, $this->proxy_settings);
|
||||
$this->server->proxy->redirect_url = $this->redirect_url;
|
||||
$this->server->save();
|
||||
$this->server->setupDefault404Redirect();
|
||||
$this->server->setupDefaultRedirect();
|
||||
$this->dispatch('success', 'Proxy configuration saved.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
|
||||
@@ -65,7 +65,7 @@ class Deploy extends Component
|
||||
public function restart()
|
||||
{
|
||||
try {
|
||||
$this->stop(forceStop: false);
|
||||
$this->stop();
|
||||
$this->dispatch('checkProxy');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
@@ -105,6 +105,7 @@ class Deploy extends Component
|
||||
|
||||
$startTime = Carbon::now()->getTimestamp();
|
||||
while ($process->running()) {
|
||||
ray('running');
|
||||
if (Carbon::now()->getTimestamp() - $startTime >= $timeout) {
|
||||
$this->forceStopContainer($containerName);
|
||||
break;
|
||||
|
||||
@@ -1321,7 +1321,21 @@ class Application extends BaseModel
|
||||
if (! $gitRemoteStatus['is_accessible']) {
|
||||
throw new \RuntimeException("Failed to read Git source:\n\n{$gitRemoteStatus['error']}");
|
||||
}
|
||||
$getGitVersion = instant_remote_process(['git --version'], $this->destination->server, false);
|
||||
$gitVersion = str($getGitVersion)->explode(' ')->last();
|
||||
|
||||
if (version_compare($gitVersion, '2.35.1', '<')) {
|
||||
$fileList = $fileList->map(function ($file) {
|
||||
$parts = explode('/', trim($file, '.'));
|
||||
$paths = collect();
|
||||
$currentPath = '';
|
||||
foreach ($parts as $part) {
|
||||
$currentPath .= ($currentPath ? '/' : '').$part;
|
||||
$paths->push($currentPath);
|
||||
}
|
||||
|
||||
return $paths;
|
||||
})->flatten()->unique()->values();
|
||||
$commands = collect([
|
||||
"rm -rf /tmp/{$uuid}",
|
||||
"mkdir -p /tmp/{$uuid}",
|
||||
@@ -1332,6 +1346,18 @@ class Application extends BaseModel
|
||||
'git read-tree -mu HEAD',
|
||||
"cat .$workdir$composeFile",
|
||||
]);
|
||||
} else {
|
||||
$commands = collect([
|
||||
"rm -rf /tmp/{$uuid}",
|
||||
"mkdir -p /tmp/{$uuid}",
|
||||
"cd /tmp/{$uuid}",
|
||||
$cloneCommand,
|
||||
'git sparse-checkout init --cone',
|
||||
"git sparse-checkout set {$fileList->implode(' ')}",
|
||||
'git read-tree -mu HEAD',
|
||||
"cat .$workdir$composeFile",
|
||||
]);
|
||||
}
|
||||
try {
|
||||
$composeFileContent = instant_remote_process($commands, $this->destination->server);
|
||||
} catch (\Exception $e) {
|
||||
|
||||
@@ -105,6 +105,14 @@ class Server extends BaseModel
|
||||
]);
|
||||
}
|
||||
}
|
||||
if (! isset($server->proxy->redirect_enabled)) {
|
||||
$server->proxy->redirect_enabled = true;
|
||||
}
|
||||
});
|
||||
static::retrieved(function ($server) {
|
||||
if (! isset($server->proxy->redirect_enabled)) {
|
||||
$server->proxy->redirect_enabled = true;
|
||||
}
|
||||
});
|
||||
|
||||
static::forceDeleting(function ($server) {
|
||||
@@ -184,42 +192,46 @@ class Server extends BaseModel
|
||||
return $this->proxyType() && $this->proxyType() !== 'NONE' && $this->isFunctional() && ! $this->isSwarmWorker() && ! $this->settings->is_build_server;
|
||||
}
|
||||
|
||||
public function setupDefault404Redirect()
|
||||
public function setupDefaultRedirect()
|
||||
{
|
||||
$banner =
|
||||
"# This file is generated by Coolify, do not edit it manually.\n".
|
||||
"# Disable the default redirect to customize (only if you know what are you doing).\n\n";
|
||||
$dynamic_conf_path = $this->proxyPath().'/dynamic';
|
||||
$proxy_type = $this->proxyType();
|
||||
$redirect_enabled = $this->proxy->redirect_enabled ?? true;
|
||||
$redirect_url = $this->proxy->redirect_url;
|
||||
if ($proxy_type === ProxyTypes::TRAEFIK->value) {
|
||||
$default_redirect_file = "$dynamic_conf_path/default_redirect_404.yaml";
|
||||
} elseif ($proxy_type === ProxyTypes::CADDY->value) {
|
||||
$default_redirect_file = "$dynamic_conf_path/default_redirect_404.caddy";
|
||||
}
|
||||
if (empty($redirect_url)) {
|
||||
if (isDev()) {
|
||||
if ($proxy_type === ProxyTypes::CADDY->value) {
|
||||
$conf = ':80, :443 {
|
||||
respond 404
|
||||
}';
|
||||
$conf =
|
||||
"# This file is automatically generated by Coolify.\n".
|
||||
"# Do not edit it manually (only if you know what are you doing).\n\n".
|
||||
$conf;
|
||||
$base64 = base64_encode($conf);
|
||||
instant_remote_process([
|
||||
"mkdir -p $dynamic_conf_path",
|
||||
"echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null",
|
||||
], $this);
|
||||
$this->reloadCaddy();
|
||||
|
||||
return;
|
||||
$dynamic_conf_path = '/data/coolify/proxy/caddy/dynamic';
|
||||
}
|
||||
instant_remote_process([
|
||||
"mkdir -p $dynamic_conf_path",
|
||||
"rm -f $default_redirect_file",
|
||||
], $this);
|
||||
|
||||
return;
|
||||
}
|
||||
if ($proxy_type === ProxyTypes::TRAEFIK->value) {
|
||||
$default_redirect_file = "$dynamic_conf_path/default_redirect_503.yaml";
|
||||
} elseif ($proxy_type === ProxyTypes::CADDY->value) {
|
||||
$default_redirect_file = "$dynamic_conf_path/default_redirect_503.caddy";
|
||||
}
|
||||
|
||||
instant_remote_process([
|
||||
"mkdir -p $dynamic_conf_path",
|
||||
"rm -f $dynamic_conf_path/default_redirect_404.yaml",
|
||||
"rm -f $dynamic_conf_path/default_redirect_404.caddy",
|
||||
], $this);
|
||||
|
||||
if ($redirect_enabled === false) {
|
||||
instant_remote_process(["rm -f $default_redirect_file"], $this);
|
||||
} else {
|
||||
if ($proxy_type === ProxyTypes::CADDY->value) {
|
||||
if (filled($redirect_url)) {
|
||||
$conf = ":80, :443 {
|
||||
redir $redirect_url
|
||||
}";
|
||||
} else {
|
||||
$conf = ':80, :443 {
|
||||
respond 503
|
||||
}';
|
||||
}
|
||||
} elseif ($proxy_type === ProxyTypes::TRAEFIK->value) {
|
||||
$dynamic_conf = [
|
||||
'http' => [
|
||||
'routers' => [
|
||||
@@ -229,28 +241,31 @@ respond 404
|
||||
1 => 'https',
|
||||
],
|
||||
'service' => 'noop',
|
||||
'rule' => 'HostRegexp(`.+`)',
|
||||
'rule' => 'PathPrefix(`/`)',
|
||||
'tls' => [
|
||||
'certResolver' => 'letsencrypt',
|
||||
],
|
||||
'priority' => 1,
|
||||
'middlewares' => [
|
||||
0 => 'redirect-regexp',
|
||||
],
|
||||
'priority' => -1000,
|
||||
],
|
||||
],
|
||||
'services' => [
|
||||
'noop' => [
|
||||
'loadBalancer' => [
|
||||
'servers' => [
|
||||
0 => [
|
||||
'servers' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
if (filled($redirect_url)) {
|
||||
$dynamic_conf['http']['routers']['catchall']['middlewares'] = [
|
||||
0 => 'redirect-regexp',
|
||||
];
|
||||
|
||||
$dynamic_conf['http']['services']['noop']['loadBalancer']['servers'][0] = [
|
||||
'url' => '',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'middlewares' => [
|
||||
];
|
||||
$dynamic_conf['http']['middlewares'] = [
|
||||
'redirect-regexp' => [
|
||||
'redirectRegex' => [
|
||||
'regex' => '(.*)',
|
||||
@@ -258,31 +273,16 @@ respond 404
|
||||
'permanent' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
$conf = Yaml::dump($dynamic_conf, 12, 2);
|
||||
$conf =
|
||||
"# This file is automatically generated by Coolify.\n".
|
||||
"# Do not edit it manually (only if you know what are you doing).\n\n".
|
||||
$conf;
|
||||
|
||||
$base64 = base64_encode($conf);
|
||||
} elseif ($proxy_type === ProxyTypes::CADDY->value) {
|
||||
$conf = ":80, :443 {
|
||||
redir $redirect_url
|
||||
}";
|
||||
$conf =
|
||||
"# This file is automatically generated by Coolify.\n".
|
||||
"# Do not edit it manually (only if you know what are you doing).\n\n".
|
||||
$conf;
|
||||
$base64 = base64_encode($conf);
|
||||
}
|
||||
|
||||
$conf = Yaml::dump($dynamic_conf, 12, 2);
|
||||
}
|
||||
$conf = $banner.$conf;
|
||||
$base64 = base64_encode($conf);
|
||||
instant_remote_process([
|
||||
"mkdir -p $dynamic_conf_path",
|
||||
"echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null",
|
||||
], $this);
|
||||
}
|
||||
|
||||
if ($proxy_type === 'CADDY') {
|
||||
$this->reloadCaddy();
|
||||
@@ -612,6 +612,7 @@ $schema://$host {
|
||||
$memory = json_decode($memory, true);
|
||||
$parsedCollection = collect($memory)->map(function ($metric) {
|
||||
$usedPercent = $metric['usedPercent'] ?? 0.0;
|
||||
|
||||
return [(int) $metric['time'], (float) $usedPercent];
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
8
app/Notifications/Channels/SendsSlack.php
Normal file
8
app/Notifications/Channels/SendsSlack.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications\Channels;
|
||||
|
||||
interface SendsSlack
|
||||
{
|
||||
public function routeNotificationForSlack();
|
||||
}
|
||||
22
app/Notifications/Channels/SlackChannel.php
Normal file
22
app/Notifications/Channels/SlackChannel.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
33
app/Notifications/Dto/SlackMessage.php
Normal file
33
app/Notifications/Dto/SlackMessage.php
Normal 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';
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,13 +173,12 @@ function generate_default_proxy_configuration(Server $server)
|
||||
],
|
||||
'volumes' => [
|
||||
'/var/run/docker.sock:/var/run/docker.sock:ro',
|
||||
"{$proxy_path}:/traefik",
|
||||
|
||||
],
|
||||
'command' => [
|
||||
'--ping=true',
|
||||
'--ping.entrypoint=http',
|
||||
'--api.dashboard=true',
|
||||
'--api.insecure=false',
|
||||
'--entrypoints.http.address=:80',
|
||||
'--entrypoints.https.address=:443',
|
||||
'--entrypoints.http.http.encodequerysemicolons=true',
|
||||
@@ -187,21 +186,26 @@ function generate_default_proxy_configuration(Server $server)
|
||||
'--entrypoints.https.http.encodequerysemicolons=true',
|
||||
'--entryPoints.https.http2.maxConcurrentStreams=50',
|
||||
'--entrypoints.https.http3',
|
||||
'--providers.docker.exposedbydefault=false',
|
||||
'--providers.file.directory=/traefik/dynamic/',
|
||||
'--providers.docker.exposedbydefault=false',
|
||||
'--providers.file.watch=true',
|
||||
'--certificatesresolvers.letsencrypt.acme.httpchallenge=true',
|
||||
'--certificatesresolvers.letsencrypt.acme.storage=/traefik/acme.json',
|
||||
'--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http',
|
||||
'--certificatesresolvers.letsencrypt.acme.storage=/traefik/acme.json',
|
||||
],
|
||||
'labels' => $labels,
|
||||
],
|
||||
],
|
||||
];
|
||||
if (isDev()) {
|
||||
// $config['services']['traefik']['command'][] = "--log.level=debug";
|
||||
$config['services']['traefik']['command'][] = '--api.insecure=true';
|
||||
$config['services']['traefik']['command'][] = '--log.level=debug';
|
||||
$config['services']['traefik']['command'][] = '--accesslog.filepath=/traefik/access.log';
|
||||
$config['services']['traefik']['command'][] = '--accesslog.bufferingsize=100';
|
||||
$config['services']['traefik']['volumes'][] = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/:/traefik';
|
||||
} else {
|
||||
$config['services']['traefik']['command'][] = '--api.insecure=false';
|
||||
$config['services']['traefik']['volumes'][] = "{$proxy_path}:/traefik";
|
||||
}
|
||||
if ($server->isSwarm()) {
|
||||
data_forget($config, 'services.traefik.container_name');
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
11
public/svgs/heimdall.svg
Normal file
11
public/svgs/heimdall.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="346" height="434" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<g>
|
||||
<title>background</title>
|
||||
<rect fill="none" id="canvas_background" height="436" width="348" y="-1" x="-1"/>
|
||||
</g>
|
||||
<g>
|
||||
<title>Layer 1</title>
|
||||
<path id="svg_1" d="m221.227,397.119l0,-24.919l8.552,-26.577c59.868,-59.323 106.907,-114.847 115.221,-131.932l-10.215,3.56s9.5,-11.864 8.552,-18.51c-7.6,-116.273 -73.17,-176.862 -91.226,-191.732c26.607,31.955 19.326,42.4 19.326,42.4c-11.723,-21.833 -35.797,-43.032 -40.549,-48.409l-5.7,33.853c22.806,13.289 16.79,20.882 16.79,20.882c-5.387,-7.594 -19.957,-14.235 -19.957,-14.235l-1.586,9.491c6.973,3.165 38.327,22.149 38.565,70.872c0,59.323 -33.735,74.985 -39.674,77.357c-19.718,-11.865 -41.1,-11.627 -46.326,-12.1c-5.226,0.474 -26.608,0.236 -46.324,12.1c-5.941,-2.376 -39.676,-18.038 -39.676,-77.361c0.238,-48.723 31.6,-67.707 38.564,-70.872l-1.581,-9.487s-14.574,6.644 -19.955,14.238c0,0 -6.022,-7.593 16.786,-20.882l-5.702,-33.856c-4.752,5.378 -28.824,26.577 -40.543,48.407c0,0 -7.288,-10.443 19.32,-42.4c-18.054,14.87 -83.625,75.459 -91.226,191.732c-0.95,6.646 8.554,18.51 8.554,18.51l-10.217,-3.558c8.316,17.085 55.355,72.612 115.221,131.935l8.554,26.577l0,24.916l-7.6,-26.577l-17.345,-28.475l0,28.95c7.127,20.169 34.921,60.983 34.921,60.983l0,-84.475l-106.431,-116.037s66.52,54.1 85.526,73.56l9.978,-10.2c-12.355,-8.78 -66.045,-62.646 -70.8,-110.1c40.388,85.426 79.111,96.1 107.62,129.325l0,8.127l24.707,0l0,-8.131c28.508,-33.221 67.233,-43.9 107.619,-129.325c-4.75,47.459 -58.442,101.325 -70.8,110.1l9.976,10.2c19.006,-19.458 85.526,-73.56 85.526,-73.56l-106.423,116.041l0,84.475s27.8,-40.815 34.923,-60.984l0,-28.95l-17.343,28.475l-7.602,26.578zm-48.227,-102.036c-17.818,-11.865 -63.668,-39.629 -82.672,-83.053a314.117,314.117 0 0 0 34.447,34.882s-1.189,-30.61 1.186,-37.255c4.276,36.781 13.068,52.442 47.039,71.9c33.973,-19.458 42.763,-35.119 47.039,-71.9c2.375,6.645 1.188,37.255 1.188,37.255a314.117,314.117 0 0 0 34.447,-34.882c-19.006,43.424 -64.856,71.188 -82.674,83.053z" class="cls-1"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
BIN
public/svgs/invoiceninja.png
Normal file
BIN
public/svgs/invoiceninja.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
BIN
public/svgs/kuzzle.png
Normal file
BIN
public/svgs/kuzzle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
public/svgs/pairdrop.png
Normal file
BIN
public/svgs/pairdrop.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
3
public/svgs/penpot.svg
Normal file
3
public/svgs/penpot.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 6.4 KiB |
BIN
public/svgs/whoogle.png
Normal file
BIN
public/svgs/whoogle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
@@ -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>
|
||||
43
resources/views/livewire/notifications/slack.blade.php
Normal file
43
resources/views/livewire/notifications/slack.blade.php
Normal 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>
|
||||
@@ -27,6 +27,13 @@
|
||||
helper="If set, all resources will only have docker container labels for {{ str($server->proxyType())->title() }}.<br>For applications, labels needs to be regenerated manually. <br>Resources needs to be restarted."
|
||||
id="server.settings.generate_exact_labels"
|
||||
label="Generate labels only for {{ str($server->proxyType())->title() }}" instantSave />
|
||||
<x-forms.checkbox instantSave="instantSaveRedirect" id="redirect_enabled"
|
||||
label="Override default request handler"
|
||||
helper="Requests to unknown hosts or stopped services will recieve a 503 response or be redirected to the URL you set below (need to enable this first)." />
|
||||
@if ($redirect_enabled)
|
||||
<x-forms.input placeholder="https://app.coolify.io" id="redirect_url"
|
||||
label="Redirect to (optional)" />
|
||||
@endif
|
||||
</div>
|
||||
@if ($server->proxyType() === ProxyTypes::TRAEFIK->value)
|
||||
<h4>Traefik</h4>
|
||||
@@ -40,8 +47,6 @@
|
||||
configurations.
|
||||
</div>
|
||||
@endif
|
||||
<x-forms.input placeholder="https://app.coolify.io" id="redirect_url" label="Default Redirect 404"
|
||||
helper="All urls that has no service available will be redirected to this domain." />
|
||||
<div wire:loading wire:target="loadProxyConfiguration" class="pt-4">
|
||||
<x-loading text="Loading proxy configuration..." />
|
||||
</div>
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
@if (str_replace('|', '.', $fileName) === 'coolify.yaml' ||
|
||||
str_replace('|', '.', $fileName) === 'Caddyfile' ||
|
||||
str_replace('|', '.', $fileName) === 'coolify.caddy' ||
|
||||
str_replace('|', '.', $fileName) === 'default_redirect_404.caddy')
|
||||
str_replace('|', '.', $fileName) === 'default_redirect_503.yaml' ||
|
||||
str_replace('|', '.', $fileName) === 'default_redirect_503.caddy')
|
||||
<div>
|
||||
<h3 class="dark:text-white">File: {{ str_replace('|', '.', $fileName) }}</h3>
|
||||
</div>
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -30,7 +30,7 @@ WARNING_SPACE=false
|
||||
|
||||
if [ "$TOTAL_SPACE" -lt "$REQUIRED_TOTAL_SPACE" ]; then
|
||||
WARNING_SPACE=true
|
||||
cat << 'EOF'
|
||||
cat << EOF
|
||||
WARNING: Insufficient total disk space!
|
||||
|
||||
Total disk space: ${TOTAL_SPACE}GB
|
||||
@@ -41,7 +41,7 @@ EOF
|
||||
fi
|
||||
|
||||
if [ "$AVAILABLE_SPACE" -lt "$REQUIRED_AVAILABLE_SPACE" ]; then
|
||||
cat << 'EOF'
|
||||
cat << EOF
|
||||
WARNING: Insufficient available disk space!
|
||||
|
||||
Available disk space: ${AVAILABLE_SPACE}GB
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# documentation: https://github.com/linuxserver/Heimdall
|
||||
# slogan: Heimdall is a dashboard for managing and organizing your server applications.
|
||||
# tags: dashboard, server, applications, interface
|
||||
# logo: svgs/heimdall.svg
|
||||
|
||||
services:
|
||||
heimdall:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# documentation: https://invoiceninja.github.io/selfhost.html
|
||||
# slogan: The leading open-source invoicing platform
|
||||
# tags: invoicing, billing, accounting, finance, self-hosted
|
||||
# logo: svgs/invoiceninja.png
|
||||
# port: 9000
|
||||
|
||||
services:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# documentation: https://kuzzle.io
|
||||
# slogan: Kuzzle is a generic backend offering the basic building blocks common to every application.
|
||||
# tags: backend, api, realtime, websocket, mqtt, rest, sdk, iot, geofencing, low-code
|
||||
# logo: svgs/kuzzle.png
|
||||
# port: 7512
|
||||
|
||||
services:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# documentation: https://pairdrop.net/
|
||||
# slogan: Pairdrop is a self-hosted file sharing and collaboration platform, offering secure file sharing and collaboration capabilities for efficient teamwork.
|
||||
# tags: file, sharing, collaboration, teamwork
|
||||
# logo: svgs/pairdrop.png
|
||||
# port: 3000
|
||||
|
||||
services:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# documentation: https://help.penpot.app/technical-guide/getting-started/#install-with-docker
|
||||
# slogan: Penpot is the first Open Source design and prototyping platform for product teams.
|
||||
# tags: penpot,design,prototyping,figma,open,source
|
||||
# logo: svgs/penpot.svg
|
||||
|
||||
services:
|
||||
frontend:
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
# port: 3000
|
||||
|
||||
x-common-env: &common-env
|
||||
PORT: 3030
|
||||
REMIX_APP_PORT: 3000
|
||||
NODE_ENV: production
|
||||
RUNTIME_PLATFORM: docker-compose
|
||||
@@ -118,7 +117,7 @@ services:
|
||||
environment:
|
||||
<<: *common-env
|
||||
PLATFORM_HOST: trigger
|
||||
PLATFORM_WS_PORT: 3030
|
||||
PLATFORM_WS_PORT: 3000
|
||||
SECURE_CONNECTION: "false"
|
||||
PLATFORM_SECRET: $PROVIDER_SECRET
|
||||
coordinator:
|
||||
@@ -133,7 +132,7 @@ services:
|
||||
environment:
|
||||
<<: *common-env
|
||||
PLATFORM_HOST: trigger
|
||||
PLATFORM_WS_PORT: 3030
|
||||
PLATFORM_WS_PORT: 3000
|
||||
SECURE_CONNECTION: "false"
|
||||
PLATFORM_SECRET: $COORDINATOR_SECRET
|
||||
healthcheck:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# documentation: https://github.com/benbusby/whoogle-search?tab=readme-ov-file
|
||||
# slogan: Whoogle is a self-hosted, privacy-focused search engine front-end for accessing Google search results without tracking and data collection.
|
||||
# tags: privacy, search engine
|
||||
# logo: svgs/whoogle.png
|
||||
# port: 5000
|
||||
|
||||
services:
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user