Merge pull request #33 from peaklabs-dev/next

Next into notifications
This commit is contained in:
🏔️ Peak
2024-12-06 14:51:15 +01:00
committed by GitHub
51 changed files with 1000 additions and 282 deletions

View File

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

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

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

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

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

View File

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

View File

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

View File

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

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

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

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

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

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',
]);
});
}
};

11
public/svgs/heimdall.svg Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
public/svgs/kuzzle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
public/svgs/pairdrop.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

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

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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