diff --git a/.dockerignore b/.dockerignore
index 0adca0b32..6f3c903f0 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -3,7 +3,6 @@
/public/build
/public/hot
/public/storage
-/storage/*.key
/vendor
.env
.env.backup
@@ -25,3 +24,15 @@ yarn-error.log
.ignition.json
.env.dusk.local
docker/coolify-realtime/node_modules
+
+/storage/*.key
+/storage/app/backups
+/storage/app/ssh/keys
+/storage/app/ssh/mux
+/storage/app/tmp
+/storage/app/debugbar
+/storage/logs
+/storage/pail
+
+
+
diff --git a/.github/workflows/coolify-production-build.yml b/.github/workflows/coolify-production-build.yml
index 5271143ec..d7244fc84 100644
--- a/.github/workflows/coolify-production-build.yml
+++ b/.github/workflows/coolify-production-build.yml
@@ -47,7 +47,7 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
- file: docker/prod/Dockerfile
+ file: docker/production/Dockerfile
platforms: linux/amd64
push: true
tags: |
@@ -82,7 +82,7 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
- file: docker/prod/Dockerfile
+ file: docker/production/Dockerfile
platforms: linux/aarch64
push: true
tags: |
diff --git a/.github/workflows/coolify-staging-build.yml b/.github/workflows/coolify-staging-build.yml
index 2c57a36a3..bcb65ecbf 100644
--- a/.github/workflows/coolify-staging-build.yml
+++ b/.github/workflows/coolify-staging-build.yml
@@ -42,7 +42,7 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
- file: docker/prod/Dockerfile
+ file: docker/production/Dockerfile
platforms: linux/amd64
push: true
tags: |
@@ -75,7 +75,7 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
- file: docker/prod/Dockerfile
+ file: docker/production/Dockerfile
platforms: linux/aarch64
push: true
tags: |
diff --git a/README.md b/README.md
index dac48d127..56edffd31 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,8 @@ # Donations
### Special Sponsors
-
+
+
* [CCCareers](https://cccareers.org/) - A career development platform connecting coding bootcamp graduates with job opportunities in the tech industry.
* [Hetzner](http://htznr.li/CoolifyXHetzner) - A German web hosting company offering affordable dedicated servers, cloud services, and web hosting solutions.
@@ -52,7 +53,9 @@ ### Special Sponsors
* [SupaGuide](https://supa.guide/?ref=coolify.io) - A comprehensive resource hub offering guides and tutorials for web development using Supabase.
* [GoldenVM](https://billing.goldenvm.com/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes.
* [Tigris](https://tigrisdata.com/?ref=coolify.io) - A fully managed serverless object storage service compatible with Amazon S3 API. Offers high performance, scalability, and built-in search capabilities for efficient data management.
-* [Advin](https://coolify.ad.vin/?ref=coolify.io) - A digital advertising agency specializing in programmatic advertising and data-driven marketing strategies.
+* [Cloudify.ro](https://cloudify.ro/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes.
+* [Syntaxfm](https://syntax.fm/?ref=coolify.io) - Podcast for web developers.
+* [PFGlabs](https://pfglabs.com/?ref=coolify.io) - Build real project with Golang.
* [Treive](https://trieve.ai/?ref=coolify.io) - An AI-powered search and discovery platform for enhancing information retrieval in large datasets.
* [Blacksmith](https://blacksmith.sh/?ref=coolify.io) - A cloud-native platform for automating infrastructure provisioning and management across multiple cloud providers.
* [Brand Dev](https://brand.dev/?ref=coolify.io) - A web development agency specializing in creating custom digital experiences and brand identities.
@@ -92,6 +95,9 @@ ## Github Sponsors ($40+)
+
+
+
diff --git a/app/Console/Commands/CleanupUnreachableServers.php b/app/Console/Commands/CleanupUnreachableServers.php
index df0c6b81b..def01b265 100644
--- a/app/Console/Commands/CleanupUnreachableServers.php
+++ b/app/Console/Commands/CleanupUnreachableServers.php
@@ -18,7 +18,6 @@ public function handle()
if ($servers->count() > 0) {
foreach ($servers as $server) {
echo "Cleanup unreachable server ($server->id) with name $server->name";
- // send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up...");
$server->update([
'ip' => '1.2.3.4',
]);
diff --git a/app/Console/Commands/Dev.php b/app/Console/Commands/Dev.php
index 962000d07..257de0a92 100644
--- a/app/Console/Commands/Dev.php
+++ b/app/Console/Commands/Dev.php
@@ -76,7 +76,5 @@ public function init()
} else {
echo "Instance already initialized.\n";
}
- // Set permissions
- Process::run(['chmod', '-R', 'o+rwx', '.']);
}
}
diff --git a/app/Console/Commands/Emails.php b/app/Console/Commands/Emails.php
index f0e0e7fa0..33ddf3019 100644
--- a/app/Console/Commands/Emails.php
+++ b/app/Console/Commands/Emails.php
@@ -2,14 +2,12 @@
namespace App\Console\Commands;
-use App\Jobs\SendConfirmationForWaitlistJob;
use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Models\ScheduledDatabaseBackup;
use App\Models\Server;
use App\Models\StandalonePostgresql;
use App\Models\Team;
-use App\Models\Waitlist;
use App\Notifications\Application\DeploymentFailed;
use App\Notifications\Application\DeploymentSuccess;
use App\Notifications\Application\StatusChanged;
@@ -64,8 +62,6 @@ public function handle()
'backup-success' => 'Database - Backup Success',
'backup-failed' => 'Database - Backup Failed',
// 'invitation-link' => 'Invitation Link',
- 'waitlist-invitation-link' => 'Waitlist Invitation Link',
- 'waitlist-confirmation' => 'Waitlist Confirmation',
'realusers-before-trial' => 'REAL - Registered Users Before Trial without Subscription',
'realusers-server-lost-connection' => 'REAL - Server Lost Connection',
],
@@ -187,7 +183,7 @@ public function handle()
'team_id' => 0,
]);
}
- // $this->mail = (new BackupSuccess($backup->frequency, $db->name))->toMail();
+ //$this->mail = (new BackupSuccess($backup->frequency, $db->name))->toMail();
$this->sendEmail();
break;
// case 'invitation-link':
@@ -204,23 +200,6 @@ public function handle()
// $this->mail = (new InvitationLink($user))->toMail();
// $this->sendEmail();
// break;
- case 'waitlist-invitation-link':
- $this->mail = new MailMessage;
- $this->mail->view('emails.waitlist-invitation', [
- 'loginLink' => 'https://coolify.io',
- ]);
- $this->mail->subject('Congratulations! You are invited to join Coolify Cloud.');
- $this->sendEmail();
- break;
- case 'waitlist-confirmation':
- $found = Waitlist::where('email', $this->email)->first();
- if ($found) {
- SendConfirmationForWaitlistJob::dispatch($this->email, $found->uuid);
- } else {
- throw new Exception('Waitlist not found');
- }
-
- break;
case 'realusers-before-trial':
$this->mail = new MailMessage;
$this->mail->view('emails.before-trial-conversion');
diff --git a/app/Console/Commands/Horizon.php b/app/Console/Commands/Horizon.php
index 655729ec9..d3e35ca5a 100644
--- a/app/Console/Commands/Horizon.php
+++ b/app/Console/Commands/Horizon.php
@@ -13,7 +13,7 @@ class Horizon extends Command
public function handle()
{
if (config('constants.horizon.is_horizon_enabled')) {
- $this->info('[x]: Horizon is enabled. Starting.');
+ $this->info('Horizon is enabled on this server.');
$this->call('horizon');
exit(0);
} else {
diff --git a/app/Console/Commands/Init.php b/app/Console/Commands/Init.php
index 216262819..cc9bee0a5 100644
--- a/app/Console/Commands/Init.php
+++ b/app/Console/Commands/Init.php
@@ -55,10 +55,8 @@ public function handle()
} else {
$this->cleanup_in_progress_application_deployments();
}
- echo "[3]: Cleanup Redis keys.\n";
$this->call('cleanup:redis');
- echo "[4]: Cleanup stucked resources.\n";
$this->call('cleanup:stucked-resources');
try {
@@ -114,7 +112,6 @@ private function pullTemplatesFromCDN()
private function optimize()
{
- echo "[1]: Optimizing Laravel (caching config, routes, views).\n";
Artisan::call('optimize:clear');
Artisan::call('optimize');
}
@@ -189,7 +186,6 @@ private function cleanup_unused_network_from_coolify_proxy()
}
}
if ($commands->isNotEmpty()) {
- echo "Cleaning up unused networks from coolify proxy\n";
remote_process(command: $commands, type: ActivityTypes::INLINE->value, server: $server, ignore_errors: false);
}
} catch (\Throwable $e) {
@@ -232,15 +228,14 @@ private function send_alive_signal()
$settings = instanceSettings();
$do_not_track = data_get($settings, 'do_not_track');
if ($do_not_track == true) {
- echo "[2]: Skipping sending live signal as do_not_track is enabled\n";
+ echo "Do_not_track is enabled\n";
return;
}
try {
Http::get("https://undead.coolify.io/v4/alive?appId=$id&version=$version");
- echo "[2]: Sending live signal!\n";
} catch (\Throwable $e) {
- echo "[2]: Error in sending live signal: {$e->getMessage()}\n";
+ echo "Error in sending live signal: {$e->getMessage()}\n";
}
}
@@ -253,7 +248,6 @@ private function cleanup_in_progress_application_deployments()
}
$queued_inprogress_deployments = ApplicationDeploymentQueue::whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS->value, ApplicationDeploymentStatus::QUEUED->value])->get();
foreach ($queued_inprogress_deployments as $deployment) {
- echo "Cleaning up deployment: {$deployment->id}\n";
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
$deployment->save();
}
diff --git a/app/Console/Commands/Migration.php b/app/Console/Commands/Migration.php
new file mode 100644
index 000000000..44c17203b
--- /dev/null
+++ b/app/Console/Commands/Migration.php
@@ -0,0 +1,24 @@
+info('Migration is enabled on this server.');
+ $this->call('migrate', ['--force' => true, '--isolated' => true]);
+ exit(0);
+ } else {
+ $this->info('Migration is disabled on this server.');
+ exit(0);
+ }
+ }
+}
diff --git a/app/Console/Commands/NotifyDemo.php b/app/Console/Commands/NotifyDemo.php
index f0131b7b2..990a03869 100644
--- a/app/Console/Commands/NotifyDemo.php
+++ b/app/Console/Commands/NotifyDemo.php
@@ -59,9 +59,10 @@ private function showHelp()
Channels:
email
-
slack
discord
telegram
+
slack
+
pushover
@@ -72,6 +73,6 @@ private function showHelp()
In which manner you wish a coolified notification?
- HTML, ['email', 'slack', 'discord', 'telegram']);
+ HTML, ['email', 'discord', 'telegram', 'slack', 'pushover']);
}
}
diff --git a/app/Console/Commands/Scheduler.php b/app/Console/Commands/Scheduler.php
index 9ee7b06e6..ee64368c3 100644
--- a/app/Console/Commands/Scheduler.php
+++ b/app/Console/Commands/Scheduler.php
@@ -13,7 +13,7 @@ class Scheduler extends Command
public function handle()
{
if (config('constants.horizon.is_scheduler_enabled')) {
- $this->info('[x]: Scheduler is enabled. Starting.');
+ $this->info('Scheduler is enabled on this server.');
$this->call('schedule:work');
exit(0);
} else {
diff --git a/app/Console/Commands/Seeder.php b/app/Console/Commands/Seeder.php
new file mode 100644
index 000000000..e37b6a9d2
--- /dev/null
+++ b/app/Console/Commands/Seeder.php
@@ -0,0 +1,24 @@
+info('Seeder is enabled on this server.');
+ $this->call('db:seed', ['--class' => 'ProductionSeeder', '--force' => true]);
+ exit(0);
+ } else {
+ $this->info('Seeder is disabled on this server.');
+ exit(0);
+ }
+ }
+}
diff --git a/app/Console/Commands/WaitlistInvite.php b/app/Console/Commands/WaitlistInvite.php
deleted file mode 100644
index 2e330068c..000000000
--- a/app/Console/Commands/WaitlistInvite.php
+++ /dev/null
@@ -1,114 +0,0 @@
-option('people');
- for ($i = 0; $i < $people; $i++) {
- $this->main();
- }
- }
-
- private function main()
- {
- if ($this->argument('email')) {
- if ($this->option('only-email')) {
- $this->next_patient = User::whereEmail($this->argument('email'))->first();
- $this->password = Str::password();
- $this->next_patient->update([
- 'password' => Hash::make($this->password),
- 'force_password_reset' => true,
- ]);
- } else {
- $this->next_patient = Waitlist::where('email', $this->argument('email'))->first();
- }
- if (! $this->next_patient) {
- $this->error("{$this->argument('email')} not found in the waitlist.");
-
- return;
- }
- } else {
- $this->next_patient = Waitlist::orderBy('created_at', 'asc')->where('verified', true)->first();
- }
- if ($this->next_patient) {
- if ($this->option('only-email')) {
- $this->send_email();
-
- return;
- }
- $this->register_user();
- $this->remove_from_waitlist();
- $this->send_email();
- } else {
- $this->info('No verified user found in the waitlist. 👀');
- }
- }
-
- private function register_user()
- {
- $already_registered = User::whereEmail($this->next_patient->email)->first();
- if (! $already_registered) {
- $this->password = Str::password();
- User::create([
- 'name' => str($this->next_patient->email)->before('@'),
- 'email' => $this->next_patient->email,
- 'password' => Hash::make($this->password),
- 'force_password_reset' => true,
- ]);
- $this->info("User registered ({$this->next_patient->email}) successfully. 🎉");
- } else {
- throw new \Exception('User already registered');
- }
- }
-
- private function remove_from_waitlist()
- {
- $this->next_patient->delete();
- $this->info('User removed from waitlist successfully.');
- }
-
- private function send_email()
- {
- $token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password");
- $loginLink = route('auth.link', ['token' => $token]);
- $mail = new MailMessage;
- $mail->view('emails.waitlist-invitation', [
- 'loginLink' => $loginLink,
- ]);
- $mail->subject('Congratulations! You are invited to join Coolify Cloud.');
- send_user_an_email($mail, $this->next_patient->email);
- $this->info('Email sent successfully. 📧');
- }
-}
diff --git a/app/Console/Commands/Weird.php b/app/Console/Commands/Weird.php
deleted file mode 100644
index e471a5f96..000000000
--- a/app/Console/Commands/Weird.php
+++ /dev/null
@@ -1,58 +0,0 @@
-error('This command can only be run in development mode');
-
- return;
- }
- $run = $this->option('run');
- if ($run) {
- $servers = Server::all();
- foreach ($servers as $server) {
- ServerCheck::dispatch($server);
- }
-
- return;
- }
- $number = $this->option('number');
- for ($i = 0; $i < $number; $i++) {
- $uuid = Str::uuid();
- $server = Server::create([
- 'name' => 'localhost-'.$uuid,
- 'description' => 'This is a test docker container in development mode',
- 'ip' => 'coolify-testing-host',
- 'team_id' => 0,
- 'private_key_id' => 1,
- 'proxy' => [
- 'type' => ProxyTypes::NONE->value,
- 'status' => ProxyStatus::EXITED->value,
- ],
- ]);
- $server->settings->update([
- 'is_usable' => true,
- 'is_reachable' => true,
- ]);
- }
- } catch (\Exception $e) {
- $this->error($e->getMessage());
- }
- }
-}
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
index 19d22ae21..2ed3ee454 100644
--- a/app/Console/Kernel.php
+++ b/app/Console/Kernel.php
@@ -218,7 +218,7 @@ private function checkScheduledTasks(): void
}
}
if ($service) {
- if (str($service->status())->contains('running') === false) {
+ if (str($service->status)->contains('running') === false) {
continue;
}
}
diff --git a/app/Http/Controllers/Api/ResourcesController.php b/app/Http/Controllers/Api/ResourcesController.php
index 4180cef9a..ad12c83ab 100644
--- a/app/Http/Controllers/Api/ResourcesController.php
+++ b/app/Http/Controllers/Api/ResourcesController.php
@@ -53,11 +53,7 @@ public function resources(Request $request)
$resources = $resources->flatten();
$resources = $resources->map(function ($resource) {
$payload = $resource->toArray();
- if ($resource->getMorphClass() === \App\Models\Service::class) {
- $payload['status'] = $resource->status();
- } else {
- $payload['status'] = $resource->status;
- }
+ $payload['status'] = $resource->status;
$payload['type'] = $resource->type();
return $payload;
diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php
index f37040bdd..b1deb5321 100644
--- a/app/Http/Controllers/Api/ServersController.php
+++ b/app/Http/Controllers/Api/ServersController.php
@@ -154,11 +154,7 @@ public function server_by_uuid(Request $request)
'created_at' => $resource->created_at,
'updated_at' => $resource->updated_at,
];
- if ($resource->type() === 'service') {
- $payload['status'] = $resource->status();
- } else {
- $payload['status'] = $resource->status;
- }
+ $payload['status'] = $resource->status;
return $payload;
});
@@ -237,11 +233,7 @@ public function resources_by_server(Request $request)
'created_at' => $resource->created_at,
'updated_at' => $resource->updated_at,
];
- if ($resource->type() === 'service') {
- $payload['status'] = $resource->status();
- } else {
- $payload['status'] = $resource->status;
- }
+ $payload['status'] = $resource->status;
return $payload;
});
diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php
index e6b7e9854..bcaba7107 100644
--- a/app/Http/Controllers/Api/ServicesController.php
+++ b/app/Http/Controllers/Api/ServicesController.php
@@ -25,6 +25,8 @@ private function removeSensitiveData($service)
$service->makeHidden([
'docker_compose_raw',
'docker_compose',
+ 'value',
+ 'real_value',
]);
}
@@ -1070,7 +1072,7 @@ public function action_deploy(Request $request)
if (! $service) {
return response()->json(['message' => 'Service not found.'], 404);
}
- if (str($service->status())->contains('running')) {
+ if (str($service->status)->contains('running')) {
return response()->json(['message' => 'Service is already running.'], 400);
}
StartService::dispatch($service);
@@ -1148,7 +1150,7 @@ public function action_stop(Request $request)
if (! $service) {
return response()->json(['message' => 'Service not found.'], 404);
}
- if (str($service->status())->contains('stopped') || str($service->status())->contains('exited')) {
+ if (str($service->status)->contains('stopped') || str($service->status)->contains('exited')) {
return response()->json(['message' => 'Service is already stopped.'], 400);
}
StopService::dispatch($service);
diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php
index 9f1e4eeb8..522683efa 100644
--- a/app/Http/Controllers/Controller.php
+++ b/app/Http/Controllers/Controller.php
@@ -42,15 +42,13 @@ public function verify()
public function email_verify(EmailVerificationRequest $request)
{
$request->fulfill();
- $name = request()->user()?->name;
- // send_internal_notification("User {$name} verified their email address.");
return redirect(RouteServiceProvider::HOME);
}
public function forgot_password(Request $request)
{
- if (is_transactional_emails_active()) {
+ if (is_transactional_emails_enabled()) {
$arrayOfRequest = $request->only(Fortify::email());
$request->merge([
'email' => Str::lower($arrayOfRequest['email']),
diff --git a/app/Http/Controllers/Webhook/Waitlist.php b/app/Http/Controllers/Webhook/Waitlist.php
deleted file mode 100644
index dec8ca72d..000000000
--- a/app/Http/Controllers/Webhook/Waitlist.php
+++ /dev/null
@@ -1,63 +0,0 @@
-get('email');
- $confirmation_code = request()->get('confirmation_code');
- try {
- $found = ModelsWaitlist::where('uuid', $confirmation_code)->where('email', $email)->first();
- if ($found) {
- if (! $found->verified) {
- if ($found->created_at > now()->subMinutes(config('constants.waitlist.expiration'))) {
- $found->verified = true;
- $found->save();
- send_internal_notification('Waitlist confirmed: '.$email);
-
- return 'Thank you for confirming your email address. We will notify you when you are next in line.';
- } else {
- $found->delete();
- send_internal_notification('Waitlist expired: '.$email);
-
- return 'Your confirmation code has expired. Please sign up again.';
- }
- }
- }
-
- return redirect()->route('dashboard');
- } catch (Exception $e) {
- send_internal_notification('Waitlist confirmation failed: '.$e->getMessage());
-
- return redirect()->route('dashboard');
- }
- }
-
- public function cancel(Request $request)
- {
- $email = request()->get('email');
- $confirmation_code = request()->get('confirmation_code');
- try {
- $found = ModelsWaitlist::where('uuid', $confirmation_code)->where('email', $email)->first();
- if ($found && ! $found->verified) {
- $found->delete();
- send_internal_notification('Waitlist cancelled: '.$email);
-
- return 'Your email address has been removed from the waitlist.';
- }
-
- return redirect()->route('dashboard');
- } catch (Exception $e) {
- send_internal_notification('Waitlist cancellation failed: '.$e->getMessage());
-
- return redirect()->route('dashboard');
- }
- }
-}
diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php
index 04e71c4e3..6b677fa0e 100644
--- a/app/Jobs/ApplicationDeploymentJob.php
+++ b/app/Jobs/ApplicationDeploymentJob.php
@@ -2409,7 +2409,7 @@ private function next(string $status)
if (! $this->only_this_server) {
$this->deploy_to_additional_destinations();
}
- //$this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
+ $this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
}
}
diff --git a/app/Jobs/CheckResaleLicenseJob.php b/app/Jobs/CheckResaleLicenseJob.php
deleted file mode 100644
index 7479867b6..000000000
--- a/app/Jobs/CheckResaleLicenseJob.php
+++ /dev/null
@@ -1,28 +0,0 @@
-getMessage());
- throw $e;
- }
- }
-}
diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php
index ee702202f..6730dceb7 100644
--- a/app/Jobs/DatabaseBackupJob.php
+++ b/app/Jobs/DatabaseBackupJob.php
@@ -32,8 +32,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
public Server $server;
- public ScheduledDatabaseBackup $backup;
-
public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database;
public ?string $container_name = null;
@@ -58,10 +56,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
public ?S3Storage $s3 = null;
- public function __construct($backup)
+ public function __construct(public ScheduledDatabaseBackup $backup)
{
$this->onQueue('high');
- $this->backup = $backup;
}
public function handle(): void
@@ -306,7 +303,9 @@ public function handle(): void
if ($this->backup->save_s3) {
$this->upload_to_s3();
}
- //$this->team?->notify(new BackupSuccess($this->backup, $this->database, $database));
+
+ $this->team->notify(new BackupSuccess($this->backup, $this->database, $database));
+
$this->backup_log->update([
'status' => 'success',
'message' => $this->backup_output,
diff --git a/app/Jobs/DockerCleanupJob.php b/app/Jobs/DockerCleanupJob.php
index 80542e03b..103c137b9 100644
--- a/app/Jobs/DockerCleanupJob.php
+++ b/app/Jobs/DockerCleanupJob.php
@@ -4,7 +4,8 @@
use App\Actions\Server\CleanupDocker;
use App\Models\Server;
-use App\Notifications\Server\DockerCleanup;
+use App\Notifications\Server\DockerCleanupFailed;
+use App\Notifications\Server\DockerCleanupSuccess;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
@@ -12,7 +13,6 @@
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
-use Illuminate\Support\Facades\Log;
class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
{
@@ -38,35 +38,36 @@ public function handle(): void
return;
}
- if ($this->manualCleanup || $this->server->settings->force_docker_cleanup) {
- Log::info('DockerCleanupJob '.($this->manualCleanup ? 'manual' : 'force').' cleanup on '.$this->server->name);
- CleanupDocker::run(server: $this->server);
-
- return;
- }
-
$this->usageBefore = $this->server->getDiskUsage();
- if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) {
- Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
+
+ if ($this->manualCleanup || $this->server->settings->force_docker_cleanup) {
CleanupDocker::run(server: $this->server);
+ $usageAfter = $this->server->getDiskUsage();
+ $this->server->team?->notify(new DockerCleanupSuccess($this->server, ($this->manualCleanup ? 'Manual' : 'Forced').' Docker cleanup job executed successfully. Disk usage before: '.$this->usageBefore.'%, Disk usage after: '.$usageAfter.'%.'));
return;
}
+
+ if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) {
+ CleanupDocker::run(server: $this->server);
+ $this->server->team?->notify(new DockerCleanupSuccess($this->server, 'Docker cleanup job executed successfully, but no disk usage could be determined.'));
+ }
+
if ($this->usageBefore >= $this->server->settings->docker_cleanup_threshold) {
CleanupDocker::run(server: $this->server);
$usageAfter = $this->server->getDiskUsage();
- if ($usageAfter < $this->usageBefore) {
- $this->server->team?->notify(new DockerCleanup($this->server, 'Saved '.($this->usageBefore - $usageAfter).'% disk space.'));
- Log::info('DockerCleanupJob done: Saved '.($this->usageBefore - $usageAfter).'% disk space on '.$this->server->name);
+ $diskSaved = $this->usageBefore - $usageAfter;
+
+ if ($diskSaved > 0) {
+ $this->server->team?->notify(new DockerCleanupSuccess($this->server, 'Saved '.$diskSaved.'% disk space. Disk usage before: '.$this->usageBefore.'%, Disk usage after: '.$usageAfter.'%.'));
} else {
- Log::info('DockerCleanupJob failed to save disk space on '.$this->server->name);
+ $this->server->team?->notify(new DockerCleanupSuccess($this->server, 'Docker cleanup job executed successfully, but no disk space was saved. Disk usage before: '.$this->usageBefore.'%, Disk usage after: '.$usageAfter.'%.'));
}
} else {
- Log::info('No need to clean up '.$this->server->name);
+ $this->server->team?->notify(new DockerCleanupSuccess($this->server, 'No cleanup needed for '.$this->server->name));
}
} catch (\Throwable $e) {
- CleanupDocker::run(server: $this->server);
- Log::error('DockerCleanupJob failed: '.$e->getMessage());
+ $this->server->team?->notify(new DockerCleanupFailed($this->server, 'Docker cleanup job failed with the following error: '.$e->getMessage()));
throw $e;
}
}
diff --git a/app/Jobs/ScheduledTaskJob.php b/app/Jobs/ScheduledTaskJob.php
index 00575e187..90a10f3e9 100644
--- a/app/Jobs/ScheduledTaskJob.php
+++ b/app/Jobs/ScheduledTaskJob.php
@@ -10,6 +10,7 @@
use App\Models\Service;
use App\Models\Team;
use App\Notifications\ScheduledTask\TaskFailed;
+use App\Notifications\ScheduledTask\TaskSuccess;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
@@ -111,6 +112,8 @@ public function handle(): void
'message' => $this->task_output,
]);
+ $this->team?->notify(new TaskSuccess($this->task, $this->task_output));
+
return;
}
}
@@ -125,7 +128,6 @@ public function handle(): void
]);
}
$this->team?->notify(new TaskFailed($this->task, $e->getMessage()));
- // send_internal_notification('ScheduledTaskJob failed with: ' . $e->getMessage());
throw $e;
} finally {
ScheduledTaskDone::dispatch($this->team->id);
diff --git a/app/Jobs/SendConfirmationForWaitlistJob.php b/app/Jobs/SendConfirmationForWaitlistJob.php
deleted file mode 100755
index 7af8205fc..000000000
--- a/app/Jobs/SendConfirmationForWaitlistJob.php
+++ /dev/null
@@ -1,37 +0,0 @@
-email.'&confirmation_code='.$this->uuid;
- $cancel_url = base_url().'/webhooks/waitlist/cancel?email='.$this->email.'&confirmation_code='.$this->uuid;
- $mail->view('emails.waitlist-confirmation',
- [
- 'confirmation_url' => $confirmation_url,
- 'cancel_url' => $cancel_url,
- ]);
- $mail->subject('You are on the waitlist!');
- send_user_an_email($mail, $this->email);
- } catch (\Throwable $e) {
- send_internal_notification("SendConfirmationForWaitlistJob failed for {$this->email} with error: ".$e->getMessage());
- throw $e;
- }
- }
-}
diff --git a/app/Jobs/SendMessageToPushoverJob.php b/app/Jobs/SendMessageToPushoverJob.php
new file mode 100644
index 000000000..834a32b07
--- /dev/null
+++ b/app/Jobs/SendMessageToPushoverJob.php
@@ -0,0 +1,50 @@
+onQueue('high');
+ }
+
+ /**
+ * Execute the job.
+ */
+ public function handle(): void
+ {
+ $response = Http::post('https://api.pushover.net/1/messages.json', $this->message->toPayload($this->token, $this->user));
+ if ($response->failed()) {
+ throw new \RuntimeException('Pushover notification failed with ' . $response->status() . ' status code.' . $response->body());
+ }
+ }
+}
diff --git a/app/Jobs/SendMessageToTelegramJob.php b/app/Jobs/SendMessageToTelegramJob.php
index 85f4fc934..6b0a64ae3 100644
--- a/app/Jobs/SendMessageToTelegramJob.php
+++ b/app/Jobs/SendMessageToTelegramJob.php
@@ -32,7 +32,7 @@ public function __construct(
public array $buttons,
public string $token,
public string $chatId,
- public ?string $topicId = null,
+ public ?string $threadId = null,
) {
$this->onQueue('high');
}
@@ -67,8 +67,8 @@ public function handle(): void
'chat_id' => $this->chatId,
'text' => $this->text,
];
- if ($this->topicId) {
- $payload['message_thread_id'] = $this->topicId;
+ if ($this->threadId) {
+ $payload['message_thread_id'] = $this->threadId;
}
$response = Http::post($url, $payload);
if ($response->failed()) {
diff --git a/app/Jobs/StripeProcessJob.php b/app/Jobs/StripeProcessJob.php
index 00c9b6d18..d61c738f4 100644
--- a/app/Jobs/StripeProcessJob.php
+++ b/app/Jobs/StripeProcessJob.php
@@ -173,8 +173,8 @@ public function handle(): void
$userId = data_get($data, 'metadata.user_id');
$customerId = data_get($data, 'customer');
$status = data_get($data, 'status');
- $subscriptionId = data_get($data, 'items.data.0.subscription');
- $planId = data_get($data, 'items.data.0.plan.id');
+ $subscriptionId = data_get($data, 'items.data.0.subscription') ?? data_get($data, 'id');
+ $planId = data_get($data, 'items.data.0.plan.id') ?? data_get($data, 'plan.id');
if (Str::contains($excludedPlans, $planId)) {
send_internal_notification('Subscription excluded.');
break;
@@ -218,9 +218,18 @@ public function handle(): void
'stripe_cancel_at_period_end' => $cancelAtPeriodEnd,
]);
if ($status === 'paused' || $status === 'incomplete_expired') {
- $subscription->update([
- 'stripe_invoice_paid' => false,
- ]);
+ if ($subscription->stripe_subscription_id === $subscriptionId) {
+ $subscription->update([
+ 'stripe_invoice_paid' => false,
+ ]);
+ }
+ }
+ if ($status === 'active') {
+ if ($subscription->stripe_subscription_id === $subscriptionId) {
+ $subscription->update([
+ 'stripe_invoice_paid' => true,
+ ]);
+ }
}
if ($feedback) {
$reason = "Cancellation feedback for {$customerId}: '".$feedback."'";
@@ -228,13 +237,24 @@ public function handle(): void
$reason .= ' with comment: \''.$comment."'";
}
}
+
break;
case 'customer.subscription.deleted':
- // End subscription
$customerId = data_get($data, 'customer');
- $subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
- $team = data_get($subscription, 'team');
- $team?->subscriptionEnded();
+ $subscriptionId = data_get($data, 'id');
+ $subscription = Subscription::where('stripe_customer_id', $customerId)->where('stripe_subscription_id', $subscriptionId)->first();
+ if ($subscription) {
+ $team = data_get($subscription, 'team');
+ if ($team) {
+ $team->subscriptionEnded();
+ } else {
+ send_internal_notification('Subscription deleted but no team found in Coolify for customer: '.$customerId);
+ throw new \RuntimeException("No team found in Coolify for customer: {$customerId}");
+ }
+ } else {
+ send_internal_notification('Subscription deleted but no subscription found in Coolify for customer: '.$customerId);
+ throw new \RuntimeException("No subscription found in Coolify for customer: {$customerId}");
+ }
break;
default:
throw new \RuntimeException("Unhandled event type: {$type}");
diff --git a/app/Livewire/Notifications/Discord.php b/app/Livewire/Notifications/Discord.php
index 7a177a227..57007813e 100644
--- a/app/Livewire/Notifications/Discord.php
+++ b/app/Livewire/Notifications/Discord.php
@@ -2,6 +2,7 @@
namespace App\Livewire\Notifications;
+use App\Models\DiscordNotificationSettings;
use App\Models\Team;
use App\Notifications\Test;
use Livewire\Attributes\Validate;
@@ -11,6 +12,8 @@ class Discord extends Component
{
public Team $team;
+ public DiscordNotificationSettings $settings;
+
#[Validate(['boolean'])]
public bool $discordEnabled = false;
@@ -18,27 +21,46 @@ class Discord extends Component
public ?string $discordWebhookUrl = null;
#[Validate(['boolean'])]
- public bool $discordNotificationsTest = false;
+ public bool $deploymentSuccessDiscordNotifications = false;
#[Validate(['boolean'])]
- public bool $discordNotificationsDeployments = false;
+ public bool $deploymentFailureDiscordNotifications = true;
#[Validate(['boolean'])]
- public bool $discordNotificationsStatusChanges = false;
+ public bool $statusChangeDiscordNotifications = false;
#[Validate(['boolean'])]
- public bool $discordNotificationsDatabaseBackups = false;
+ public bool $backupSuccessDiscordNotifications = false;
#[Validate(['boolean'])]
- public bool $discordNotificationsScheduledTasks = false;
+ public bool $backupFailureDiscordNotifications = true;
#[Validate(['boolean'])]
- public bool $discordNotificationsServerDiskUsage = false;
+ public bool $scheduledTaskSuccessDiscordNotifications = false;
+
+ #[Validate(['boolean'])]
+ public bool $scheduledTaskFailureDiscordNotifications = true;
+
+ #[Validate(['boolean'])]
+ public bool $dockerCleanupSuccessDiscordNotifications = false;
+
+ #[Validate(['boolean'])]
+ public bool $dockerCleanupFailureDiscordNotifications = true;
+
+ #[Validate(['boolean'])]
+ public bool $serverDiskUsageDiscordNotifications = true;
+
+ #[Validate(['boolean'])]
+ public bool $serverReachableDiscordNotifications = false;
+
+ #[Validate(['boolean'])]
+ public bool $serverUnreachableDiscordNotifications = true;
public function mount()
{
try {
$this->team = auth()->user()->currentTeam();
+ $this->settings = $this->team->discordNotificationSettings;
$this->syncData();
} catch (\Throwable $e) {
return handleError($e, $this);
@@ -49,25 +71,40 @@ public function syncData(bool $toModel = false)
{
if ($toModel) {
$this->validate();
- $this->team->discord_enabled = $this->discordEnabled;
- $this->team->discord_webhook_url = $this->discordWebhookUrl;
- $this->team->discord_notifications_test = $this->discordNotificationsTest;
- $this->team->discord_notifications_deployments = $this->discordNotificationsDeployments;
- $this->team->discord_notifications_status_changes = $this->discordNotificationsStatusChanges;
- $this->team->discord_notifications_database_backups = $this->discordNotificationsDatabaseBackups;
- $this->team->discord_notifications_scheduled_tasks = $this->discordNotificationsScheduledTasks;
- $this->team->discord_notifications_server_disk_usage = $this->discordNotificationsServerDiskUsage;
- $this->team->save();
+ $this->settings->discord_enabled = $this->discordEnabled;
+ $this->settings->discord_webhook_url = $this->discordWebhookUrl;
+
+ $this->settings->deployment_success_discord_notifications = $this->deploymentSuccessDiscordNotifications;
+ $this->settings->deployment_failure_discord_notifications = $this->deploymentFailureDiscordNotifications;
+ $this->settings->status_change_discord_notifications = $this->statusChangeDiscordNotifications;
+ $this->settings->backup_success_discord_notifications = $this->backupSuccessDiscordNotifications;
+ $this->settings->backup_failure_discord_notifications = $this->backupFailureDiscordNotifications;
+ $this->settings->scheduled_task_success_discord_notifications = $this->scheduledTaskSuccessDiscordNotifications;
+ $this->settings->scheduled_task_failure_discord_notifications = $this->scheduledTaskFailureDiscordNotifications;
+ $this->settings->docker_cleanup_success_discord_notifications = $this->dockerCleanupSuccessDiscordNotifications;
+ $this->settings->docker_cleanup_failure_discord_notifications = $this->dockerCleanupFailureDiscordNotifications;
+ $this->settings->server_disk_usage_discord_notifications = $this->serverDiskUsageDiscordNotifications;
+ $this->settings->server_reachable_discord_notifications = $this->serverReachableDiscordNotifications;
+ $this->settings->server_unreachable_discord_notifications = $this->serverUnreachableDiscordNotifications;
+
+ $this->settings->save();
refreshSession();
} else {
- $this->discordEnabled = $this->team->discord_enabled;
- $this->discordWebhookUrl = $this->team->discord_webhook_url;
- $this->discordNotificationsTest = $this->team->discord_notifications_test;
- $this->discordNotificationsDeployments = $this->team->discord_notifications_deployments;
- $this->discordNotificationsStatusChanges = $this->team->discord_notifications_status_changes;
- $this->discordNotificationsDatabaseBackups = $this->team->discord_notifications_database_backups;
- $this->discordNotificationsScheduledTasks = $this->team->discord_notifications_scheduled_tasks;
- $this->discordNotificationsServerDiskUsage = $this->team->discord_notifications_server_disk_usage;
+ $this->discordEnabled = $this->settings->discord_enabled;
+ $this->discordWebhookUrl = $this->settings->discord_webhook_url;
+
+ $this->deploymentSuccessDiscordNotifications = $this->settings->deployment_success_discord_notifications;
+ $this->deploymentFailureDiscordNotifications = $this->settings->deployment_failure_discord_notifications;
+ $this->statusChangeDiscordNotifications = $this->settings->status_change_discord_notifications;
+ $this->backupSuccessDiscordNotifications = $this->settings->backup_success_discord_notifications;
+ $this->backupFailureDiscordNotifications = $this->settings->backup_failure_discord_notifications;
+ $this->scheduledTaskSuccessDiscordNotifications = $this->settings->scheduled_task_success_discord_notifications;
+ $this->scheduledTaskFailureDiscordNotifications = $this->settings->scheduled_task_failure_discord_notifications;
+ $this->dockerCleanupSuccessDiscordNotifications = $this->settings->docker_cleanup_success_discord_notifications;
+ $this->dockerCleanupFailureDiscordNotifications = $this->settings->docker_cleanup_failure_discord_notifications;
+ $this->serverDiskUsageDiscordNotifications = $this->settings->server_disk_usage_discord_notifications;
+ $this->serverReachableDiscordNotifications = $this->settings->server_reachable_discord_notifications;
+ $this->serverUnreachableDiscordNotifications = $this->settings->server_unreachable_discord_notifications;
}
}
@@ -117,7 +154,7 @@ public function saveModel()
public function sendTestNotification()
{
try {
- $this->team->notify(new Test);
+ $this->team->notify(new Test(channel: 'discord'));
$this->dispatch('success', 'Test notification sent.');
} catch (\Throwable $e) {
return handleError($e, $this);
diff --git a/app/Livewire/Notifications/Email.php b/app/Livewire/Notifications/Email.php
index ab3768643..eac48cf77 100644
--- a/app/Livewire/Notifications/Email.php
+++ b/app/Livewire/Notifications/Email.php
@@ -2,6 +2,7 @@
namespace App\Livewire\Notifications;
+use App\Models\EmailNotificationSettings;
use App\Models\Team;
use App\Notifications\Test;
use Illuminate\Support\Facades\RateLimiter;
@@ -11,17 +12,20 @@
class Email extends Component
{
+ protected $listeners = ['refresh' => '$refresh'];
+
+ #[Locked]
public Team $team;
+ #[Locked]
+ public EmailNotificationSettings $settings;
+
#[Locked]
public string $emails;
#[Validate(['boolean'])]
public bool $smtpEnabled = false;
- #[Validate(['boolean'])]
- public bool $useInstanceEmailSettings = false;
-
#[Validate(['nullable', 'email'])]
public ?string $smtpFromAddress = null;
@@ -34,11 +38,11 @@ class Email extends Component
#[Validate(['nullable', 'string'])]
public ?string $smtpHost = null;
- #[Validate(['nullable', 'numeric'])]
+ #[Validate(['nullable', 'numeric', 'min:1', 'max:65535'])]
public ?int $smtpPort = null;
#[Validate(['nullable', 'string', 'in:tls,ssl,none'])]
- public ?string $smtpEncryption = null;
+ public ?string $smtpEncryption = 'tls';
#[Validate(['nullable', 'string'])]
public ?string $smtpUsername = null;
@@ -50,29 +54,50 @@ class Email extends Component
public ?int $smtpTimeout = null;
#[Validate(['boolean'])]
- public bool $smtpNotificationsTest = false;
-
- #[Validate(['boolean'])]
- public bool $smtpNotificationsDeployments = false;
-
- #[Validate(['boolean'])]
- public bool $smtpNotificationsStatusChanges = false;
-
- #[Validate(['boolean'])]
- public bool $smtpNotificationsDatabaseBackups = false;
-
- #[Validate(['boolean'])]
- public bool $smtpNotificationsScheduledTasks = false;
-
- #[Validate(['boolean'])]
- public bool $smtpNotificationsServerDiskUsage = false;
-
- #[Validate(['boolean'])]
- public bool $resendEnabled;
+ public bool $resendEnabled = false;
#[Validate(['nullable', 'string'])]
public ?string $resendApiKey = null;
+ #[Validate(['boolean'])]
+ public bool $useInstanceEmailSettings = false;
+
+ #[Validate(['boolean'])]
+ public bool $deploymentSuccessEmailNotifications = false;
+
+ #[Validate(['boolean'])]
+ public bool $deploymentFailureEmailNotifications = true;
+
+ #[Validate(['boolean'])]
+ public bool $statusChangeEmailNotifications = false;
+
+ #[Validate(['boolean'])]
+ public bool $backupSuccessEmailNotifications = false;
+
+ #[Validate(['boolean'])]
+ public bool $backupFailureEmailNotifications = true;
+
+ #[Validate(['boolean'])]
+ public bool $scheduledTaskSuccessEmailNotifications = false;
+
+ #[Validate(['boolean'])]
+ public bool $scheduledTaskFailureEmailNotifications = true;
+
+ #[Validate(['boolean'])]
+ public bool $dockerCleanupSuccessEmailNotifications = false;
+
+ #[Validate(['boolean'])]
+ public bool $dockerCleanupFailureEmailNotifications = true;
+
+ #[Validate(['boolean'])]
+ public bool $serverDiskUsageEmailNotifications = true;
+
+ #[Validate(['boolean'])]
+ public bool $serverReachableEmailNotifications = false;
+
+ #[Validate(['boolean'])]
+ public bool $serverUnreachableEmailNotifications = true;
+
#[Validate(['nullable', 'email'])]
public ?string $testEmailAddress = null;
@@ -81,7 +106,9 @@ public function mount()
try {
$this->team = auth()->user()->currentTeam();
$this->emails = auth()->user()->email;
+ $this->settings = $this->team->emailNotificationSettings;
$this->syncData();
+ $this->testEmailAddress = auth()->user()->email;
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -91,47 +118,191 @@ public function syncData(bool $toModel = false)
{
if ($toModel) {
$this->validate();
- $this->team->smtp_enabled = $this->smtpEnabled;
- $this->team->smtp_from_address = $this->smtpFromAddress;
- $this->team->smtp_from_name = $this->smtpFromName;
- $this->team->smtp_host = $this->smtpHost;
- $this->team->smtp_port = $this->smtpPort;
- $this->team->smtp_encryption = $this->smtpEncryption;
- $this->team->smtp_username = $this->smtpUsername;
- $this->team->smtp_password = $this->smtpPassword;
- $this->team->smtp_timeout = $this->smtpTimeout;
- $this->team->smtp_recipients = $this->smtpRecipients;
- $this->team->smtp_notifications_test = $this->smtpNotificationsTest;
- $this->team->smtp_notifications_deployments = $this->smtpNotificationsDeployments;
- $this->team->smtp_notifications_status_changes = $this->smtpNotificationsStatusChanges;
- $this->team->smtp_notifications_database_backups = $this->smtpNotificationsDatabaseBackups;
- $this->team->smtp_notifications_scheduled_tasks = $this->smtpNotificationsScheduledTasks;
- $this->team->smtp_notifications_server_disk_usage = $this->smtpNotificationsServerDiskUsage;
- $this->team->use_instance_email_settings = $this->useInstanceEmailSettings;
- $this->team->resend_enabled = $this->resendEnabled;
- $this->team->resend_api_key = $this->resendApiKey;
- $this->team->save();
- refreshSession();
+ $this->settings->smtp_enabled = $this->smtpEnabled;
+ $this->settings->smtp_from_address = $this->smtpFromAddress;
+ $this->settings->smtp_from_name = $this->smtpFromName;
+ $this->settings->smtp_recipients = $this->smtpRecipients;
+ $this->settings->smtp_host = $this->smtpHost;
+ $this->settings->smtp_port = $this->smtpPort;
+ $this->settings->smtp_encryption = $this->smtpEncryption;
+ $this->settings->smtp_username = $this->smtpUsername;
+ $this->settings->smtp_password = $this->smtpPassword;
+ $this->settings->smtp_timeout = $this->smtpTimeout;
+
+ $this->settings->resend_enabled = $this->resendEnabled;
+ $this->settings->resend_api_key = $this->resendApiKey;
+
+ $this->settings->use_instance_email_settings = $this->useInstanceEmailSettings;
+
+ $this->settings->deployment_success_email_notifications = $this->deploymentSuccessEmailNotifications;
+ $this->settings->deployment_failure_email_notifications = $this->deploymentFailureEmailNotifications;
+ $this->settings->status_change_email_notifications = $this->statusChangeEmailNotifications;
+ $this->settings->backup_success_email_notifications = $this->backupSuccessEmailNotifications;
+ $this->settings->backup_failure_email_notifications = $this->backupFailureEmailNotifications;
+ $this->settings->scheduled_task_success_email_notifications = $this->scheduledTaskSuccessEmailNotifications;
+ $this->settings->scheduled_task_failure_email_notifications = $this->scheduledTaskFailureEmailNotifications;
+ $this->settings->docker_cleanup_success_email_notifications = $this->dockerCleanupSuccessEmailNotifications;
+ $this->settings->docker_cleanup_failure_email_notifications = $this->dockerCleanupFailureEmailNotifications;
+ $this->settings->server_disk_usage_email_notifications = $this->serverDiskUsageEmailNotifications;
+ $this->settings->server_reachable_email_notifications = $this->serverReachableEmailNotifications;
+ $this->settings->server_unreachable_email_notifications = $this->serverUnreachableEmailNotifications;
+ $this->settings->save();
+
} else {
- $this->smtpEnabled = $this->team->smtp_enabled;
- $this->smtpFromAddress = $this->team->smtp_from_address;
- $this->smtpFromName = $this->team->smtp_from_name;
- $this->smtpHost = $this->team->smtp_host;
- $this->smtpPort = $this->team->smtp_port;
- $this->smtpEncryption = $this->team->smtp_encryption;
- $this->smtpUsername = $this->team->smtp_username;
- $this->smtpPassword = $this->team->smtp_password;
- $this->smtpTimeout = $this->team->smtp_timeout;
- $this->smtpRecipients = $this->team->smtp_recipients;
- $this->smtpNotificationsTest = $this->team->smtp_notifications_test;
- $this->smtpNotificationsDeployments = $this->team->smtp_notifications_deployments;
- $this->smtpNotificationsStatusChanges = $this->team->smtp_notifications_status_changes;
- $this->smtpNotificationsDatabaseBackups = $this->team->smtp_notifications_database_backups;
- $this->smtpNotificationsScheduledTasks = $this->team->smtp_notifications_scheduled_tasks;
- $this->smtpNotificationsServerDiskUsage = $this->team->smtp_notifications_server_disk_usage;
- $this->useInstanceEmailSettings = $this->team->use_instance_email_settings;
- $this->resendEnabled = $this->team->resend_enabled;
- $this->resendApiKey = $this->team->resend_api_key;
+ $this->smtpEnabled = $this->settings->smtp_enabled;
+ $this->smtpFromAddress = $this->settings->smtp_from_address;
+ $this->smtpFromName = $this->settings->smtp_from_name;
+ $this->smtpRecipients = $this->settings->smtp_recipients;
+ $this->smtpHost = $this->settings->smtp_host;
+ $this->smtpPort = $this->settings->smtp_port;
+ $this->smtpEncryption = $this->settings->smtp_encryption;
+ $this->smtpUsername = $this->settings->smtp_username;
+ $this->smtpPassword = $this->settings->smtp_password;
+ $this->smtpTimeout = $this->settings->smtp_timeout;
+
+ $this->resendEnabled = $this->settings->resend_enabled;
+ $this->resendApiKey = $this->settings->resend_api_key;
+
+ $this->useInstanceEmailSettings = $this->settings->use_instance_email_settings;
+
+ $this->deploymentSuccessEmailNotifications = $this->settings->deployment_success_email_notifications;
+ $this->deploymentFailureEmailNotifications = $this->settings->deployment_failure_email_notifications;
+ $this->statusChangeEmailNotifications = $this->settings->status_change_email_notifications;
+ $this->backupSuccessEmailNotifications = $this->settings->backup_success_email_notifications;
+ $this->backupFailureEmailNotifications = $this->settings->backup_failure_email_notifications;
+ $this->scheduledTaskSuccessEmailNotifications = $this->settings->scheduled_task_success_email_notifications;
+ $this->scheduledTaskFailureEmailNotifications = $this->settings->scheduled_task_failure_email_notifications;
+ $this->dockerCleanupSuccessEmailNotifications = $this->settings->docker_cleanup_success_email_notifications;
+ $this->dockerCleanupFailureEmailNotifications = $this->settings->docker_cleanup_failure_email_notifications;
+ $this->serverDiskUsageEmailNotifications = $this->settings->server_disk_usage_email_notifications;
+ $this->serverReachableEmailNotifications = $this->settings->server_reachable_email_notifications;
+ $this->serverUnreachableEmailNotifications = $this->settings->server_unreachable_email_notifications;
+ }
+ }
+
+ public function submit()
+ {
+ try {
+ $this->resetErrorBag();
+ $this->saveModel();
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
+
+ public function saveModel()
+ {
+ $this->syncData(true);
+ $this->dispatch('success', 'Email notifications settings updated.');
+ }
+
+ public function instantSave(?string $type = null)
+ {
+ try {
+ $this->resetErrorBag();
+
+ if ($type === 'SMTP') {
+ $this->submitSmtp();
+ } elseif ($type === 'Resend') {
+ $this->submitResend();
+ } else {
+ $this->smtpEnabled = false;
+ $this->resendEnabled = false;
+ $this->saveModel();
+
+ return;
+ }
+ } catch (\Throwable $e) {
+ if ($type === 'SMTP') {
+ $this->smtpEnabled = false;
+ } elseif ($type === 'Resend') {
+ $this->resendEnabled = false;
+ }
+
+ return handleError($e, $this);
+ } finally {
+ $this->dispatch('refresh');
+ }
+ }
+
+ public function submitSmtp()
+ {
+ try {
+ $this->resetErrorBag();
+ $this->validate([
+ 'smtpEnabled' => 'boolean',
+ 'smtpFromAddress' => 'required|email',
+ 'smtpFromName' => 'required|string',
+ 'smtpHost' => 'required|string',
+ 'smtpPort' => 'required|numeric',
+ 'smtpEncryption' => 'required|string|in:tls,ssl,none',
+ 'smtpUsername' => 'nullable|string',
+ 'smtpPassword' => 'nullable|string',
+ 'smtpTimeout' => 'nullable|numeric',
+ ], [
+ 'smtpFromAddress.required' => 'From Address is required.',
+ 'smtpFromAddress.email' => 'Please enter a valid email address.',
+ 'smtpFromName.required' => 'From Name is required.',
+ 'smtpHost.required' => 'SMTP Host is required.',
+ 'smtpPort.required' => 'SMTP Port is required.',
+ 'smtpPort.numeric' => 'SMTP Port must be a number.',
+ 'smtpEncryption.required' => 'Encryption type is required.',
+ ]);
+
+ $this->settings->resend_enabled = false;
+ $this->settings->use_instance_email_settings = false;
+ $this->resendEnabled = false;
+ $this->useInstanceEmailSettings = false;
+
+ $this->settings->smtp_enabled = $this->smtpEnabled;
+ $this->settings->smtp_from_address = $this->smtpFromAddress;
+ $this->settings->smtp_from_name = $this->smtpFromName;
+ $this->settings->smtp_host = $this->smtpHost;
+ $this->settings->smtp_port = $this->smtpPort;
+ $this->settings->smtp_encryption = $this->smtpEncryption;
+ $this->settings->smtp_username = $this->smtpUsername;
+ $this->settings->smtp_password = $this->smtpPassword;
+ $this->settings->smtp_timeout = $this->smtpTimeout;
+
+ $this->settings->save();
+ $this->dispatch('success', 'SMTP settings updated.');
+ } catch (\Throwable $e) {
+ $this->smtpEnabled = false;
+
+ return handleError($e);
+ }
+ }
+
+ public function submitResend()
+ {
+ try {
+ $this->resetErrorBag();
+ $this->validate([
+ 'resendEnabled' => 'boolean',
+ 'resendApiKey' => 'required|string',
+ 'smtpFromAddress' => 'required|email',
+ 'smtpFromName' => 'required|string',
+ ], [
+ 'resendApiKey.required' => 'Resend API Key is required.',
+ 'smtpFromAddress.required' => 'From Address is required.',
+ 'smtpFromAddress.email' => 'Please enter a valid email address.',
+ 'smtpFromName.required' => 'From Name is required.',
+ ]);
+
+ $this->settings->smtp_enabled = false;
+ $this->settings->use_instance_email_settings = false;
+ $this->smtpEnabled = false;
+ $this->useInstanceEmailSettings = false;
+
+ $this->settings->resend_enabled = $this->resendEnabled;
+ $this->settings->resend_api_key = $this->resendApiKey;
+ $this->settings->smtp_from_address = $this->smtpFromAddress;
+ $this->settings->smtp_from_name = $this->smtpFromName;
+
+ $this->settings->save();
+ $this->dispatch('success', 'Resend settings updated.');
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
}
}
@@ -149,7 +320,7 @@ public function sendTestEmail()
'test-email:'.$this->team->id,
$perMinute = 0,
function () {
- $this->team?->notify(new Test($this->testEmailAddress));
+ $this->team?->notify(new Test($this->testEmailAddress, 'email'));
$this->dispatch('success', 'Test Email sent.');
},
$decaySeconds = 10,
@@ -163,70 +334,6 @@ function () {
}
}
- public function instantSaveInstance()
- {
- try {
- $this->smtpEnabled = false;
- $this->resendEnabled = false;
- $this->saveModel();
- } catch (\Throwable $e) {
- return handleError($e, $this);
- }
- }
-
- public function instantSaveSmtpEnabled()
- {
- try {
- $this->validate([
- 'smtpHost' => 'required',
- 'smtpPort' => 'required|numeric',
- ], [
- 'smtpHost.required' => 'SMTP Host is required.',
- 'smtpPort.required' => 'SMTP Port is required.',
- ]);
- $this->resendEnabled = false;
- $this->saveModel();
- } catch (\Throwable $e) {
- $this->smtpEnabled = false;
-
- return handleError($e, $this);
- }
- }
-
- public function instantSaveResend()
- {
- try {
- $this->validate([
- 'resendApiKey' => 'required',
- ], [
- 'resendApiKey.required' => 'Resend API Key is required.',
- ]);
- $this->smtpEnabled = false;
- $this->saveModel();
- } catch (\Throwable $e) {
- $this->resendEnabled = false;
-
- return handleError($e, $this);
- }
- }
-
- public function saveModel()
- {
- $this->syncData(true);
- refreshSession();
- $this->dispatch('success', 'Settings saved.');
- }
-
- public function submit()
- {
- try {
- $this->resetErrorBag();
- $this->saveModel();
- } catch (\Throwable $e) {
- return handleError($e, $this);
- }
- }
-
public function copyFromInstanceSettings()
{
$settings = instanceSettings();
diff --git a/app/Livewire/Notifications/Pushover.php b/app/Livewire/Notifications/Pushover.php
new file mode 100644
index 000000000..f1e4c464d
--- /dev/null
+++ b/app/Livewire/Notifications/Pushover.php
@@ -0,0 +1,184 @@
+ '$refresh'];
+
+ #[Locked]
+ public Team $team;
+
+ #[Locked]
+ public PushoverNotificationSettings $settings;
+
+ #[Validate(['boolean'])]
+ public bool $pushoverEnabled = false;
+
+ #[Validate(['nullable', 'string'])]
+ public ?string $pushoverUserKey = null;
+
+ #[Validate(['nullable', 'string'])]
+ public ?string $pushoverApiToken = null;
+
+ #[Validate(['boolean'])]
+ public bool $deploymentSuccessPushoverNotifications = false;
+
+ #[Validate(['boolean'])]
+ public bool $deploymentFailurePushoverNotifications = true;
+
+ #[Validate(['boolean'])]
+ public bool $statusChangePushoverNotifications = false;
+
+ #[Validate(['boolean'])]
+ public bool $backupSuccessPushoverNotifications = false;
+
+ #[Validate(['boolean'])]
+ public bool $backupFailurePushoverNotifications = true;
+
+ #[Validate(['boolean'])]
+ public bool $scheduledTaskSuccessPushoverNotifications = false;
+
+ #[Validate(['boolean'])]
+ public bool $scheduledTaskFailurePushoverNotifications = true;
+
+ #[Validate(['boolean'])]
+ public bool $dockerCleanupSuccessPushoverNotifications = false;
+
+ #[Validate(['boolean'])]
+ public bool $dockerCleanupFailurePushoverNotifications = true;
+
+ #[Validate(['boolean'])]
+ public bool $serverDiskUsagePushoverNotifications = true;
+
+ #[Validate(['boolean'])]
+ public bool $serverReachablePushoverNotifications = false;
+
+ #[Validate(['boolean'])]
+ public bool $serverUnreachablePushoverNotifications = true;
+
+ public function mount()
+ {
+ try {
+ $this->team = auth()->user()->currentTeam();
+ $this->settings = $this->team->pushoverNotificationSettings;
+ $this->syncData();
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
+
+ public function syncData(bool $toModel = false)
+ {
+ if ($toModel) {
+ $this->validate();
+ $this->settings->pushover_enabled = $this->pushoverEnabled;
+ $this->settings->pushover_user_key = $this->pushoverUserKey;
+ $this->settings->pushover_api_token = $this->pushoverApiToken;
+
+ $this->settings->deployment_success_pushover_notifications = $this->deploymentSuccessPushoverNotifications;
+ $this->settings->deployment_failure_pushover_notifications = $this->deploymentFailurePushoverNotifications;
+ $this->settings->status_change_pushover_notifications = $this->statusChangePushoverNotifications;
+ $this->settings->backup_success_pushover_notifications = $this->backupSuccessPushoverNotifications;
+ $this->settings->backup_failure_pushover_notifications = $this->backupFailurePushoverNotifications;
+ $this->settings->scheduled_task_success_pushover_notifications = $this->scheduledTaskSuccessPushoverNotifications;
+ $this->settings->scheduled_task_failure_pushover_notifications = $this->scheduledTaskFailurePushoverNotifications;
+ $this->settings->docker_cleanup_success_pushover_notifications = $this->dockerCleanupSuccessPushoverNotifications;
+ $this->settings->docker_cleanup_failure_pushover_notifications = $this->dockerCleanupFailurePushoverNotifications;
+ $this->settings->server_disk_usage_pushover_notifications = $this->serverDiskUsagePushoverNotifications;
+ $this->settings->server_reachable_pushover_notifications = $this->serverReachablePushoverNotifications;
+ $this->settings->server_unreachable_pushover_notifications = $this->serverUnreachablePushoverNotifications;
+
+ $this->settings->save();
+ refreshSession();
+ } else {
+ $this->pushoverEnabled = $this->settings->pushover_enabled;
+ $this->pushoverUserKey = $this->settings->pushover_user_key;
+ $this->pushoverApiToken = $this->settings->pushover_api_token;
+
+ $this->deploymentSuccessPushoverNotifications = $this->settings->deployment_success_pushover_notifications;
+ $this->deploymentFailurePushoverNotifications = $this->settings->deployment_failure_pushover_notifications;
+ $this->statusChangePushoverNotifications = $this->settings->status_change_pushover_notifications;
+ $this->backupSuccessPushoverNotifications = $this->settings->backup_success_pushover_notifications;
+ $this->backupFailurePushoverNotifications = $this->settings->backup_failure_pushover_notifications;
+ $this->scheduledTaskSuccessPushoverNotifications = $this->settings->scheduled_task_success_pushover_notifications;
+ $this->scheduledTaskFailurePushoverNotifications = $this->settings->scheduled_task_failure_pushover_notifications;
+ $this->dockerCleanupSuccessPushoverNotifications = $this->settings->docker_cleanup_success_pushover_notifications;
+ $this->dockerCleanupFailurePushoverNotifications = $this->settings->docker_cleanup_failure_pushover_notifications;
+ $this->serverDiskUsagePushoverNotifications = $this->settings->server_disk_usage_pushover_notifications;
+ $this->serverReachablePushoverNotifications = $this->settings->server_reachable_pushover_notifications;
+ $this->serverUnreachablePushoverNotifications = $this->settings->server_unreachable_pushover_notifications;
+ }
+ }
+
+ public function instantSavePushoverEnabled()
+ {
+ try {
+ $this->validate([
+ 'pushoverUserKey' => 'required',
+ 'pushoverApiToken' => 'required',
+ ], [
+ 'pushoverUserKey.required' => 'Pushover User Key is required.',
+ 'pushoverApiToken.required' => 'Pushover API Token is required.',
+ ]);
+ $this->saveModel();
+ } catch (\Throwable $e) {
+ $this->pushoverEnabled = false;
+
+ return handleError($e, $this);
+ } finally {
+ $this->dispatch('refresh');
+ }
+ }
+
+ public function instantSave()
+ {
+ try {
+ $this->syncData(true);
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ } finally {
+ $this->dispatch('refresh');
+ }
+ }
+
+ 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(channel: 'pushover'));
+ $this->dispatch('success', 'Test notification sent.');
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
+
+ public function render()
+ {
+ return view('livewire.notifications.pushover');
+ }
+}
diff --git a/app/Livewire/Notifications/Slack.php b/app/Livewire/Notifications/Slack.php
index 06b7643ea..f47ad8a97 100644
--- a/app/Livewire/Notifications/Slack.php
+++ b/app/Livewire/Notifications/Slack.php
@@ -2,15 +2,23 @@
namespace App\Livewire\Notifications;
+use App\Models\SlackNotificationSettings;
use App\Models\Team;
use App\Notifications\Test;
+use Livewire\Attributes\Locked;
use Livewire\Attributes\Validate;
use Livewire\Component;
class Slack extends Component
{
+ protected $listeners = ['refresh' => '$refresh'];
+
+ #[Locked]
public Team $team;
+ #[Locked]
+ public SlackNotificationSettings $settings;
+
#[Validate(['boolean'])]
public bool $slackEnabled = false;
@@ -18,27 +26,46 @@ class Slack extends Component
public ?string $slackWebhookUrl = null;
#[Validate(['boolean'])]
- public bool $slackNotificationsTest = false;
+ public bool $deploymentSuccessSlackNotifications = false;
#[Validate(['boolean'])]
- public bool $slackNotificationsDeployments = false;
+ public bool $deploymentFailureSlackNotifications = true;
#[Validate(['boolean'])]
- public bool $slackNotificationsStatusChanges = false;
+ public bool $statusChangeSlackNotifications = false;
#[Validate(['boolean'])]
- public bool $slackNotificationsDatabaseBackups = false;
+ public bool $backupSuccessSlackNotifications = false;
#[Validate(['boolean'])]
- public bool $slackNotificationsScheduledTasks = false;
+ public bool $backupFailureSlackNotifications = true;
#[Validate(['boolean'])]
- public bool $slackNotificationsServerDiskUsage = false;
+ public bool $scheduledTaskSuccessSlackNotifications = false;
+
+ #[Validate(['boolean'])]
+ public bool $scheduledTaskFailureSlackNotifications = true;
+
+ #[Validate(['boolean'])]
+ public bool $dockerCleanupSuccessSlackNotifications = false;
+
+ #[Validate(['boolean'])]
+ public bool $dockerCleanupFailureSlackNotifications = true;
+
+ #[Validate(['boolean'])]
+ public bool $serverDiskUsageSlackNotifications = true;
+
+ #[Validate(['boolean'])]
+ public bool $serverReachableSlackNotifications = false;
+
+ #[Validate(['boolean'])]
+ public bool $serverUnreachableSlackNotifications = true;
public function mount()
{
try {
$this->team = auth()->user()->currentTeam();
+ $this->settings = $this->team->slackNotificationSettings;
$this->syncData();
} catch (\Throwable $e) {
return handleError($e, $this);
@@ -49,25 +76,40 @@ 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();
+ $this->settings->slack_enabled = $this->slackEnabled;
+ $this->settings->slack_webhook_url = $this->slackWebhookUrl;
+
+ $this->settings->deployment_success_slack_notifications = $this->deploymentSuccessSlackNotifications;
+ $this->settings->deployment_failure_slack_notifications = $this->deploymentFailureSlackNotifications;
+ $this->settings->status_change_slack_notifications = $this->statusChangeSlackNotifications;
+ $this->settings->backup_success_slack_notifications = $this->backupSuccessSlackNotifications;
+ $this->settings->backup_failure_slack_notifications = $this->backupFailureSlackNotifications;
+ $this->settings->scheduled_task_success_slack_notifications = $this->scheduledTaskSuccessSlackNotifications;
+ $this->settings->scheduled_task_failure_slack_notifications = $this->scheduledTaskFailureSlackNotifications;
+ $this->settings->docker_cleanup_success_slack_notifications = $this->dockerCleanupSuccessSlackNotifications;
+ $this->settings->docker_cleanup_failure_slack_notifications = $this->dockerCleanupFailureSlackNotifications;
+ $this->settings->server_disk_usage_slack_notifications = $this->serverDiskUsageSlackNotifications;
+ $this->settings->server_reachable_slack_notifications = $this->serverReachableSlackNotifications;
+ $this->settings->server_unreachable_slack_notifications = $this->serverUnreachableSlackNotifications;
+
+ $this->settings->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;
+ $this->slackEnabled = $this->settings->slack_enabled;
+ $this->slackWebhookUrl = $this->settings->slack_webhook_url;
+
+ $this->deploymentSuccessSlackNotifications = $this->settings->deployment_success_slack_notifications;
+ $this->deploymentFailureSlackNotifications = $this->settings->deployment_failure_slack_notifications;
+ $this->statusChangeSlackNotifications = $this->settings->status_change_slack_notifications;
+ $this->backupSuccessSlackNotifications = $this->settings->backup_success_slack_notifications;
+ $this->backupFailureSlackNotifications = $this->settings->backup_failure_slack_notifications;
+ $this->scheduledTaskSuccessSlackNotifications = $this->settings->scheduled_task_success_slack_notifications;
+ $this->scheduledTaskFailureSlackNotifications = $this->settings->scheduled_task_failure_slack_notifications;
+ $this->dockerCleanupSuccessSlackNotifications = $this->settings->docker_cleanup_success_slack_notifications;
+ $this->dockerCleanupFailureSlackNotifications = $this->settings->docker_cleanup_failure_slack_notifications;
+ $this->serverDiskUsageSlackNotifications = $this->settings->server_disk_usage_slack_notifications;
+ $this->serverReachableSlackNotifications = $this->settings->server_reachable_slack_notifications;
+ $this->serverUnreachableSlackNotifications = $this->settings->server_unreachable_slack_notifications;
}
}
@@ -84,6 +126,8 @@ public function instantSaveSlackEnabled()
$this->slackEnabled = false;
return handleError($e, $this);
+ } finally {
+ $this->dispatch('refresh');
}
}
@@ -93,6 +137,8 @@ public function instantSave()
$this->syncData(true);
} catch (\Throwable $e) {
return handleError($e, $this);
+ } finally {
+ $this->dispatch('refresh');
}
}
@@ -117,7 +163,7 @@ public function saveModel()
public function sendTestNotification()
{
try {
- $this->team->notify(new Test);
+ $this->team->notify(new Test(channel: 'slack'));
$this->dispatch('success', 'Test notification sent.');
} catch (\Throwable $e) {
return handleError($e, $this);
diff --git a/app/Livewire/Notifications/Telegram.php b/app/Livewire/Notifications/Telegram.php
index 15ec20577..01afb4ac6 100644
--- a/app/Livewire/Notifications/Telegram.php
+++ b/app/Livewire/Notifications/Telegram.php
@@ -3,14 +3,22 @@
namespace App\Livewire\Notifications;
use App\Models\Team;
+use App\Models\TelegramNotificationSettings;
use App\Notifications\Test;
+use Livewire\Attributes\Locked;
use Livewire\Attributes\Validate;
use Livewire\Component;
class Telegram extends Component
{
+ protected $listeners = ['refresh' => '$refresh'];
+
+ #[Locked]
public Team $team;
+ #[Locked]
+ public TelegramNotificationSettings $settings;
+
#[Validate(['boolean'])]
public bool $telegramEnabled = false;
@@ -21,42 +29,82 @@ class Telegram extends Component
public ?string $telegramChatId = null;
#[Validate(['boolean'])]
- public bool $telegramNotificationsTest = false;
+ public bool $deploymentSuccessTelegramNotifications = false;
#[Validate(['boolean'])]
- public bool $telegramNotificationsDeployments = false;
+ public bool $deploymentFailureTelegramNotifications = true;
#[Validate(['boolean'])]
- public bool $telegramNotificationsStatusChanges = false;
+ public bool $statusChangeTelegramNotifications = false;
#[Validate(['boolean'])]
- public bool $telegramNotificationsDatabaseBackups = false;
+ public bool $backupSuccessTelegramNotifications = false;
#[Validate(['boolean'])]
- public bool $telegramNotificationsScheduledTasks = false;
-
- #[Validate(['nullable', 'string'])]
- public ?string $telegramNotificationsTestMessageThreadId = null;
-
- #[Validate(['nullable', 'string'])]
- public ?string $telegramNotificationsDeploymentsMessageThreadId = null;
-
- #[Validate(['nullable', 'string'])]
- public ?string $telegramNotificationsStatusChangesMessageThreadId = null;
-
- #[Validate(['nullable', 'string'])]
- public ?string $telegramNotificationsDatabaseBackupsMessageThreadId = null;
-
- #[Validate(['nullable', 'string'])]
- public ?string $telegramNotificationsScheduledTasksThreadId = null;
+ public bool $backupFailureTelegramNotifications = true;
#[Validate(['boolean'])]
- public bool $telegramNotificationsServerDiskUsage = false;
+ public bool $scheduledTaskSuccessTelegramNotifications = false;
+
+ #[Validate(['boolean'])]
+ public bool $scheduledTaskFailureTelegramNotifications = true;
+
+ #[Validate(['boolean'])]
+ public bool $dockerCleanupSuccessTelegramNotifications = false;
+
+ #[Validate(['boolean'])]
+ public bool $dockerCleanupFailureTelegramNotifications = true;
+
+ #[Validate(['boolean'])]
+ public bool $serverDiskUsageTelegramNotifications = true;
+
+ #[Validate(['boolean'])]
+ public bool $serverReachableTelegramNotifications = false;
+
+ #[Validate(['boolean'])]
+ public bool $serverUnreachableTelegramNotifications = true;
+
+ #[Validate(['nullable', 'string'])]
+ public ?string $telegramNotificationsDeploymentSuccessThreadId = null;
+
+ #[Validate(['nullable', 'string'])]
+ public ?string $telegramNotificationsDeploymentFailureThreadId = null;
+
+ #[Validate(['nullable', 'string'])]
+ public ?string $telegramNotificationsStatusChangeThreadId = null;
+
+ #[Validate(['nullable', 'string'])]
+ public ?string $telegramNotificationsBackupSuccessThreadId = null;
+
+ #[Validate(['nullable', 'string'])]
+ public ?string $telegramNotificationsBackupFailureThreadId = null;
+
+ #[Validate(['nullable', 'string'])]
+ public ?string $telegramNotificationsScheduledTaskSuccessThreadId = null;
+
+ #[Validate(['nullable', 'string'])]
+ public ?string $telegramNotificationsScheduledTaskFailureThreadId = null;
+
+ #[Validate(['nullable', 'string'])]
+ public ?string $telegramNotificationsDockerCleanupSuccessThreadId = null;
+
+ #[Validate(['nullable', 'string'])]
+ public ?string $telegramNotificationsDockerCleanupFailureThreadId = null;
+
+ #[Validate(['nullable', 'string'])]
+ public ?string $telegramNotificationsServerDiskUsageThreadId = null;
+
+ #[Validate(['nullable', 'string'])]
+ public ?string $telegramNotificationsServerReachableThreadId = null;
+
+ #[Validate(['nullable', 'string'])]
+ public ?string $telegramNotificationsServerUnreachableThreadId = null;
public function mount()
{
try {
$this->team = auth()->user()->currentTeam();
+ $this->settings = $this->team->telegramNotificationSettings;
$this->syncData();
} catch (\Throwable $e) {
return handleError($e, $this);
@@ -67,39 +115,68 @@ public function syncData(bool $toModel = false)
{
if ($toModel) {
$this->validate();
- $this->team->telegram_enabled = $this->telegramEnabled;
- $this->team->telegram_token = $this->telegramToken;
- $this->team->telegram_chat_id = $this->telegramChatId;
- $this->team->telegram_notifications_test = $this->telegramNotificationsTest;
- $this->team->telegram_notifications_deployments = $this->telegramNotificationsDeployments;
- $this->team->telegram_notifications_status_changes = $this->telegramNotificationsStatusChanges;
- $this->team->telegram_notifications_database_backups = $this->telegramNotificationsDatabaseBackups;
- $this->team->telegram_notifications_scheduled_tasks = $this->telegramNotificationsScheduledTasks;
- $this->team->telegram_notifications_test_message_thread_id = $this->telegramNotificationsTestMessageThreadId;
- $this->team->telegram_notifications_deployments_message_thread_id = $this->telegramNotificationsDeploymentsMessageThreadId;
- $this->team->telegram_notifications_status_changes_message_thread_id = $this->telegramNotificationsStatusChangesMessageThreadId;
- $this->team->telegram_notifications_database_backups_message_thread_id = $this->telegramNotificationsDatabaseBackupsMessageThreadId;
- $this->team->telegram_notifications_scheduled_tasks_thread_id = $this->telegramNotificationsScheduledTasksThreadId;
- $this->team->telegram_notifications_server_disk_usage = $this->telegramNotificationsServerDiskUsage;
- $this->team->save();
- refreshSession();
- } else {
- $this->telegramEnabled = $this->team->telegram_enabled;
- $this->telegramToken = $this->team->telegram_token;
- $this->telegramChatId = $this->team->telegram_chat_id;
- $this->telegramNotificationsTest = $this->team->telegram_notifications_test;
- $this->telegramNotificationsDeployments = $this->team->telegram_notifications_deployments;
- $this->telegramNotificationsStatusChanges = $this->team->telegram_notifications_status_changes;
- $this->telegramNotificationsDatabaseBackups = $this->team->telegram_notifications_database_backups;
- $this->telegramNotificationsScheduledTasks = $this->team->telegram_notifications_scheduled_tasks;
- $this->telegramNotificationsTestMessageThreadId = $this->team->telegram_notifications_test_message_thread_id;
- $this->telegramNotificationsDeploymentsMessageThreadId = $this->team->telegram_notifications_deployments_message_thread_id;
- $this->telegramNotificationsStatusChangesMessageThreadId = $this->team->telegram_notifications_status_changes_message_thread_id;
- $this->telegramNotificationsDatabaseBackupsMessageThreadId = $this->team->telegram_notifications_database_backups_message_thread_id;
- $this->telegramNotificationsScheduledTasksThreadId = $this->team->telegram_notifications_scheduled_tasks_thread_id;
- $this->telegramNotificationsServerDiskUsage = $this->team->telegram_notifications_server_disk_usage;
- }
+ $this->settings->telegram_enabled = $this->telegramEnabled;
+ $this->settings->telegram_token = $this->telegramToken;
+ $this->settings->telegram_chat_id = $this->telegramChatId;
+ $this->settings->deployment_success_telegram_notifications = $this->deploymentSuccessTelegramNotifications;
+ $this->settings->deployment_failure_telegram_notifications = $this->deploymentFailureTelegramNotifications;
+ $this->settings->status_change_telegram_notifications = $this->statusChangeTelegramNotifications;
+ $this->settings->backup_success_telegram_notifications = $this->backupSuccessTelegramNotifications;
+ $this->settings->backup_failure_telegram_notifications = $this->backupFailureTelegramNotifications;
+ $this->settings->scheduled_task_success_telegram_notifications = $this->scheduledTaskSuccessTelegramNotifications;
+ $this->settings->scheduled_task_failure_telegram_notifications = $this->scheduledTaskFailureTelegramNotifications;
+ $this->settings->docker_cleanup_success_telegram_notifications = $this->dockerCleanupSuccessTelegramNotifications;
+ $this->settings->docker_cleanup_failure_telegram_notifications = $this->dockerCleanupFailureTelegramNotifications;
+ $this->settings->server_disk_usage_telegram_notifications = $this->serverDiskUsageTelegramNotifications;
+ $this->settings->server_reachable_telegram_notifications = $this->serverReachableTelegramNotifications;
+ $this->settings->server_unreachable_telegram_notifications = $this->serverUnreachableTelegramNotifications;
+
+ $this->settings->telegram_notifications_deployment_success_thread_id = $this->telegramNotificationsDeploymentSuccessThreadId;
+ $this->settings->telegram_notifications_deployment_failure_thread_id = $this->telegramNotificationsDeploymentFailureThreadId;
+ $this->settings->telegram_notifications_status_change_thread_id = $this->telegramNotificationsStatusChangeThreadId;
+ $this->settings->telegram_notifications_backup_success_thread_id = $this->telegramNotificationsBackupSuccessThreadId;
+ $this->settings->telegram_notifications_backup_failure_thread_id = $this->telegramNotificationsBackupFailureThreadId;
+ $this->settings->telegram_notifications_scheduled_task_success_thread_id = $this->telegramNotificationsScheduledTaskSuccessThreadId;
+ $this->settings->telegram_notifications_scheduled_task_failure_thread_id = $this->telegramNotificationsScheduledTaskFailureThreadId;
+ $this->settings->telegram_notifications_docker_cleanup_success_thread_id = $this->telegramNotificationsDockerCleanupSuccessThreadId;
+ $this->settings->telegram_notifications_docker_cleanup_failure_thread_id = $this->telegramNotificationsDockerCleanupFailureThreadId;
+ $this->settings->telegram_notifications_server_disk_usage_thread_id = $this->telegramNotificationsServerDiskUsageThreadId;
+ $this->settings->telegram_notifications_server_reachable_thread_id = $this->telegramNotificationsServerReachableThreadId;
+ $this->settings->telegram_notifications_server_unreachable_thread_id = $this->telegramNotificationsServerUnreachableThreadId;
+
+ $this->settings->save();
+ } else {
+ $this->telegramEnabled = $this->settings->telegram_enabled;
+ $this->telegramToken = $this->settings->telegram_token;
+ $this->telegramChatId = $this->settings->telegram_chat_id;
+
+ $this->deploymentSuccessTelegramNotifications = $this->settings->deployment_success_telegram_notifications;
+ $this->deploymentFailureTelegramNotifications = $this->settings->deployment_failure_telegram_notifications;
+ $this->statusChangeTelegramNotifications = $this->settings->status_change_telegram_notifications;
+ $this->backupSuccessTelegramNotifications = $this->settings->backup_success_telegram_notifications;
+ $this->backupFailureTelegramNotifications = $this->settings->backup_failure_telegram_notifications;
+ $this->scheduledTaskSuccessTelegramNotifications = $this->settings->scheduled_task_success_telegram_notifications;
+ $this->scheduledTaskFailureTelegramNotifications = $this->settings->scheduled_task_failure_telegram_notifications;
+ $this->dockerCleanupSuccessTelegramNotifications = $this->settings->docker_cleanup_success_telegram_notifications;
+ $this->dockerCleanupFailureTelegramNotifications = $this->settings->docker_cleanup_failure_telegram_notifications;
+ $this->serverDiskUsageTelegramNotifications = $this->settings->server_disk_usage_telegram_notifications;
+ $this->serverReachableTelegramNotifications = $this->settings->server_reachable_telegram_notifications;
+ $this->serverUnreachableTelegramNotifications = $this->settings->server_unreachable_telegram_notifications;
+
+ $this->telegramNotificationsDeploymentSuccessThreadId = $this->settings->telegram_notifications_deployment_success_thread_id;
+ $this->telegramNotificationsDeploymentFailureThreadId = $this->settings->telegram_notifications_deployment_failure_thread_id;
+ $this->telegramNotificationsStatusChangeThreadId = $this->settings->telegram_notifications_status_change_thread_id;
+ $this->telegramNotificationsBackupSuccessThreadId = $this->settings->telegram_notifications_backup_success_thread_id;
+ $this->telegramNotificationsBackupFailureThreadId = $this->settings->telegram_notifications_backup_failure_thread_id;
+ $this->telegramNotificationsScheduledTaskSuccessThreadId = $this->settings->telegram_notifications_scheduled_task_success_thread_id;
+ $this->telegramNotificationsScheduledTaskFailureThreadId = $this->settings->telegram_notifications_scheduled_task_failure_thread_id;
+ $this->telegramNotificationsDockerCleanupSuccessThreadId = $this->settings->telegram_notifications_docker_cleanup_success_thread_id;
+ $this->telegramNotificationsDockerCleanupFailureThreadId = $this->settings->telegram_notifications_docker_cleanup_failure_thread_id;
+ $this->telegramNotificationsServerDiskUsageThreadId = $this->settings->telegram_notifications_server_disk_usage_thread_id;
+ $this->telegramNotificationsServerReachableThreadId = $this->settings->telegram_notifications_server_reachable_thread_id;
+ $this->telegramNotificationsServerUnreachableThreadId = $this->settings->telegram_notifications_server_unreachable_thread_id;
+ }
}
public function instantSave()
@@ -108,6 +185,8 @@ public function instantSave()
$this->syncData(true);
} catch (\Throwable $e) {
return handleError($e, $this);
+ } finally {
+ $this->dispatch('refresh');
}
}
@@ -137,6 +216,8 @@ public function instantSaveTelegramEnabled()
$this->telegramEnabled = false;
return handleError($e, $this);
+ } finally {
+ $this->dispatch('refresh');
}
}
@@ -150,7 +231,7 @@ public function saveModel()
public function sendTestNotification()
{
try {
- $this->team->notify(new Test);
+ $this->team->notify(new Test(channel: 'telegram'));
$this->dispatch('success', 'Test notification sent.');
} catch (\Throwable $e) {
return handleError($e, $this);
diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php
index ff29b74e9..d1cc3476c 100644
--- a/app/Livewire/Project/Application/General.php
+++ b/app/Livewire/Project/Application/General.php
@@ -327,7 +327,7 @@ public function checkFqdns($showToaster = true)
}
}
- public function set_redirect()
+ public function setRedirect()
{
try {
$has_www = collect($this->application->fqdns)->filter(fn ($fqdn) => str($fqdn)->contains('www.'))->count();
@@ -360,10 +360,10 @@ public function submit($showToaster = true)
if ($warning) {
$this->dispatch('warning', __('warning.sslipdomain'));
}
- $this->resetDefaultLabels();
+ // $this->resetDefaultLabels();
if ($this->application->isDirty('redirect')) {
- $this->set_redirect();
+ $this->setRedirect();
}
$this->checkFqdns();
diff --git a/app/Livewire/Project/Database/BackupNow.php b/app/Livewire/Project/Database/BackupNow.php
index 9c9c175e2..3cd360562 100644
--- a/app/Livewire/Project/Database/BackupNow.php
+++ b/app/Livewire/Project/Database/BackupNow.php
@@ -9,11 +9,9 @@ class BackupNow extends Component
{
public $backup;
- public function backup_now()
+ public function backupNow()
{
- dispatch(new DatabaseBackupJob(
- backup: $this->backup
- ));
+ DatabaseBackupJob::dispatch($this->backup);
$this->dispatch('success', 'Backup queued. It will be available in a few minutes.');
}
}
diff --git a/app/Livewire/Project/Resource/Index.php b/app/Livewire/Project/Resource/Index.php
index 283496887..0c2ea802a 100644
--- a/app/Livewire/Project/Resource/Index.php
+++ b/app/Livewire/Project/Resource/Index.php
@@ -46,125 +46,84 @@ public function mount()
return redirect()->route('dashboard');
}
$this->project = $project;
- $this->environment = $environment;
- $this->applications = $this->environment->applications->load(['tags']);
+ $this->environment = $environment->loadCount([
+ 'applications',
+ 'redis',
+ 'postgresqls',
+ 'mysqls',
+ 'keydbs',
+ 'dragonflies',
+ 'clickhouses',
+ 'mariadbs',
+ 'mongodbs',
+ 'services',
+ ]);
+
+ // Eager load all relationships for applications including nested ones
+ $this->applications = $this->environment->applications()->with([
+ 'tags',
+ 'additional_servers.settings',
+ 'additional_networks',
+ 'destination.server.settings',
+ 'settings',
+ ])->get()->sortBy('name');
$this->applications = $this->applications->map(function ($application) {
- if (data_get($application, 'environment.project.uuid')) {
- $application->hrefLink = route('project.application.configuration', [
- 'project_uuid' => data_get($application, 'environment.project.uuid'),
- 'environment_name' => data_get($application, 'environment.name'),
- 'application_uuid' => data_get($application, 'uuid'),
- ]);
- }
+ $application->hrefLink = route('project.application.configuration', [
+ 'project_uuid' => $this->project->uuid,
+ 'application_uuid' => $application->uuid,
+ 'environment_name' => $this->environment->name,
+ ]);
return $application;
});
- $this->postgresqls = $this->environment->postgresqls->load(['tags'])->sortBy('name');
- $this->postgresqls = $this->postgresqls->map(function ($postgresql) {
- if (data_get($postgresql, 'environment.project.uuid')) {
- $postgresql->hrefLink = route('project.database.configuration', [
- 'project_uuid' => data_get($postgresql, 'environment.project.uuid'),
- 'environment_name' => data_get($postgresql, 'environment.name'),
- 'database_uuid' => data_get($postgresql, 'uuid'),
- ]);
- }
- return $postgresql;
- });
- $this->redis = $this->environment->redis->load(['tags'])->sortBy('name');
- $this->redis = $this->redis->map(function ($redis) {
- if (data_get($redis, 'environment.project.uuid')) {
- $redis->hrefLink = route('project.database.configuration', [
- 'project_uuid' => data_get($redis, 'environment.project.uuid'),
- 'environment_name' => data_get($redis, 'environment.name'),
- 'database_uuid' => data_get($redis, 'uuid'),
- ]);
- }
+ // Load all database resources in a single query per type
+ $databaseTypes = [
+ 'postgresqls' => 'postgresqls',
+ 'redis' => 'redis',
+ 'mongodbs' => 'mongodbs',
+ 'mysqls' => 'mysqls',
+ 'mariadbs' => 'mariadbs',
+ 'keydbs' => 'keydbs',
+ 'dragonflies' => 'dragonflies',
+ 'clickhouses' => 'clickhouses',
+ ];
- return $redis;
- });
- $this->mongodbs = $this->environment->mongodbs->load(['tags'])->sortBy('name');
- $this->mongodbs = $this->mongodbs->map(function ($mongodb) {
- if (data_get($mongodb, 'environment.project.uuid')) {
- $mongodb->hrefLink = route('project.database.configuration', [
- 'project_uuid' => data_get($mongodb, 'environment.project.uuid'),
- 'environment_name' => data_get($mongodb, 'environment.name'),
- 'database_uuid' => data_get($mongodb, 'uuid'),
- ]);
- }
+ // Load all server-related data first to prevent duplicate queries
+ $serverData = $this->environment->applications()
+ ->with(['destination.server.settings'])
+ ->get()
+ ->pluck('destination.server')
+ ->filter()
+ ->unique('id');
- return $mongodb;
- });
- $this->mysqls = $this->environment->mysqls->load(['tags'])->sortBy('name');
- $this->mysqls = $this->mysqls->map(function ($mysql) {
- if (data_get($mysql, 'environment.project.uuid')) {
- $mysql->hrefLink = route('project.database.configuration', [
- 'project_uuid' => data_get($mysql, 'environment.project.uuid'),
- 'environment_name' => data_get($mysql, 'environment.name'),
- 'database_uuid' => data_get($mysql, 'uuid'),
+ foreach ($databaseTypes as $property => $relation) {
+ $this->{$property} = $this->environment->{$relation}()->with([
+ 'tags',
+ 'destination.server.settings',
+ ])->get()->sortBy('name');
+ $this->{$property} = $this->{$property}->map(function ($db) {
+ $db->hrefLink = route('project.database.configuration', [
+ 'project_uuid' => $this->project->uuid,
+ 'database_uuid' => $db->uuid,
+ 'environment_name' => $this->environment->name,
]);
- }
- return $mysql;
- });
- $this->mariadbs = $this->environment->mariadbs->load(['tags'])->sortBy('name');
- $this->mariadbs = $this->mariadbs->map(function ($mariadb) {
- if (data_get($mariadb, 'environment.project.uuid')) {
- $mariadb->hrefLink = route('project.database.configuration', [
- 'project_uuid' => data_get($mariadb, 'environment.project.uuid'),
- 'environment_name' => data_get($mariadb, 'environment.name'),
- 'database_uuid' => data_get($mariadb, 'uuid'),
- ]);
- }
+ return $db;
+ });
+ }
- return $mariadb;
- });
- $this->keydbs = $this->environment->keydbs->load(['tags'])->sortBy('name');
- $this->keydbs = $this->keydbs->map(function ($keydb) {
- if (data_get($keydb, 'environment.project.uuid')) {
- $keydb->hrefLink = route('project.database.configuration', [
- 'project_uuid' => data_get($keydb, 'environment.project.uuid'),
- 'environment_name' => data_get($keydb, 'environment.name'),
- 'database_uuid' => data_get($keydb, 'uuid'),
- ]);
- }
-
- return $keydb;
- });
- $this->dragonflies = $this->environment->dragonflies->load(['tags'])->sortBy('name');
- $this->dragonflies = $this->dragonflies->map(function ($dragonfly) {
- if (data_get($dragonfly, 'environment.project.uuid')) {
- $dragonfly->hrefLink = route('project.database.configuration', [
- 'project_uuid' => data_get($dragonfly, 'environment.project.uuid'),
- 'environment_name' => data_get($dragonfly, 'environment.name'),
- 'database_uuid' => data_get($dragonfly, 'uuid'),
- ]);
- }
-
- return $dragonfly;
- });
- $this->clickhouses = $this->environment->clickhouses->load(['tags'])->sortBy('name');
- $this->clickhouses = $this->clickhouses->map(function ($clickhouse) {
- if (data_get($clickhouse, 'environment.project.uuid')) {
- $clickhouse->hrefLink = route('project.database.configuration', [
- 'project_uuid' => data_get($clickhouse, 'environment.project.uuid'),
- 'environment_name' => data_get($clickhouse, 'environment.name'),
- 'database_uuid' => data_get($clickhouse, 'uuid'),
- ]);
- }
-
- return $clickhouse;
- });
- $this->services = $this->environment->services->load(['tags'])->sortBy('name');
+ // Load services with their tags and server
+ $this->services = $this->environment->services()->with([
+ 'tags',
+ 'destination.server.settings',
+ ])->get()->sortBy('name');
$this->services = $this->services->map(function ($service) {
- if (data_get($service, 'environment.project.uuid')) {
- $service->hrefLink = route('project.service.configuration', [
- 'project_uuid' => data_get($service, 'environment.project.uuid'),
- 'environment_name' => data_get($service, 'environment.name'),
- 'service_uuid' => data_get($service, 'uuid'),
- ]);
- $service->status = $service->status();
- }
+ $service->hrefLink = route('project.service.configuration', [
+ 'project_uuid' => $this->project->uuid,
+ 'service_uuid' => $service->uuid,
+ 'environment_name' => $this->environment->name,
+ ]);
return $service;
});
diff --git a/app/Livewire/Project/Service/Navbar.php b/app/Livewire/Project/Service/Navbar.php
index ee43dc911..22fc1c0d6 100644
--- a/app/Livewire/Project/Service/Navbar.php
+++ b/app/Livewire/Project/Service/Navbar.php
@@ -27,7 +27,7 @@ class Navbar extends Component
public function mount()
{
- if (str($this->service->status())->contains('running') && is_null($this->service->config_hash)) {
+ if (str($this->service->status)->contains('running') && is_null($this->service->config_hash)) {
$this->service->isConfigurationChanged(true);
$this->dispatch('configurationChanged');
}
diff --git a/app/Livewire/Project/Shared/ResourceOperations.php b/app/Livewire/Project/Shared/ResourceOperations.php
index e67df6aa9..de7bb3c05 100644
--- a/app/Livewire/Project/Shared/ResourceOperations.php
+++ b/app/Livewire/Project/Shared/ResourceOperations.php
@@ -42,9 +42,11 @@ public function cloneTo($destination_id)
$uuid = (string) new Cuid2;
$server = $new_destination->server;
if ($this->resource->getMorphClass() === \App\Models\Application::class) {
+ $name = 'clone-of-'.str($this->resource->name)->limit(20).'-'.$uuid;
+
$new_resource = $this->resource->replicate()->fill([
'uuid' => $uuid,
- 'name' => $this->resource->name.'-clone-'.$uuid,
+ 'name' => $name,
'fqdn' => generateFqdn($server, $uuid),
'status' => 'exited',
'destination_id' => $new_destination->id,
@@ -64,8 +66,12 @@ public function cloneTo($destination_id)
}
$persistentVolumes = $this->resource->persistentStorages()->get();
foreach ($persistentVolumes as $volume) {
+ $volumeName = str($volume->name)->replace($this->resource->uuid, $new_resource->uuid)->value();
+ if ($volumeName === $volume->name) {
+ $volumeName = $new_resource->uuid.'-'.str($volume->name)->afterLast('-');
+ }
$newPersistentVolume = $volume->replicate()->fill([
- 'name' => $new_resource->uuid.'-'.str($volume->name)->afterLast('-'),
+ 'name' => $volumeName,
'resource_id' => $new_resource->id,
]);
$newPersistentVolume->save();
diff --git a/app/Livewire/Settings/Index.php b/app/Livewire/Settings/Index.php
index c1be35ced..7adb0f8a7 100644
--- a/app/Livewire/Settings/Index.php
+++ b/app/Livewire/Settings/Index.php
@@ -23,9 +23,6 @@ class Index extends Component
#[Validate('nullable|string|max:255')]
public ?string $fqdn = null;
- #[Validate('nullable|string|max:255')]
- public ?string $resale_license = null;
-
#[Validate('required|integer|min:1025|max:65535')]
public int $public_port_min;
@@ -83,7 +80,6 @@ public function mount()
} else {
$this->settings = instanceSettings();
$this->fqdn = $this->settings->fqdn;
- $this->resale_license = $this->settings->resale_license;
$this->public_port_min = $this->settings->public_port_min;
$this->public_port_max = $this->settings->public_port_max;
$this->custom_dns_servers = $this->settings->custom_dns_servers;
@@ -122,7 +118,6 @@ public function instantSave($isSave = true)
}
$this->settings->fqdn = $this->fqdn;
- $this->settings->resale_license = $this->resale_license;
$this->settings->public_port_min = $this->public_port_min;
$this->settings->public_port_max = $this->public_port_max;
$this->settings->custom_dns_servers = $this->custom_dns_servers;
diff --git a/app/Livewire/SettingsEmail.php b/app/Livewire/SettingsEmail.php
index abf3a12f9..1c5edb108 100644
--- a/app/Livewire/SettingsEmail.php
+++ b/app/Livewire/SettingsEmail.php
@@ -3,6 +3,10 @@
namespace App\Livewire;
use App\Models\InstanceSettings;
+use App\Models\Team;
+use App\Notifications\Test;
+use Illuminate\Support\Facades\RateLimiter;
+use Livewire\Attributes\Locked;
use Livewire\Attributes\Validate;
use Livewire\Component;
@@ -10,9 +14,21 @@ class SettingsEmail extends Component
{
public InstanceSettings $settings;
+ #[Locked]
+ public Team $team;
+
#[Validate(['boolean'])]
public bool $smtpEnabled = false;
+ #[Validate(['nullable', 'email'])]
+ public ?string $smtpFromAddress = null;
+
+ #[Validate(['nullable', 'string'])]
+ public ?string $smtpFromName = null;
+
+ #[Validate(['nullable', 'string'])]
+ public ?string $smtpRecipients = null;
+
#[Validate(['nullable', 'string'])]
public ?string $smtpHost = null;
@@ -20,29 +36,26 @@ class SettingsEmail extends Component
public ?int $smtpPort = null;
#[Validate(['nullable', 'string', 'in:tls,ssl,none'])]
- public ?string $smtpEncryption = null;
+ public ?string $smtpEncryption = 'tls';
#[Validate(['nullable', 'string'])]
public ?string $smtpUsername = null;
- #[Validate(['nullable'])]
+ #[Validate(['nullable', 'string'])]
public ?string $smtpPassword = null;
#[Validate(['nullable', 'numeric'])]
public ?int $smtpTimeout = null;
- #[Validate(['nullable', 'email'])]
- public ?string $smtpFromAddress = null;
-
- #[Validate(['nullable', 'string'])]
- public ?string $smtpFromName = null;
-
#[Validate(['boolean'])]
public bool $resendEnabled = false;
#[Validate(['nullable', 'string'])]
public ?string $resendApiKey = null;
+ #[Validate(['nullable', 'email'])]
+ public ?string $testEmailAddress = null;
+
public function mount()
{
if (isInstanceAdmin() === false) {
@@ -50,6 +63,8 @@ public function mount()
}
$this->settings = instanceSettings();
$this->syncData();
+ $this->team = auth()->user()->currentTeam();
+ $this->testEmailAddress = auth()->user()->email;
}
public function syncData(bool $toModel = false)
@@ -90,7 +105,7 @@ public function submit()
try {
$this->resetErrorBag();
$this->syncData(true);
- $this->dispatch('success', 'Settings saved.');
+ $this->dispatch('success', 'Transactional email settings updated.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -99,19 +114,129 @@ public function submit()
public function instantSave(string $type)
{
try {
+ $this->resetErrorBag();
+
if ($type === 'SMTP') {
- $this->resendEnabled = false;
- } else {
- $this->smtpEnabled = false;
- }
- $this->syncData(true);
- if ($this->smtpEnabled || $this->resendEnabled) {
- $this->dispatch('success', "{$type} enabled.");
- } else {
- $this->dispatch('success', "{$type} disabled.");
+ $this->submitSmtp();
+ } elseif ($type === 'Resend') {
+ $this->submitResend();
}
+
} catch (\Throwable $e) {
+ if ($type === 'SMTP') {
+ $this->smtpEnabled = false;
+ } elseif ($type === 'Resend') {
+ $this->resendEnabled = false;
+ }
+
return handleError($e, $this);
}
}
+
+ public function submitSmtp()
+ {
+ try {
+ $this->validate([
+ 'smtpEnabled' => 'boolean',
+ 'smtpFromAddress' => 'required|email',
+ 'smtpFromName' => 'required|string',
+ 'smtpHost' => 'required|string',
+ 'smtpPort' => 'required|numeric',
+ 'smtpEncryption' => 'required|string|in:tls,ssl,none',
+ 'smtpUsername' => 'nullable|string',
+ 'smtpPassword' => 'nullable|string',
+ 'smtpTimeout' => 'nullable|numeric',
+ ], [
+ 'smtpFromAddress.required' => 'From Address is required.',
+ 'smtpFromAddress.email' => 'Please enter a valid email address.',
+ 'smtpFromName.required' => 'From Name is required.',
+ 'smtpHost.required' => 'SMTP Host is required.',
+ 'smtpPort.required' => 'SMTP Port is required.',
+ 'smtpPort.numeric' => 'SMTP Port must be a number.',
+ 'smtpEncryption.required' => 'Encryption type is required.',
+ ]);
+
+ $this->resendEnabled = false;
+ $this->settings->resend_enabled = false;
+
+ $this->settings->smtp_enabled = $this->smtpEnabled;
+ $this->settings->smtp_host = $this->smtpHost;
+ $this->settings->smtp_port = $this->smtpPort;
+ $this->settings->smtp_encryption = $this->smtpEncryption;
+ $this->settings->smtp_username = $this->smtpUsername;
+ $this->settings->smtp_password = $this->smtpPassword;
+ $this->settings->smtp_timeout = $this->smtpTimeout;
+ $this->settings->smtp_from_address = $this->smtpFromAddress;
+ $this->settings->smtp_from_name = $this->smtpFromName;
+
+ $this->settings->save();
+
+ $this->dispatch('success', 'SMTP settings updated.');
+ } catch (\Throwable $e) {
+ $this->smtpEnabled = false;
+
+ return handleError($e);
+ }
+ }
+
+ public function submitResend()
+ {
+ try {
+ $this->validate([
+ 'resendEnabled' => 'boolean',
+ 'resendApiKey' => 'required|string',
+ 'smtpFromAddress' => 'required|email',
+ 'smtpFromName' => 'required|string',
+ ], [
+ 'resendApiKey.required' => 'Resend API Key is required.',
+ 'smtpFromAddress.required' => 'From Address is required.',
+ 'smtpFromAddress.email' => 'Please enter a valid email address.',
+ 'smtpFromName.required' => 'From Name is required.',
+ ]);
+
+ $this->smtpEnabled = false;
+ $this->settings->smtp_enabled = false;
+
+ $this->settings->resend_enabled = $this->resendEnabled;
+ $this->settings->resend_api_key = $this->resendApiKey;
+ $this->settings->smtp_from_address = $this->smtpFromAddress;
+ $this->settings->smtp_from_name = $this->smtpFromName;
+
+ $this->settings->save();
+
+ $this->dispatch('success', 'Resend settings updated.');
+ } catch (\Throwable $e) {
+ $this->resendEnabled = false;
+
+ return handleError($e);
+ }
+ }
+
+ public function sendTestEmail()
+ {
+ try {
+ $this->validate([
+ 'testEmailAddress' => 'required|email',
+ ], [
+ 'testEmailAddress.required' => 'Test email address is required.',
+ 'testEmailAddress.email' => 'Please enter a valid email address.',
+ ]);
+
+ $executed = RateLimiter::attempt(
+ 'test-email:'.$this->team->id,
+ $perMinute = 0,
+ function () {
+ $this->team?->notify(new Test($this->testEmailAddress, 'email'));
+ $this->dispatch('success', 'Test Email sent.');
+ },
+ $decaySeconds = 10,
+ );
+
+ if (! $executed) {
+ throw new \Exception('Too many messages sent!');
+ }
+ } catch (\Throwable $e) {
+ return handleError($e);
+ }
+ }
}
diff --git a/app/Livewire/SettingsOauth.php b/app/Livewire/SettingsOauth.php
index 17b3b89a3..e23f94a73 100644
--- a/app/Livewire/SettingsOauth.php
+++ b/app/Livewire/SettingsOauth.php
@@ -17,6 +17,7 @@ protected function rules()
$carry["oauth_settings_map.$setting->provider.client_secret"] = 'nullable';
$carry["oauth_settings_map.$setting->provider.redirect_uri"] = 'nullable';
$carry["oauth_settings_map.$setting->provider.tenant"] = 'nullable';
+ $carry["oauth_settings_map.$setting->provider.base_url"] = 'nullable';
return $carry;
}, []);
@@ -34,16 +35,30 @@ public function mount()
}, []);
}
- private function updateOauthSettings()
+ private function updateOauthSettings(?string $provider = null)
{
- foreach (array_values($this->oauth_settings_map) as &$setting) {
- $setting->save();
+ if ($provider) {
+ $oauth = $this->oauth_settings_map[$provider];
+ if (! $oauth->couldBeEnabled()) {
+ $oauth->update(['enabled' => false]);
+ throw new \Exception('OAuth settings are not complete for '.$oauth->provider.'. Please fill in all required fields.');
+ }
+ $oauth->save();
+ $this->dispatch('success', 'OAuth settings for '.$oauth->provider.' updated successfully!');
+ } else {
+ foreach (array_values($this->oauth_settings_map) as &$setting) {
+ $setting->save();
+ }
}
}
- public function instantSave()
+ public function instantSave(string $provider)
{
- $this->updateOauthSettings();
+ try {
+ $this->updateOauthSettings($provider);
+ } catch (\Exception $e) {
+ return handleError($e, $this);
+ }
}
public function submit()
diff --git a/app/Livewire/Waitlist/Index.php b/app/Livewire/Waitlist/Index.php
deleted file mode 100644
index 0524b495c..000000000
--- a/app/Livewire/Waitlist/Index.php
+++ /dev/null
@@ -1,70 +0,0 @@
- 'required|email',
- ];
-
- public function render()
- {
- return view('livewire.waitlist.index')->layout('layouts.simple');
- }
-
- public function mount()
- {
- if (config('constants.waitlist.enabled') == false) {
- return redirect()->route('register');
- }
- $this->waitingInLine = Waitlist::whereVerified(true)->count();
- $this->users = User::count();
- if (isDev()) {
- $this->email = 'waitlist@example.com';
- }
- }
-
- public function submit()
- {
- $this->validate();
- try {
- $already_registered = User::whereEmail($this->email)->first();
- if ($already_registered) {
- throw new \Exception('You are already on the waitlist or registered. Please check your email to verify your email address or contact support.');
- }
- $found = Waitlist::where('email', $this->email)->first();
- if ($found) {
- if (! $found->verified) {
- $this->dispatch('error', 'You are already on the waitlist. Please check your email to verify your email address.');
-
- return;
- }
- $this->dispatch('error', 'You are already on the waitlist. You will be notified when your turn comes. Thank you.');
-
- return;
- }
- $waitlist = Waitlist::create([
- 'email' => Str::lower($this->email),
- 'type' => 'registration',
- ]);
-
- $this->dispatch('success', 'Check your email to verify your email address.');
- dispatch(new SendConfirmationForWaitlistJob($this->email, $waitlist->uuid));
- } catch (\Throwable $e) {
- return handleError($e, $this);
- }
- }
-}
diff --git a/app/Models/Application.php b/app/Models/Application.php
index d1efd3f33..bfb2a1041 100644
--- a/app/Models/Application.php
+++ b/app/Models/Application.php
@@ -115,6 +115,12 @@ class Application extends BaseModel
protected static function booted()
{
+ static::addGlobalScope('withRelations', function ($builder) {
+ $builder->withCount([
+ 'additional_servers',
+ 'additional_networks',
+ ]);
+ });
static::saving(function ($application) {
$payload = [];
if ($application->isDirty('fqdn')) {
@@ -327,7 +333,7 @@ public function link()
return null;
}
- public function failedTaskLink($task_uuid)
+ public function taskLink($task_uuid)
{
if (data_get($this, 'environment.project.uuid')) {
$route = route('project.application.scheduled-tasks', [
@@ -551,20 +557,21 @@ protected function serverStatus(): Attribute
{
return Attribute::make(
get: function () {
- if ($this->additional_servers->count() === 0) {
- return $this->destination->server->isFunctional();
- } else {
- $additional_servers_status = $this->additional_servers->pluck('pivot.status');
- $main_server_status = $this->destination->server->isFunctional();
- foreach ($additional_servers_status as $status) {
- $server_status = str($status)->before(':')->value();
- if ($server_status !== 'running') {
- return false;
- }
- }
-
- return $main_server_status;
+ if (! $this->relationLoaded('additional_servers') || $this->additional_servers->count() === 0) {
+ return $this->destination?->server?->isFunctional() ?? false;
}
+
+ $additional_servers_status = $this->additional_servers->pluck('pivot.status');
+ $main_server_status = $this->destination?->server?->isFunctional() ?? false;
+
+ foreach ($additional_servers_status as $status) {
+ $server_status = str($status)->before(':')->value();
+ if ($server_status !== 'running') {
+ return false;
+ }
+ }
+
+ return $main_server_status;
}
);
}
@@ -1331,7 +1338,9 @@ public function loadComposeFile($isInit = false)
$currentPath = '';
foreach ($parts as $part) {
$currentPath .= ($currentPath ? '/' : '').$part;
- $paths->push($currentPath);
+ if (str($currentPath)->isNotEmpty()) {
+ $paths->push($currentPath);
+ }
}
return $paths;
@@ -1341,7 +1350,7 @@ public function loadComposeFile($isInit = false)
"mkdir -p /tmp/{$uuid}",
"cd /tmp/{$uuid}",
$cloneCommand,
- 'git sparse-checkout init --cone',
+ 'git sparse-checkout init',
"git sparse-checkout set {$fileList->implode(' ')}",
'git read-tree -mu HEAD',
"cat .$workdir$composeFile",
diff --git a/app/Models/BaseModel.php b/app/Models/BaseModel.php
index 79801987b..727abed5f 100644
--- a/app/Models/BaseModel.php
+++ b/app/Models/BaseModel.php
@@ -20,7 +20,7 @@ protected static function boot()
});
}
- public function name(): Attribute
+ public function sanitizedName(): Attribute
{
return new Attribute(
get: fn () => sanitize_string($this->getRawOriginal('name')),
diff --git a/app/Models/DiscordNotificationSettings.php b/app/Models/DiscordNotificationSettings.php
new file mode 100644
index 000000000..619393ddc
--- /dev/null
+++ b/app/Models/DiscordNotificationSettings.php
@@ -0,0 +1,59 @@
+ 'boolean',
+ 'discord_webhook_url' => 'encrypted',
+
+ 'deployment_success_discord_notifications' => 'boolean',
+ 'deployment_failure_discord_notifications' => 'boolean',
+ 'status_change_discord_notifications' => 'boolean',
+ 'backup_success_discord_notifications' => 'boolean',
+ 'backup_failure_discord_notifications' => 'boolean',
+ 'scheduled_task_success_discord_notifications' => 'boolean',
+ 'scheduled_task_failure_discord_notifications' => 'boolean',
+ 'docker_cleanup_discord_notifications' => 'boolean',
+ 'server_disk_usage_discord_notifications' => 'boolean',
+ 'server_reachable_discord_notifications' => 'boolean',
+ 'server_unreachable_discord_notifications' => 'boolean',
+ ];
+
+ public function team()
+ {
+ return $this->belongsTo(Team::class);
+ }
+
+ public function isEnabled()
+ {
+ return $this->discord_enabled;
+ }
+}
diff --git a/app/Models/EmailNotificationSettings.php b/app/Models/EmailNotificationSettings.php
new file mode 100644
index 000000000..ae118986f
--- /dev/null
+++ b/app/Models/EmailNotificationSettings.php
@@ -0,0 +1,79 @@
+ 'boolean',
+ 'smtp_from_address' => 'encrypted',
+ 'smtp_from_name' => 'encrypted',
+ 'smtp_recipients' => 'encrypted',
+ 'smtp_host' => 'encrypted',
+ 'smtp_port' => 'integer',
+ 'smtp_username' => 'encrypted',
+ 'smtp_password' => 'encrypted',
+ 'smtp_timeout' => 'integer',
+
+ 'resend_enabled' => 'boolean',
+ 'resend_api_key' => 'encrypted',
+
+ 'use_instance_email_settings' => 'boolean',
+
+ 'deployment_success_email_notifications' => 'boolean',
+ 'deployment_failure_email_notifications' => 'boolean',
+ 'status_change_email_notifications' => 'boolean',
+ 'backup_success_email_notifications' => 'boolean',
+ 'backup_failure_email_notifications' => 'boolean',
+ 'scheduled_task_success_email_notifications' => 'boolean',
+ 'scheduled_task_failure_email_notifications' => 'boolean',
+ 'server_disk_usage_email_notifications' => 'boolean',
+ ];
+
+ public function team()
+ {
+ return $this->belongsTo(Team::class);
+ }
+
+ public function isEnabled()
+ {
+ if (isCloud()) {
+ return true;
+ }
+
+ return $this->smtp_enabled || $this->resend_enabled || $this->use_instance_email_settings;
+ }
+}
diff --git a/app/Models/InstanceSettings.php b/app/Models/InstanceSettings.php
index eeb803925..5b89bb401 100644
--- a/app/Models/InstanceSettings.php
+++ b/app/Models/InstanceSettings.php
@@ -16,8 +16,19 @@ class InstanceSettings extends Model implements SendsEmail
protected $guarded = [];
protected $casts = [
- 'resale_license' => 'encrypted',
+ 'smtp_enabled' => 'boolean',
+ 'smtp_from_address' => 'encrypted',
+ 'smtp_from_name' => 'encrypted',
+ 'smtp_recipients' => 'encrypted',
+ 'smtp_host' => 'encrypted',
+ 'smtp_port' => 'integer',
+ 'smtp_username' => 'encrypted',
'smtp_password' => 'encrypted',
+ 'smtp_timeout' => 'integer',
+
+ 'resend_enabled' => 'boolean',
+ 'resend_api_key' => 'encrypted',
+
'allowed_ip_ranges' => 'array',
'is_auto_update_enabled' => 'boolean',
'auto_update_frequency' => 'string',
@@ -81,7 +92,7 @@ public static function get()
return InstanceSettings::findOrFail(0);
}
- public function getRecepients($notification)
+ public function getRecipients($notification)
{
$recipients = data_get($notification, 'emails', null);
if (is_null($recipients) || $recipients === '') {
diff --git a/app/Models/OauthSetting.php b/app/Models/OauthSetting.php
index c17c318f1..3d82e89f2 100644
--- a/app/Models/OauthSetting.php
+++ b/app/Models/OauthSetting.php
@@ -11,6 +11,8 @@ class OauthSetting extends Model
{
use HasFactory;
+ protected $fillable = ['provider', 'client_id', 'client_secret', 'redirect_uri', 'tenant', 'base_url', 'enabled'];
+
protected function clientSecret(): Attribute
{
return Attribute::make(
@@ -18,4 +20,16 @@ protected function clientSecret(): Attribute
set: fn (?string $value) => empty($value) ? null : Crypt::encryptString($value),
);
}
+
+ public function couldBeEnabled(): bool
+ {
+ switch ($this->provider) {
+ case 'azure':
+ return filled($this->client_id) && filled($this->client_secret) && filled($this->redirect_uri) && filled($this->tenant);
+ case 'authentik':
+ return filled($this->client_id) && filled($this->client_secret) && filled($this->redirect_uri) && filled($this->base_url);
+ default:
+ return filled($this->client_id) && filled($this->client_secret) && filled($this->redirect_uri);
+ }
+ }
}
diff --git a/app/Models/PushoverNotificationSettings.php b/app/Models/PushoverNotificationSettings.php
new file mode 100644
index 000000000..e3191dcc3
--- /dev/null
+++ b/app/Models/PushoverNotificationSettings.php
@@ -0,0 +1,61 @@
+ 'boolean',
+ 'pushover_user_key' => 'encrypted',
+ 'pushover_api_token' => 'encrypted',
+
+ 'deployment_success_pushover_notifications' => 'boolean',
+ 'deployment_failure_pushover_notifications' => 'boolean',
+ 'status_change_pushover_notifications' => 'boolean',
+ 'backup_success_pushover_notifications' => 'boolean',
+ 'backup_failure_pushover_notifications' => 'boolean',
+ 'scheduled_task_success_pushover_notifications' => 'boolean',
+ 'scheduled_task_failure_pushover_notifications' => 'boolean',
+ 'docker_cleanup_pushover_notifications' => 'boolean',
+ 'server_disk_usage_pushover_notifications' => 'boolean',
+ 'server_reachable_pushover_notifications' => 'boolean',
+ 'server_unreachable_pushover_notifications' => 'boolean',
+ ];
+
+ public function team()
+ {
+ return $this->belongsTo(Team::class);
+ }
+
+ public function isEnabled()
+ {
+ return $this->pushover_enabled;
+ }
+}
diff --git a/app/Models/S3Storage.php b/app/Models/S3Storage.php
index a432a6e9c..f1247e6f7 100644
--- a/app/Models/S3Storage.php
+++ b/app/Models/S3Storage.php
@@ -59,7 +59,7 @@ public function testConnection(bool $shouldSave = false)
$this->is_usable = true;
} catch (\Throwable $e) {
$this->is_usable = false;
- if ($this->unusable_email_sent === false && is_transactional_emails_active()) {
+ if ($this->unusable_email_sent === false && is_transactional_emails_enabled()) {
$mail = new MailMessage;
$mail->subject('Coolify: S3 Storage Connection Error');
$mail->view('emails.s3-connection-error', ['name' => $this->name, 'reason' => $e->getMessage(), 'url' => route('storage.show', ['storage_uuid' => $this->uuid])]);
diff --git a/app/Models/Server.php b/app/Models/Server.php
index 6dfb0a4a1..cc8211789 100644
--- a/app/Models/Server.php
+++ b/app/Models/Server.php
@@ -1042,7 +1042,7 @@ public function sendReachableNotification()
$this->unreachable_notification_sent = false;
$this->save();
$this->refresh();
- // $this->team->notify(new Reachable($this));
+ $this->team->notify(new Reachable($this));
}
public function sendUnreachableNotification()
@@ -1050,7 +1050,7 @@ public function sendUnreachableNotification()
$this->unreachable_notification_sent = true;
$this->save();
$this->refresh();
- // $this->team->notify(new Unreachable($this));
+ $this->team->notify(new Unreachable($this));
}
public function validateConnection(bool $justCheckingNewKey = false)
diff --git a/app/Models/Service.php b/app/Models/Service.php
index 6d3d2024b..5a2690490 100644
--- a/app/Models/Service.php
+++ b/app/Models/Service.php
@@ -46,7 +46,7 @@ class Service extends BaseModel
protected $guarded = [];
- protected $appends = ['server_status'];
+ protected $appends = ['server_status', 'status'];
protected static function booted()
{
@@ -105,12 +105,12 @@ protected function serverStatus(): Attribute
public function isRunning()
{
- return (bool) str($this->status())->contains('running');
+ return (bool) str($this->status)->contains('running');
}
public function isExited()
{
- return (bool) str($this->status())->contains('exited');
+ return (bool) str($this->status)->contains('exited');
}
public function type()
@@ -213,7 +213,7 @@ public function delete_connected_networks($uuid)
instant_remote_process(["docker network rm {$uuid}"], $server, false);
}
- public function status()
+ public function getStatusAttribute()
{
$applications = $this->applications;
$databases = $this->databases;
@@ -1140,7 +1140,7 @@ public function link()
return null;
}
- public function failedTaskLink($task_uuid)
+ public function taskLink($task_uuid)
{
if (data_get($this, 'environment.project.uuid')) {
$route = route('project.service.scheduled-tasks', [
diff --git a/app/Models/SlackNotificationSettings.php b/app/Models/SlackNotificationSettings.php
new file mode 100644
index 000000000..48153f2ea
--- /dev/null
+++ b/app/Models/SlackNotificationSettings.php
@@ -0,0 +1,59 @@
+ 'boolean',
+ 'slack_webhook_url' => 'encrypted',
+
+ 'deployment_success_slack_notifications' => 'boolean',
+ 'deployment_failure_slack_notifications' => 'boolean',
+ 'status_change_slack_notifications' => 'boolean',
+ 'backup_success_slack_notifications' => 'boolean',
+ 'backup_failure_slack_notifications' => 'boolean',
+ 'scheduled_task_success_slack_notifications' => 'boolean',
+ 'scheduled_task_failure_slack_notifications' => 'boolean',
+ 'docker_cleanup_slack_notifications' => 'boolean',
+ 'server_disk_usage_slack_notifications' => 'boolean',
+ 'server_reachable_slack_notifications' => 'boolean',
+ 'server_unreachable_slack_notifications' => 'boolean',
+ ];
+
+ public function team()
+ {
+ return $this->belongsTo(Team::class);
+ }
+
+ public function isEnabled()
+ {
+ return $this->slack_enabled;
+ }
+}
diff --git a/app/Models/Team.php b/app/Models/Team.php
index ecf662787..e55cb0d19 100644
--- a/app/Models/Team.php
+++ b/app/Models/Team.php
@@ -4,7 +4,9 @@
use App\Notifications\Channels\SendsDiscord;
use App\Notifications\Channels\SendsEmail;
+use App\Notifications\Channels\SendsPushover;
use App\Notifications\Channels\SendsSlack;
+use App\Traits\HasNotificationSettings;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
@@ -20,49 +22,8 @@
'personal_team' => ['type' => 'boolean', 'description' => 'Whether the team is personal or not.'],
'created_at' => ['type' => 'string', 'description' => 'The date and time the team was created.'],
'updated_at' => ['type' => 'string', 'description' => 'The date and time the team was last updated.'],
- 'smtp_enabled' => ['type' => 'boolean', 'description' => 'Whether SMTP is enabled or not.'],
- 'smtp_from_address' => ['type' => 'string', 'description' => 'The email address to send emails from.'],
- 'smtp_from_name' => ['type' => 'string', 'description' => 'The name to send emails from.'],
- 'smtp_recipients' => ['type' => 'string', 'description' => 'The email addresses to send emails to.'],
- 'smtp_host' => ['type' => 'string', 'description' => 'The SMTP host.'],
- 'smtp_port' => ['type' => 'string', 'description' => 'The SMTP port.'],
- 'smtp_encryption' => ['type' => 'string', 'description' => 'The SMTP encryption.'],
- 'smtp_username' => ['type' => 'string', 'description' => 'The SMTP username.'],
- 'smtp_password' => ['type' => 'string', 'description' => 'The SMTP password.'],
- 'smtp_timeout' => ['type' => 'string', 'description' => 'The SMTP timeout.'],
- 'smtp_notifications_test' => ['type' => 'boolean', 'description' => 'Whether to send test notifications via SMTP.'],
- 'smtp_notifications_deployments' => ['type' => 'boolean', 'description' => 'Whether to send deployment notifications via SMTP.'],
- 'smtp_notifications_status_changes' => ['type' => 'boolean', 'description' => 'Whether to send status change notifications via SMTP.'],
- 'smtp_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via SMTP.'],
- 'smtp_notifications_database_backups' => ['type' => 'boolean', 'description' => 'Whether to send database backup notifications via SMTP.'],
- 'smtp_notifications_server_disk_usage' => ['type' => 'boolean', 'description' => 'Whether to send server disk usage notifications via SMTP.'],
- 'discord_enabled' => ['type' => 'boolean', 'description' => 'Whether Discord is enabled or not.'],
- 'discord_webhook_url' => ['type' => 'string', 'description' => 'The Discord webhook URL.'],
- 'discord_notifications_test' => ['type' => 'boolean', 'description' => 'Whether to send test notifications via Discord.'],
- 'discord_notifications_deployments' => ['type' => 'boolean', 'description' => 'Whether to send deployment notifications via Discord.'],
- 'discord_notifications_status_changes' => ['type' => 'boolean', 'description' => 'Whether to send status change notifications via Discord.'],
- 'discord_notifications_database_backups' => ['type' => 'boolean', 'description' => 'Whether to send database backup notifications via Discord.'],
- 'discord_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via Discord.'],
- 'discord_notifications_server_disk_usage' => ['type' => 'boolean', 'description' => 'Whether to send server disk usage notifications via Discord.'],
'show_boarding' => ['type' => 'boolean', 'description' => 'Whether to show the boarding screen or not.'],
- 'resend_enabled' => ['type' => 'boolean', 'description' => 'Whether to enable resending or not.'],
- 'resend_api_key' => ['type' => 'string', 'description' => 'The resending API key.'],
- 'use_instance_email_settings' => ['type' => 'boolean', 'description' => 'Whether to use instance email settings or not.'],
- 'telegram_enabled' => ['type' => 'boolean', 'description' => 'Whether Telegram is enabled or not.'],
- 'telegram_token' => ['type' => 'string', 'description' => 'The Telegram token.'],
- 'telegram_chat_id' => ['type' => 'string', 'description' => 'The Telegram chat ID.'],
- 'telegram_notifications_test' => ['type' => 'boolean', 'description' => 'Whether to send test notifications via Telegram.'],
- 'telegram_notifications_deployments' => ['type' => 'boolean', 'description' => 'Whether to send deployment notifications via Telegram.'],
- 'telegram_notifications_status_changes' => ['type' => 'boolean', 'description' => 'Whether to send status change notifications via Telegram.'],
- 'telegram_notifications_database_backups' => ['type' => 'boolean', 'description' => 'Whether to send database backup notifications via Telegram.'],
- 'telegram_notifications_test_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram test message thread ID.'],
- 'telegram_notifications_deployments_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram deployment message thread ID.'],
- 'telegram_notifications_status_changes_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram status change message thread ID.'],
- 'telegram_notifications_database_backups_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram database backup message thread ID.'],
-
'custom_server_limit' => ['type' => 'string', 'description' => 'The custom server limit.'],
- 'telegram_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via Telegram.'],
- 'telegram_notifications_scheduled_tasks_thread_id' => ['type' => 'string', 'description' => 'The Telegram scheduled task message thread ID.'],
'members' => new OA\Property(
property: 'members',
type: 'array',
@@ -71,20 +32,27 @@
),
]
)]
-class Team extends Model implements SendsDiscord, SendsEmail, SendsSlack
+
+class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, SendsSlack
{
- use Notifiable;
+ use HasNotificationSettings, Notifiable;
protected $guarded = [];
protected $casts = [
'personal_team' => 'boolean',
- 'smtp_password' => 'encrypted',
- 'resend_api_key' => 'encrypted',
];
protected static function booted()
{
+ static::created(function ($team) {
+ $team->emailNotificationSettings()->create();
+ $team->discordNotificationSettings()->create();
+ $team->slackNotificationSettings()->create();
+ $team->telegramNotificationSettings()->create();
+ $team->pushoverNotificationSettings()->create();
+ });
+
static::saving(function ($team) {
if (auth()->user()?->isMember()) {
throw new \Exception('You are not allowed to update this team.');
@@ -115,34 +83,6 @@ protected static function booted()
});
}
- public function routeNotificationForDiscord()
- {
- return data_get($this, 'discord_webhook_url', null);
- }
-
- public function routeNotificationForTelegram()
- {
- return [
- 'token' => data_get($this, 'telegram_token', null),
- 'chat_id' => data_get($this, 'telegram_chat_id', null),
- ];
- }
-
- public function routeNotificationForSlack()
- {
- return data_get($this, 'slack_webhook_url', null);
- }
-
- public function getRecepients($notification)
- {
- $recipients = data_get($notification, 'emails', null);
- if (is_null($recipients)) {
- return $this->members()->pluck('email')->toArray();
- }
-
- return explode(',', $recipients);
- }
-
public static function serverLimitReached()
{
$serverLimit = Team::serverLimit();
@@ -196,10 +136,75 @@ public function limits(): Attribute
return $serverLimit ?? 2;
}
-
);
}
+ public function routeNotificationForDiscord()
+ {
+ return data_get($this, 'discord_webhook_url', null);
+ }
+
+ public function routeNotificationForTelegram()
+ {
+ return [
+ 'token' => data_get($this, 'telegram_token', null),
+ 'chat_id' => data_get($this, 'telegram_chat_id', null),
+ ];
+ }
+
+ public function routeNotificationForSlack()
+ {
+ return data_get($this, 'slack_webhook_url', null);
+ }
+
+ public function routeNotificationForPushover()
+ {
+ return [
+ 'user' => data_get($this, 'pushover_user_key', null),
+ 'token' => data_get($this, 'pushover_api_token', null),
+ ];
+ }
+
+ public function getRecipients($notification)
+ {
+ $recipients = data_get($notification, 'emails', null);
+ if (is_null($recipients)) {
+ return $this->members()->pluck('email')->toArray();
+ }
+
+ return explode(',', $recipients);
+ }
+
+ public function isAnyNotificationEnabled()
+ {
+ if (isCloud()) {
+ return true;
+ }
+
+ return $this->getNotificationSettings('email')?->isEnabled() ||
+ $this->getNotificationSettings('discord')?->isEnabled() ||
+ $this->getNotificationSettings('slack')?->isEnabled() ||
+ $this->getNotificationSettings('telegram')?->isEnabled() ||
+ $this->getNotificationSettings('pushover')?->isEnabled();
+ }
+
+ public function subscriptionEnded()
+ {
+ $this->subscription->update([
+ 'stripe_subscription_id' => null,
+ 'stripe_plan_id' => null,
+ 'stripe_cancel_at_period_end' => false,
+ 'stripe_invoice_paid' => false,
+ 'stripe_trial_already_ended' => false,
+ ]);
+ foreach ($this->servers as $server) {
+ $server->settings()->update([
+ 'is_usable' => false,
+ 'is_reachable' => false,
+ ]);
+ }
+ }
+
public function environment_variables()
{
return $this->hasMany(SharedEnvironmentVariable::class)->whereNull('project_id')->whereNull('environment_id');
@@ -263,32 +268,28 @@ public function s3s()
return $this->hasMany(S3Storage::class)->where('is_usable', true);
}
- public function subscriptionEnded()
+ public function emailNotificationSettings()
{
- $this->subscription->update([
- 'stripe_subscription_id' => null,
- 'stripe_plan_id' => null,
- 'stripe_cancel_at_period_end' => false,
- 'stripe_invoice_paid' => false,
- 'stripe_trial_already_ended' => false,
- ]);
- foreach ($this->servers as $server) {
- $server->settings()->update([
- 'is_usable' => false,
- 'is_reachable' => false,
- ]);
- }
+ return $this->hasOne(EmailNotificationSettings::class);
}
- public function isAnyNotificationEnabled()
+ public function discordNotificationSettings()
{
- if (isCloud()) {
- return true;
- }
- if ($this->smtp_enabled || $this->resend_enabled || $this->discord_enabled || $this->telegram_enabled || $this->use_instance_email_settings) {
- return true;
- }
+ return $this->hasOne(DiscordNotificationSettings::class);
+ }
- return false;
+ public function telegramNotificationSettings()
+ {
+ return $this->hasOne(TelegramNotificationSettings::class);
+ }
+
+ public function slackNotificationSettings()
+ {
+ return $this->hasOne(SlackNotificationSettings::class);
+ }
+
+ public function pushoverNotificationSettings()
+ {
+ return $this->hasOne(PushoverNotificationSettings::class);
}
}
diff --git a/app/Models/TelegramNotificationSettings.php b/app/Models/TelegramNotificationSettings.php
new file mode 100644
index 000000000..78bd841bd
--- /dev/null
+++ b/app/Models/TelegramNotificationSettings.php
@@ -0,0 +1,85 @@
+ 'boolean',
+ 'telegram_token' => 'encrypted',
+ 'telegram_chat_id' => 'encrypted',
+
+ 'deployment_success_telegram_notifications' => 'boolean',
+ 'deployment_failure_telegram_notifications' => 'boolean',
+ 'status_change_telegram_notifications' => 'boolean',
+ 'backup_success_telegram_notifications' => 'boolean',
+ 'backup_failure_telegram_notifications' => 'boolean',
+ 'scheduled_task_success_telegram_notifications' => 'boolean',
+ 'scheduled_task_failure_telegram_notifications' => 'boolean',
+ 'docker_cleanup_telegram_notifications' => 'boolean',
+ 'server_disk_usage_telegram_notifications' => 'boolean',
+ 'server_reachable_telegram_notifications' => 'boolean',
+ 'server_unreachable_telegram_notifications' => 'boolean',
+
+ 'telegram_notifications_deployment_success_thread_id' => 'encrypted',
+ 'telegram_notifications_deployment_failure_thread_id' => 'encrypted',
+ 'telegram_notifications_status_change_thread_id' => 'encrypted',
+ 'telegram_notifications_backup_success_thread_id' => 'encrypted',
+ 'telegram_notifications_backup_failure_thread_id' => 'encrypted',
+ 'telegram_notifications_scheduled_task_success_thread_id' => 'encrypted',
+ 'telegram_notifications_scheduled_task_failure_thread_id' => 'encrypted',
+ 'telegram_notifications_docker_cleanup_thread_id' => 'encrypted',
+ 'telegram_notifications_server_disk_usage_thread_id' => 'encrypted',
+ 'telegram_notifications_server_reachable_thread_id' => 'encrypted',
+ 'telegram_notifications_server_unreachable_thread_id' => 'encrypted',
+ ];
+
+ public function team()
+ {
+ return $this->belongsTo(Team::class);
+ }
+
+ public function isEnabled()
+ {
+ return $this->telegram_enabled;
+ }
+}
diff --git a/app/Models/User.php b/app/Models/User.php
index 25fb33d66..7c23631c3 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -114,7 +114,7 @@ public function teams()
return $this->belongsToMany(Team::class)->withPivot('role');
}
- public function getRecepients($notification)
+ public function getRecipients($notification)
{
return $this->email;
}
diff --git a/app/Models/Waitlist.php b/app/Models/Waitlist.php
deleted file mode 100644
index 28e5f01fd..000000000
--- a/app/Models/Waitlist.php
+++ /dev/null
@@ -1,12 +0,0 @@
-getEnabledChannels('deployment_failure');
}
public function toMail(): MailMessage
@@ -130,6 +131,31 @@ public function toTelegram(): array
];
}
+ public function toPushover(): PushoverMessage
+ {
+ if ($this->preview) {
+ $title = "Pull request #{$this->preview->pull_request_id} deployment failed";
+ $message = "Pull request deployment failed for {$this->application_name}";
+ } else {
+ $title = 'Deployment failed';
+ $message = "Deployment failed for {$this->application_name}";
+ }
+
+ $buttons[] = [
+ 'text' => 'Deployment logs',
+ 'url' => $this->deployment_url,
+ ];
+
+ return new PushoverMessage(
+ title: $title,
+ level: 'error',
+ message: $message,
+ buttons: [
+ ...$buttons,
+ ],
+ );
+ }
+
public function toSlack(): SlackMessage
{
if ($this->preview) {
diff --git a/app/Notifications/Application/DeploymentSuccess.php b/app/Notifications/Application/DeploymentSuccess.php
index 79ae19f66..b1a3d5225 100644
--- a/app/Notifications/Application/DeploymentSuccess.php
+++ b/app/Notifications/Application/DeploymentSuccess.php
@@ -6,6 +6,7 @@
use App\Models\ApplicationPreview;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
+use App\Notifications\Dto\PushoverMessage;
use App\Notifications\Dto\SlackMessage;
use Illuminate\Notifications\Messages\MailMessage;
@@ -45,13 +46,7 @@ public function __construct(Application $application, string $deployment_uuid, ?
public function via(object $notifiable): array
{
- $channels = setNotificationChannels($notifiable, 'deployments');
- if (isCloud()) {
- // TODO: Make batch notifications work with email
- $channels = array_diff($channels, [\App\Notifications\Channels\EmailChannel::class]);
- }
-
- return $channels;
+ return $notifiable->getEnabledChannels('deployment_success');
}
public function toMail(): MailMessage
@@ -145,6 +140,42 @@ public function toTelegram(): array
];
}
+ public function toPushover(): PushoverMessage
+ {
+ if ($this->preview) {
+ $title = "Pull request #{$this->preview->pull_request_id} successfully deployed";
+ $message = 'New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . '';
+ if ($this->preview->fqdn) {
+ $buttons[] = [
+ 'text' => 'Open Application',
+ 'url' => $this->preview->fqdn,
+ ];
+ }
+ } else {
+ $title = 'New version successfully deployed';
+ $message = 'New version successfully deployed of ' . $this->application_name . '';
+ if ($this->fqdn) {
+ $buttons[] = [
+ 'text' => 'Open Application',
+ 'url' => $this->fqdn,
+ ];
+ }
+ }
+ $buttons[] = [
+ 'text' => 'Deployment logs',
+ 'url' => $this->deployment_url,
+ ];
+
+ return new PushoverMessage(
+ title: $title,
+ level: 'success',
+ message: $message,
+ buttons: [
+ ...$buttons,
+ ],
+ );
+ }
+
public function toSlack(): SlackMessage
{
if ($this->preview) {
diff --git a/app/Notifications/Application/StatusChanged.php b/app/Notifications/Application/StatusChanged.php
index 2167e9f13..c9c7344c4 100644
--- a/app/Notifications/Application/StatusChanged.php
+++ b/app/Notifications/Application/StatusChanged.php
@@ -5,6 +5,7 @@
use App\Models\Application;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
+use App\Notifications\Dto\PushoverMessage;
use App\Notifications\Dto\SlackMessage;
use Illuminate\Notifications\Messages\MailMessage;
@@ -35,7 +36,7 @@ public function __construct(public Application $resource)
public function via(object $notifiable): array
{
- return setNotificationChannels($notifiable, 'status_changes');
+ return $notifiable->getEnabledChannels('status_change');
}
public function toMail(): MailMessage
@@ -77,6 +78,23 @@ public function toTelegram(): array
];
}
+ public function toPushover(): PushoverMessage
+ {
+ $message = $this->resource_name . ' has been stopped.';
+
+ return new PushoverMessage(
+ title: 'Application stopped',
+ level: 'error',
+ message: $message,
+ buttons: [
+ [
+ 'text' => 'Open Application in Coolify',
+ 'url' => $this->resource_url,
+ ],
+ ],
+ );
+ }
+
public function toSlack(): SlackMessage
{
$title = 'Application stopped';
diff --git a/app/Notifications/Channels/DiscordChannel.php b/app/Notifications/Channels/DiscordChannel.php
index df7040f8f..362006d8e 100644
--- a/app/Notifications/Channels/DiscordChannel.php
+++ b/app/Notifications/Channels/DiscordChannel.php
@@ -13,10 +13,13 @@ class DiscordChannel
public function send(SendsDiscord $notifiable, Notification $notification): void
{
$message = $notification->toDiscord();
- $webhookUrl = $notifiable->routeNotificationForDiscord();
- if (! $webhookUrl) {
+
+ $discordSettings = $notifiable->discordNotificationSettings;
+
+ if (! $discordSettings || ! $discordSettings->isEnabled() || ! $discordSettings->discord_webhook_url) {
return;
}
- SendMessageToDiscordJob::dispatch($message, $webhookUrl);
+
+ SendMessageToDiscordJob::dispatch($message, $discordSettings->discord_webhook_url);
}
}
diff --git a/app/Notifications/Channels/EmailChannel.php b/app/Notifications/Channels/EmailChannel.php
index 5394f6106..0985f4393 100644
--- a/app/Notifications/Channels/EmailChannel.php
+++ b/app/Notifications/Channels/EmailChannel.php
@@ -13,7 +13,7 @@ public function send(SendsEmail $notifiable, Notification $notification): void
{
try {
$this->bootConfigs($notifiable);
- $recipients = $notifiable->getRecepients($notification);
+ $recipients = $notifiable->getRecipients($notification);
if (count($recipients) === 0) {
throw new Exception('No email recipients found');
}
@@ -46,7 +46,9 @@ public function send(SendsEmail $notifiable, Notification $notification): void
private function bootConfigs($notifiable): void
{
- if (data_get($notifiable, 'use_instance_email_settings')) {
+ $emailSettings = $notifiable->emailNotificationSettings;
+
+ if ($emailSettings->use_instance_email_settings) {
$type = set_transanctional_email_settings();
if (! $type) {
throw new Exception('No email settings found.');
@@ -54,24 +56,27 @@ private function bootConfigs($notifiable): void
return;
}
- config()->set('mail.from.address', data_get($notifiable, 'smtp_from_address', 'test@example.com'));
- config()->set('mail.from.name', data_get($notifiable, 'smtp_from_name', 'Test'));
- if (data_get($notifiable, 'resend_enabled')) {
+
+ config()->set('mail.from.address', $emailSettings->smtp_from_address ?? 'test@example.com');
+ config()->set('mail.from.name', $emailSettings->smtp_from_name ?? 'Test');
+
+ if ($emailSettings->resend_enabled) {
config()->set('mail.default', 'resend');
- config()->set('resend.api_key', data_get($notifiable, 'resend_api_key'));
+ config()->set('resend.api_key', $emailSettings->resend_api_key);
}
- if (data_get($notifiable, 'smtp_enabled')) {
+
+ if ($emailSettings->smtp_enabled) {
config()->set('mail.default', 'smtp');
config()->set('mail.mailers.smtp', [
'transport' => 'smtp',
- 'host' => data_get($notifiable, 'smtp_host'),
- 'port' => data_get($notifiable, 'smtp_port'),
- 'encryption' => data_get($notifiable, 'smtp_encryption') === 'none' ? null : data_get($notifiable, 'smtp_encryption'),
- 'username' => data_get($notifiable, 'smtp_username'),
- 'password' => data_get($notifiable, 'smtp_password'),
- 'timeout' => data_get($notifiable, 'smtp_timeout'),
+ 'host' => $emailSettings->smtp_host,
+ 'port' => $emailSettings->smtp_port,
+ 'encryption' => $emailSettings->smtp_encryption === 'none' ? null : $emailSettings->smtp_encryption,
+ 'username' => $emailSettings->smtp_username,
+ 'password' => $emailSettings->smtp_password,
+ 'timeout' => $emailSettings->smtp_timeout,
'local_domain' => null,
- 'auto_tls' => data_get($notifiable, 'smtp_encryption') === 'none' ? '0' : '',
+ 'auto_tls' => $emailSettings->smtp_encryption === 'none' ? '0' : '',
]);
}
}
diff --git a/app/Notifications/Channels/PushoverChannel.php b/app/Notifications/Channels/PushoverChannel.php
new file mode 100644
index 000000000..3d3728d01
--- /dev/null
+++ b/app/Notifications/Channels/PushoverChannel.php
@@ -0,0 +1,21 @@
+toPushover();
+ $pushoverSettings = $notifiable->pushoverNotificationSettings;
+
+ if (! $pushoverSettings || ! $pushoverSettings->isEnabled() || ! $pushoverSettings->pushover_user_key || ! $pushoverSettings->pushover_api_token) {
+ return;
+ }
+
+ SendMessageToPushoverJob::dispatch($message, $pushoverSettings->pushover_api_token, $pushoverSettings->pushover_user_key);
+ }
+}
diff --git a/app/Notifications/Channels/SendsEmail.php b/app/Notifications/Channels/SendsEmail.php
index fc7528834..3adc6d0a2 100644
--- a/app/Notifications/Channels/SendsEmail.php
+++ b/app/Notifications/Channels/SendsEmail.php
@@ -4,5 +4,5 @@
interface SendsEmail
{
- public function getRecepients($notification);
+ public function getRecipients($notification);
}
diff --git a/app/Notifications/Channels/SendsPushover.php b/app/Notifications/Channels/SendsPushover.php
new file mode 100644
index 000000000..7922eefb4
--- /dev/null
+++ b/app/Notifications/Channels/SendsPushover.php
@@ -0,0 +1,8 @@
+toSlack();
- $webhookUrl = $notifiable->routeNotificationForSlack();
- if (! $webhookUrl) {
+ $slackSettings = $notifiable->slackNotificationSettings;
+
+ if (! $slackSettings || ! $slackSettings->isEnabled() || ! $slackSettings->slack_webhook_url) {
return;
}
- SendMessageToSlackJob::dispatch($message, $webhookUrl);
+
+ SendMessageToSlackJob::dispatch($message, $slackSettings->slack_webhook_url);
}
}
diff --git a/app/Notifications/Channels/TelegramChannel.php b/app/Notifications/Channels/TelegramChannel.php
index 958c46c21..ea4ab7171 100644
--- a/app/Notifications/Channels/TelegramChannel.php
+++ b/app/Notifications/Channels/TelegramChannel.php
@@ -9,38 +9,39 @@ class TelegramChannel
public function send($notifiable, $notification): void
{
$data = $notification->toTelegram($notifiable);
- $telegramData = $notifiable->routeNotificationForTelegram();
+ $settings = $notifiable->telegramNotificationSettings;
+
$message = data_get($data, 'message');
$buttons = data_get($data, 'buttons', []);
- $telegramToken = data_get($telegramData, 'token');
- $chatId = data_get($telegramData, 'chat_id');
- $topicId = null;
- $topicsInstance = get_class($notification);
+ $telegramToken = $settings->telegram_token;
+ $chatId = $settings->telegram_chat_id;
+
+ $threadId = match (get_class($notification)) {
+ \App\Notifications\Application\DeploymentSuccess::class => $settings->telegram_notifications_deployment_success_thread_id,
+ \App\Notifications\Application\DeploymentFailed::class => $settings->telegram_notifications_deployment_failure_thread_id,
+ \App\Notifications\Application\StatusChanged::class,
+ \App\Notifications\Container\ContainerRestarted::class,
+ \App\Notifications\Container\ContainerStopped::class => $settings->telegram_notifications_status_change_thread_id,
+
+ \App\Notifications\Database\BackupSuccess::class => $settings->telegram_notifications_backup_success_thread_id,
+ \App\Notifications\Database\BackupFailed::class => $settings->telegram_notifications_backup_failure_thread_id,
+
+ \App\Notifications\ScheduledTask\TaskSuccess::class => $settings->telegram_notifications_scheduled_task_success_thread_id,
+ \App\Notifications\ScheduledTask\TaskFailed::class => $settings->telegram_notifications_scheduled_task_failure_thread_id,
+
+ \App\Notifications\Server\DockerCleanupSuccess::class => $settings->telegram_notifications_docker_cleanup_success_thread_id,
+ \App\Notifications\Server\DockerCleanupFailed::class => $settings->telegram_notifications_docker_cleanup_failure_thread_id,
+ \App\Notifications\Server\HighDiskUsage::class => $settings->telegram_notifications_server_disk_usage_thread_id,
+ \App\Notifications\Server\Unreachable::class => $settings->telegram_notifications_server_unreachable_thread_id,
+ \App\Notifications\Server\Reachable::class => $settings->telegram_notifications_server_reachable_thread_id,
+
+ default => null,
+ };
- switch ($topicsInstance) {
- case \App\Notifications\Test::class:
- $topicId = data_get($notifiable, 'telegram_notifications_test_message_thread_id');
- break;
- case \App\Notifications\Application\StatusChanged::class:
- case \App\Notifications\Container\ContainerRestarted::class:
- case \App\Notifications\Container\ContainerStopped::class:
- $topicId = data_get($notifiable, 'telegram_notifications_status_changes_message_thread_id');
- break;
- case \App\Notifications\Application\DeploymentSuccess::class:
- case \App\Notifications\Application\DeploymentFailed::class:
- $topicId = data_get($notifiable, 'telegram_notifications_deployments_message_thread_id');
- break;
- case \App\Notifications\Database\BackupSuccess::class:
- case \App\Notifications\Database\BackupFailed::class:
- $topicId = data_get($notifiable, 'telegram_notifications_database_backups_message_thread_id');
- break;
- case \App\Notifications\ScheduledTask\TaskFailed::class:
- $topicId = data_get($notifiable, 'telegram_notifications_scheduled_tasks_thread_id');
- break;
- }
if (! $telegramToken || ! $chatId || ! $message) {
return;
}
- SendMessageToTelegramJob::dispatch($message, $buttons, $telegramToken, $chatId, $topicId);
+
+ SendMessageToTelegramJob::dispatch($message, $buttons, $telegramToken, $chatId, $threadId);
}
}
diff --git a/app/Notifications/Channels/TransactionalEmailChannel.php b/app/Notifications/Channels/TransactionalEmailChannel.php
index cc7d76ebf..761780231 100644
--- a/app/Notifications/Channels/TransactionalEmailChannel.php
+++ b/app/Notifications/Channels/TransactionalEmailChannel.php
@@ -7,7 +7,6 @@
use Illuminate\Mail\Message;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Mail;
-use Log;
class TransactionalEmailChannel
{
@@ -15,8 +14,6 @@ public function send(User $notifiable, Notification $notification): void
{
$settings = instanceSettings();
if (! data_get($settings, 'smtp_enabled') && ! data_get($settings, 'resend_enabled')) {
- Log::info('SMTP/Resend not enabled');
-
return;
}
$email = $notifiable->email;
diff --git a/app/Notifications/Container/ContainerRestarted.php b/app/Notifications/Container/ContainerRestarted.php
index f9becd0e8..68fc6b019 100644
--- a/app/Notifications/Container/ContainerRestarted.php
+++ b/app/Notifications/Container/ContainerRestarted.php
@@ -5,6 +5,7 @@
use App\Models\Server;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
+use App\Notifications\Dto\PushoverMessage;
use App\Notifications\Dto\SlackMessage;
use Illuminate\Notifications\Messages\MailMessage;
@@ -17,7 +18,7 @@ public function __construct(public string $name, public Server $server, public ?
public function via(object $notifiable): array
{
- return setNotificationChannels($notifiable, 'status_changes');
+ return $notifiable->getEnabledChannels('status_change');
}
public function toMail(): MailMessage
@@ -68,6 +69,24 @@ public function toTelegram(): array
return $payload;
}
+ public function toPushover(): PushoverMessage
+ {
+ $buttons = [];
+ if ($this->url) {
+ $buttons[] = [
+ 'text' => 'Check Proxy in Coolify',
+ 'url' => $this->url,
+ ];
+ }
+
+ return new PushoverMessage(
+ title: 'Resource restarted',
+ level: 'warning',
+ message: "A resource ({$this->name}) has been restarted automatically on {$this->server->name}",
+ buttons: $buttons,
+ );
+ }
+
public function toSlack(): SlackMessage
{
$title = 'Resource restarted';
diff --git a/app/Notifications/Container/ContainerStopped.php b/app/Notifications/Container/ContainerStopped.php
index eae2cf552..49aea196d 100644
--- a/app/Notifications/Container/ContainerStopped.php
+++ b/app/Notifications/Container/ContainerStopped.php
@@ -5,6 +5,7 @@
use App\Models\Server;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
+use App\Notifications\Dto\PushoverMessage;
use App\Notifications\Dto\SlackMessage;
use Illuminate\Notifications\Messages\MailMessage;
@@ -17,7 +18,7 @@ public function __construct(public string $name, public Server $server, public ?
public function via(object $notifiable): array
{
- return setNotificationChannels($notifiable, 'status_changes');
+ return $notifiable->getEnabledChannels('status_change');
}
public function toMail(): MailMessage
@@ -68,6 +69,25 @@ public function toTelegram(): array
return $payload;
}
+ public function toPushover(): PushoverMessage
+ {
+ $buttons = [];
+ if ($this->url) {
+ $buttons[] = [
+ 'text' => 'Open Application in Coolify',
+ 'url' => $this->url,
+ ];
+ }
+
+ return new PushoverMessage(
+ title: 'Resource stopped',
+ level: 'error',
+ message: "A resource ({$this->name}) has been stopped unexpectedly on {$this->server->name}",
+ buttons: $buttons,
+ );
+ }
+
+
public function toSlack(): SlackMessage
{
$title = 'Resource stopped';
diff --git a/app/Notifications/Database/BackupFailed.php b/app/Notifications/Database/BackupFailed.php
index 2056255d6..6dcb70583 100644
--- a/app/Notifications/Database/BackupFailed.php
+++ b/app/Notifications/Database/BackupFailed.php
@@ -5,6 +5,7 @@
use App\Models\ScheduledDatabaseBackup;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
+use App\Notifications\Dto\PushoverMessage;
use App\Notifications\Dto\SlackMessage;
use Illuminate\Notifications\Messages\MailMessage;
@@ -23,13 +24,13 @@ public function __construct(ScheduledDatabaseBackup $backup, public $database, p
public function via(object $notifiable): array
{
- return setNotificationChannels($notifiable, 'database_backups');
+ return $notifiable->getEnabledChannels('backup_failure');
}
public function toMail(): MailMessage
{
$mail = new MailMessage;
- $mail->subject("Coolify: [ACTION REQUIRED] Backup FAILED for {$this->database->name}");
+ $mail->subject("Coolify: [ACTION REQUIRED] Database Backup FAILED for {$this->database->name}");
$mail->view('emails.backup-failed', [
'name' => $this->name,
'database_name' => $this->database_name,
@@ -64,6 +65,15 @@ public function toTelegram(): array
];
}
+ public function toPushover(): PushoverMessage
+ {
+ return new PushoverMessage(
+ title: 'Database backup failed',
+ level: 'error',
+ message: "Database backup for {$this->name} (db:{$this->database_name}) was FAILED
Frequency: {$this->frequency} . Reason: {$this->output}",
+ );
+ }
+
public function toSlack(): SlackMessage
{
$title = 'Database backup failed';
diff --git a/app/Notifications/Database/BackupSuccess.php b/app/Notifications/Database/BackupSuccess.php
index 71866f9fc..4c3e8e060 100644
--- a/app/Notifications/Database/BackupSuccess.php
+++ b/app/Notifications/Database/BackupSuccess.php
@@ -5,6 +5,7 @@
use App\Models\ScheduledDatabaseBackup;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
+use App\Notifications\Dto\PushoverMessage;
use App\Notifications\Dto\SlackMessage;
use Illuminate\Notifications\Messages\MailMessage;
@@ -24,7 +25,7 @@ public function __construct(ScheduledDatabaseBackup $backup, public $database, p
public function via(object $notifiable): array
{
- return setNotificationChannels($notifiable, 'database_backups');
+ return $notifiable->getEnabledChannels('backup_success');
}
public function toMail(): MailMessage
@@ -62,6 +63,17 @@ public function toTelegram(): array
];
}
+
+ public function toPushover(): PushoverMessage
+ {
+ return new PushoverMessage(
+ title: 'Database backup successful',
+ level: 'success',
+ message: "Database backup for {$this->name} (db:{$this->database_name}) was successful.
Frequency: {$this->frequency}.",
+ );
+ }
+
+
public function toSlack(): SlackMessage
{
$title = 'Database backup successful';
diff --git a/app/Notifications/Dto/PushoverMessage.php b/app/Notifications/Dto/PushoverMessage.php
new file mode 100644
index 000000000..0efd1d526
--- /dev/null
+++ b/app/Notifications/Dto/PushoverMessage.php
@@ -0,0 +1,50 @@
+level) {
+ 'info' => 'ℹ️',
+ 'error' => '❌',
+ 'success' => '✅ ',
+ 'warning' => '⚠️',
+ };
+ }
+
+ public function toPayload(string $token, string $user): array
+ {
+ $levelIcon = $this->getLevelIcon();
+ $payload = [
+ 'token' => $token,
+ 'user' => $user,
+ 'title' => "{$levelIcon} {$this->title}",
+ 'message' => $this->message,
+ 'html' => 1,
+ ];
+
+ foreach ($this->buttons as $button) {
+ $buttonUrl = data_get($button, 'url');
+ $text = data_get($button, 'text', 'Click here');
+ if ($buttonUrl && str_contains($buttonUrl, 'http://localhost')) {
+ $buttonUrl = str_replace('http://localhost', config('app.url'), $buttonUrl);
+ }
+ $payload['message'] .= " " . $text . '';
+ }
+
+ Log::info('Pushover message', $payload);
+
+ return $payload;
+ }
+}
diff --git a/app/Notifications/Internal/GeneralNotification.php b/app/Notifications/Internal/GeneralNotification.php
index 347f4a0dd..1d2367210 100644
--- a/app/Notifications/Internal/GeneralNotification.php
+++ b/app/Notifications/Internal/GeneralNotification.php
@@ -2,10 +2,8 @@
namespace App\Notifications\Internal;
-use App\Notifications\Channels\DiscordChannel;
-use App\Notifications\Channels\SlackChannel;
-use App\Notifications\Channels\TelegramChannel;
use App\Notifications\Dto\DiscordMessage;
+use App\Notifications\Dto\PushoverMessage;
use App\Notifications\Dto\SlackMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
@@ -24,22 +22,7 @@ public function __construct(public string $message)
public function via(object $notifiable): array
{
- $channels = [];
- $isDiscordEnabled = data_get($notifiable, 'discord_enabled');
- $isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
- $isSlackEnabled = data_get($notifiable, 'slack_enabled');
-
- if ($isDiscordEnabled) {
- $channels[] = DiscordChannel::class;
- }
- if ($isTelegramEnabled) {
- $channels[] = TelegramChannel::class;
- }
- if ($isSlackEnabled) {
- $channels[] = SlackChannel::class;
- }
-
- return $channels;
+ return $notifiable->getEnabledChannels('general');
}
public function toDiscord(): DiscordMessage
@@ -58,6 +41,15 @@ public function toTelegram(): array
];
}
+ public function toPushover(): PushoverMessage
+ {
+ return new PushoverMessage(
+ title: 'General Notification',
+ level: 'info',
+ message: $this->message,
+ );
+ }
+
public function toSlack(): SlackMessage
{
return new SlackMessage(
diff --git a/app/Notifications/ScheduledTask/TaskFailed.php b/app/Notifications/ScheduledTask/TaskFailed.php
index 753da7ff0..c4d92f213 100644
--- a/app/Notifications/ScheduledTask/TaskFailed.php
+++ b/app/Notifications/ScheduledTask/TaskFailed.php
@@ -5,6 +5,7 @@
use App\Models\ScheduledTask;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
+use App\Notifications\Dto\PushoverMessage;
use App\Notifications\Dto\SlackMessage;
use Illuminate\Notifications\Messages\MailMessage;
@@ -16,15 +17,15 @@ public function __construct(public ScheduledTask $task, public string $output)
{
$this->onQueue('high');
if ($task->application) {
- $this->url = $task->application->failedTaskLink($task->uuid);
+ $this->url = $task->application->taskLink($task->uuid);
} elseif ($task->service) {
- $this->url = $task->service->failedTaskLink($task->uuid);
+ $this->url = $task->service->taskLink($task->uuid);
}
}
public function via(object $notifiable): array
{
- return setNotificationChannels($notifiable, 'scheduled_tasks');
+ return $notifiable->getEnabledChannels('scheduled_task_failure');
}
public function toMail(): MailMessage
@@ -70,6 +71,30 @@ public function toTelegram(): array
];
}
+ public function toPushover(): PushoverMessage
+ {
+ $message = "Scheduled task ({$this->task->name}) failed ";
+
+ if ($this->output) {
+ $message .= " Error Output:{$this->output}";
+ }
+
+ $buttons = [];
+ if ($this->url) {
+ $buttons[] = [
+ 'text' => 'Open task in Coolify',
+ 'url' => (string) $this->url,
+ ];
+ }
+
+ return new PushoverMessage(
+ title: 'Scheduled task failed',
+ level: 'error',
+ message: $message,
+ buttons: $buttons,
+ );
+ }
+
public function toSlack(): SlackMessage
{
$title = 'Scheduled task failed';
diff --git a/app/Notifications/ScheduledTask/TaskSuccess.php b/app/Notifications/ScheduledTask/TaskSuccess.php
new file mode 100644
index 000000000..5d4154e7a
--- /dev/null
+++ b/app/Notifications/ScheduledTask/TaskSuccess.php
@@ -0,0 +1,108 @@
+onQueue('high');
+ if ($task->application) {
+ $this->url = $task->application->taskLink($task->uuid);
+ } elseif ($task->service) {
+ $this->url = $task->service->taskLink($task->uuid);
+ }
+ }
+
+ public function via(object $notifiable): array
+ {
+ return $notifiable->getEnabledChannels('scheduled_task_success');
+ }
+
+ public function toMail(): MailMessage
+ {
+ $mail = new MailMessage;
+ $mail->subject("Coolify: Scheduled task ({$this->task->name}) succeeded.");
+ $mail->view('emails.scheduled-task-success', [
+ 'task' => $this->task,
+ 'url' => $this->url,
+ 'output' => $this->output,
+ ]);
+
+ return $mail;
+ }
+
+ public function toDiscord(): DiscordMessage
+ {
+ $message = new DiscordMessage(
+ title: ':white_check_mark: Scheduled task succeeded',
+ description: "Scheduled task ({$this->task->name}) succeeded.",
+ color: DiscordMessage::successColor(),
+ );
+
+ if ($this->url) {
+ $message->addField('Scheduled task', '[Link]('.$this->url.')');
+ }
+
+ return $message;
+ }
+
+ public function toTelegram(): array
+ {
+ $message = "Coolify: Scheduled task ({$this->task->name}) succeeded.";
+ if ($this->url) {
+ $buttons[] = [
+ 'text' => 'Open task in Coolify',
+ 'url' => (string) $this->url,
+ ];
+ }
+
+ return [
+ 'message' => $message,
+ ];
+ }
+
+ public function toPushover(): PushoverMessage
+ {
+ $message = "Coolify: Scheduled task ({$this->task->name}) succeeded.";
+ $buttons = [];
+ if ($this->url) {
+ $buttons[] = [
+ 'text' => 'Open task in Coolify',
+ 'url' => (string) $this->url,
+ ];
+ }
+
+ return new PushoverMessage(
+ title: 'Scheduled task succeeded',
+ level: 'success',
+ message: $message,
+ buttons: $buttons,
+ );
+ }
+
+ public function toSlack(): SlackMessage
+ {
+ $title = 'Scheduled task succeeded';
+ $description = "Scheduled task ({$this->task->name}) succeeded.";
+
+ if ($this->url) {
+ $description .= "\n\n**Task URL:** {$this->url}";
+ }
+
+ return new SlackMessage(
+ title: $title,
+ description: $description,
+ color: SlackMessage::successColor()
+ );
+ }
+}
diff --git a/app/Notifications/Server/DockerCleanup.php b/app/Notifications/Server/DockerCleanup.php
deleted file mode 100644
index 46b730c7b..000000000
--- a/app/Notifications/Server/DockerCleanup.php
+++ /dev/null
@@ -1,78 +0,0 @@
-onQueue('high');
- }
-
- public function via(object $notifiable): array
- {
- $channels = [];
- // $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;
- }
- // if ($isEmailEnabled) {
- // $channels[] = EmailChannel::class;
- // }
- if ($isTelegramEnabled) {
- $channels[] = TelegramChannel::class;
- }
- if ($isSlackEnabled) {
- $channels[] = SlackChannel::class;
- }
-
- return $channels;
- }
-
- // public function toMail(): MailMessage
- // {
- // $mail = new MailMessage();
- // $mail->subject("Coolify: Server ({$this->server->name}) high disk usage detected!");
- // $mail->view('emails.high-disk-usage', [
- // 'name' => $this->server->name,
- // 'disk_usage' => $this->disk_usage,
- // 'threshold' => $this->docker_cleanup_threshold,
- // ]);
- // return $mail;
- // }
-
- public function toDiscord(): DiscordMessage
- {
- return new DiscordMessage(
- title: ':white_check_mark: Server cleanup job done',
- description: $this->message,
- color: DiscordMessage::successColor(),
- );
- }
-
- public function toTelegram(): array
- {
- return [
- '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()
- );
- }
-}
diff --git a/app/Notifications/Server/DockerCleanupFailed.php b/app/Notifications/Server/DockerCleanupFailed.php
new file mode 100644
index 000000000..0291eed19
--- /dev/null
+++ b/app/Notifications/Server/DockerCleanupFailed.php
@@ -0,0 +1,69 @@
+onQueue('high');
+ }
+
+ public function via(object $notifiable): array
+ {
+ return $notifiable->getEnabledChannels('docker_cleanup_failure');
+ }
+
+ public function toMail(): MailMessage
+ {
+ $mail = new MailMessage;
+ $mail->subject("Coolify: [ACTION REQUIRED] Docker cleanup job failed on {$this->server->name}");
+ $mail->view('emails.docker-cleanup-failed', [
+ 'name' => $this->server->name,
+ 'text' => $this->message,
+ ]);
+
+ return $mail;
+ }
+
+ public function toDiscord(): DiscordMessage
+ {
+ return new DiscordMessage(
+ title: ':cross_mark: Coolify: [ACTION REQUIRED] Docker cleanup job failed on '.$this->server->name,
+ description: $this->message,
+ color: DiscordMessage::errorColor(),
+ );
+ }
+
+ public function toTelegram(): array
+ {
+ return [
+ 'message' => "Coolify: [ACTION REQUIRED] Docker cleanup job failed on {$this->server->name}!\n\n{$this->message}",
+ ];
+ }
+
+ public function toPushover(): PushoverMessage
+ {
+ return new PushoverMessage(
+ title: 'Docker cleanup job failed',
+ level: 'error',
+ message: "[ACTION REQUIRED] Docker cleanup job failed on {$this->server->name}!\n\n{$this->message}",
+ );
+ }
+
+ public function toSlack(): SlackMessage
+ {
+ return new SlackMessage(
+ title: 'Coolify: [ACTION REQUIRED] Docker cleanup job failed',
+ description: "Docker cleanup job failed on '{$this->server->name}'!\n\n{$this->message}",
+ color: SlackMessage::errorColor()
+ );
+ }
+}
diff --git a/app/Notifications/Server/DockerCleanupSuccess.php b/app/Notifications/Server/DockerCleanupSuccess.php
new file mode 100644
index 000000000..1a652d189
--- /dev/null
+++ b/app/Notifications/Server/DockerCleanupSuccess.php
@@ -0,0 +1,69 @@
+onQueue('high');
+ }
+
+ public function via(object $notifiable): array
+ {
+ return $notifiable->getEnabledChannels('docker_cleanup_success');
+ }
+
+ public function toMail(): MailMessage
+ {
+ $mail = new MailMessage;
+ $mail->subject("Coolify: Docker cleanup job succeeded on {$this->server->name}");
+ $mail->view('emails.docker-cleanup-success', [
+ 'name' => $this->server->name,
+ 'text' => $this->message,
+ ]);
+
+ return $mail;
+ }
+
+ public function toDiscord(): DiscordMessage
+ {
+ return new DiscordMessage(
+ title: ':white_check_mark: Coolify: Docker cleanup job succeeded on '.$this->server->name,
+ description: $this->message,
+ color: DiscordMessage::successColor(),
+ );
+ }
+
+ public function toTelegram(): array
+ {
+ return [
+ 'message' => "Coolify: Docker cleanup job succeeded on {$this->server->name}!\n\n{$this->message}",
+ ];
+ }
+
+ public function toPushover(): PushoverMessage
+ {
+ return new PushoverMessage(
+ title: 'Docker cleanup job succeeded',
+ level: 'success',
+ message: "Docker cleanup job succeeded on {$this->server->name}!\n\n{$this->message}",
+ );
+ }
+
+ public function toSlack(): SlackMessage
+ {
+ return new SlackMessage(
+ title: 'Coolify: Docker cleanup job succeeded',
+ description: "Docker cleanup job succeeded on '{$this->server->name}'!\n\n{$this->message}",
+ color: SlackMessage::successColor()
+ );
+ }
+}
diff --git a/app/Notifications/Server/ForceDisabled.php b/app/Notifications/Server/ForceDisabled.php
index 143917dde..7a1f7bcbf 100644
--- a/app/Notifications/Server/ForceDisabled.php
+++ b/app/Notifications/Server/ForceDisabled.php
@@ -3,12 +3,9 @@
namespace App\Notifications\Server;
use App\Models\Server;
-use App\Notifications\Channels\DiscordChannel;
-use App\Notifications\Channels\EmailChannel;
-use App\Notifications\Channels\SlackChannel;
-use App\Notifications\Channels\TelegramChannel;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
+use App\Notifications\Dto\PushoverMessage;
use App\Notifications\Dto\SlackMessage;
use Illuminate\Notifications\Messages\MailMessage;
@@ -21,25 +18,7 @@ public function __construct(public Server $server)
public function via(object $notifiable): array
{
- $channels = [];
- $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;
- }
- if ($isEmailEnabled) {
- $channels[] = EmailChannel::class;
- }
- if ($isTelegramEnabled) {
- $channels[] = TelegramChannel::class;
- }
- if ($isSlackEnabled) {
- $channels[] = SlackChannel::class;
- }
-
- return $channels;
+ return $notifiable->getEnabledChannels('server_force_disabled');
}
public function toMail(): MailMessage
@@ -73,6 +52,15 @@ public function toTelegram(): array
];
}
+ public function toPushover(): PushoverMessage
+ {
+ return new PushoverMessage(
+ title: 'Server disabled',
+ level: 'error',
+ message: "Server ({$this->server->name}) disabled because it is not paid!\n All automations and integrations are stopped. Please update your subscription to enable the server again [here](https://app.coolify.io/subscriptions).",
+ );
+ }
+
public function toSlack(): SlackMessage
{
$title = 'Server disabled';
diff --git a/app/Notifications/Server/ForceEnabled.php b/app/Notifications/Server/ForceEnabled.php
index 3b83882d8..36dad3c60 100644
--- a/app/Notifications/Server/ForceEnabled.php
+++ b/app/Notifications/Server/ForceEnabled.php
@@ -3,12 +3,9 @@
namespace App\Notifications\Server;
use App\Models\Server;
-use App\Notifications\Channels\DiscordChannel;
-use App\Notifications\Channels\EmailChannel;
-use App\Notifications\Channels\SlackChannel;
-use App\Notifications\Channels\TelegramChannel;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
+use App\Notifications\Dto\PushoverMessage;
use App\Notifications\Dto\SlackMessage;
use Illuminate\Notifications\Messages\MailMessage;
@@ -21,25 +18,7 @@ public function __construct(public Server $server)
public function via(object $notifiable): array
{
- $channels = [];
- $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;
- }
- if ($isEmailEnabled) {
- $channels[] = EmailChannel::class;
- }
- if ($isTelegramEnabled) {
- $channels[] = TelegramChannel::class;
- }
- if ($isSlackEnabled) {
- $channels[] = SlackChannel::class;
- }
-
- return $channels;
+ return $notifiable->getEnabledChannels('server_force_enabled');
}
public function toMail(): MailMessage
@@ -69,6 +48,15 @@ public function toTelegram(): array
];
}
+ public function toPushover(): PushoverMessage
+ {
+ return new PushoverMessage(
+ title: 'Server enabled',
+ level: 'success',
+ message: "Server ({$this->server->name}) enabled again!",
+ );
+ }
+
public function toSlack(): SlackMessage
{
return new SlackMessage(
diff --git a/app/Notifications/Server/HighDiskUsage.php b/app/Notifications/Server/HighDiskUsage.php
index baff49508..aea9abd03 100644
--- a/app/Notifications/Server/HighDiskUsage.php
+++ b/app/Notifications/Server/HighDiskUsage.php
@@ -5,6 +5,7 @@
use App\Models\Server;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
+use App\Notifications\Dto\PushoverMessage;
use App\Notifications\Dto\SlackMessage;
use Illuminate\Notifications\Messages\MailMessage;
@@ -17,7 +18,7 @@ public function __construct(public Server $server, public int $disk_usage, publi
public function via(object $notifiable): array
{
- return setNotificationChannels($notifiable, 'server_disk_usage');
+ return $notifiable->getEnabledChannels('server_disk_usage');
}
public function toMail(): MailMessage
@@ -57,6 +58,19 @@ public function toTelegram(): array
];
}
+ public function toPushover(): PushoverMessage
+ {
+ return new PushoverMessage(
+ title: 'High disk usage detected',
+ level: 'warning',
+ message: "Server '{$this->server->name}' high disk usage detected!
Disk usage: {$this->disk_usage}%. Threshold: {$this->server_disk_usage_notification_threshold}%. Please cleanup your disk to prevent data-loss.",
+ buttons: [
+ 'Change settings' => base_url().'/server/'.$this->server->uuid."#advanced",
+ 'Tips for cleanup' => "https://coolify.io/docs/knowledge-base/server/automated-cleanup",
+ ],
+ );
+ }
+
public function toSlack(): SlackMessage
{
$description = "Server '{$this->server->name}' high disk usage detected!\n";
diff --git a/app/Notifications/Server/Reachable.php b/app/Notifications/Server/Reachable.php
index 62ece34e8..e03aef6b7 100644
--- a/app/Notifications/Server/Reachable.php
+++ b/app/Notifications/Server/Reachable.php
@@ -3,12 +3,9 @@
namespace App\Notifications\Server;
use App\Models\Server;
-use App\Notifications\Channels\DiscordChannel;
-use App\Notifications\Channels\EmailChannel;
-use App\Notifications\Channels\SlackChannel;
-use App\Notifications\Channels\TelegramChannel;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
+use App\Notifications\Dto\PushoverMessage;
use App\Notifications\Dto\SlackMessage;
use Illuminate\Notifications\Messages\MailMessage;
@@ -30,25 +27,7 @@ public function via(object $notifiable): array
return [];
}
- $channels = [];
- $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;
- }
- if ($isEmailEnabled) {
- $channels[] = EmailChannel::class;
- }
- if ($isTelegramEnabled) {
- $channels[] = TelegramChannel::class;
- }
- if ($isSlackEnabled) {
- $channels[] = SlackChannel::class;
- }
-
- return $channels;
+ return $notifiable->getEnabledChannels('server_reachable');
}
public function toMail(): MailMessage
@@ -71,6 +50,15 @@ public function toDiscord(): DiscordMessage
);
}
+ public function toPushover(): PushoverMessage
+ {
+ return new PushoverMessage(
+ title: 'Server revived',
+ message: "Server '{$this->server->name}' revived. All automations & integrations are turned on again!",
+ level: 'success',
+ );
+ }
+
public function toTelegram(): array
{
return [
diff --git a/app/Notifications/Server/Unreachable.php b/app/Notifications/Server/Unreachable.php
index 2a90d7552..fe90cc610 100644
--- a/app/Notifications/Server/Unreachable.php
+++ b/app/Notifications/Server/Unreachable.php
@@ -3,12 +3,9 @@
namespace App\Notifications\Server;
use App\Models\Server;
-use App\Notifications\Channels\DiscordChannel;
-use App\Notifications\Channels\EmailChannel;
-use App\Notifications\Channels\SlackChannel;
-use App\Notifications\Channels\TelegramChannel;
use App\Notifications\CustomEmailNotification;
use App\Notifications\Dto\DiscordMessage;
+use App\Notifications\Dto\PushoverMessage;
use App\Notifications\Dto\SlackMessage;
use Illuminate\Notifications\Messages\MailMessage;
@@ -30,26 +27,7 @@ public function via(object $notifiable): array
return [];
}
- $channels = [];
- $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;
- }
- if ($isEmailEnabled) {
- $channels[] = EmailChannel::class;
- }
- if ($isTelegramEnabled) {
- $channels[] = TelegramChannel::class;
- }
- if ($isSlackEnabled) {
- $channels[] = SlackChannel::class;
- }
-
- return $channels;
+ return $notifiable->getEnabledChannels('server_unreachable');
}
public function toMail(): ?MailMessage
@@ -83,6 +61,15 @@ public function toTelegram(): ?array
];
}
+ public function toPushover(): PushoverMessage
+ {
+ return new PushoverMessage(
+ title: 'Server unreachable',
+ level: 'error',
+ message: "Your server '{$this->server->name}' is unreachable. All automations & integrations are turned off!
diff --git a/resources/views/emails/docker-cleanup-failed.blade.php b/resources/views/emails/docker-cleanup-failed.blade.php
new file mode 100644
index 000000000..a12f20fa8
--- /dev/null
+++ b/resources/views/emails/docker-cleanup-failed.blade.php
@@ -0,0 +1,8 @@
+
+Docker Cleanup on {{ $name }} FAILED with the following error:
+
+
+{{ $text }}
+
+
+
diff --git a/resources/views/emails/docker-cleanup-success.blade.php b/resources/views/emails/docker-cleanup-success.blade.php
new file mode 100644
index 000000000..8671d74c8
--- /dev/null
+++ b/resources/views/emails/docker-cleanup-success.blade.php
@@ -0,0 +1,9 @@
+
+Docker Cleanup on {{ $name }} succeeded with the following message:
+
+
+
+{{ $text }}
+
+
+
diff --git a/resources/views/emails/scheduled-task-success.blade.php b/resources/views/emails/scheduled-task-success.blade.php
new file mode 100644
index 000000000..44ef8fa58
--- /dev/null
+++ b/resources/views/emails/scheduled-task-success.blade.php
@@ -0,0 +1,9 @@
+
+Scheduled task ({{ $task->name }}) completed successfully with the following output:
+
+
+{{ $output }}
+
+
+Click [here]({{ $url }}) to view the task.
+
diff --git a/resources/views/emails/waitlist-confirmation.blade.php b/resources/views/emails/waitlist-confirmation.blade.php
deleted file mode 100644
index afd22916a..000000000
--- a/resources/views/emails/waitlist-confirmation.blade.php
+++ /dev/null
@@ -1,7 +0,0 @@
-
-Someone added this email to the Coolify Cloud's waitlist. [Click here]({{ $confirmation_url }}) to confirm!
-
-The link will expire in {{ config('constants.waitlist.expiration') }} minutes.
-
-You have no idea what [Coolify Cloud](https://coolify.io) is or this waitlist? [Click here]({{ $cancel_url }}) to remove you from the waitlist.
-
diff --git a/resources/views/emails/waitlist-invitation.blade.php b/resources/views/emails/waitlist-invitation.blade.php
deleted file mode 100644
index de8d64650..000000000
--- a/resources/views/emails/waitlist-invitation.blade.php
+++ /dev/null
@@ -1,3 +0,0 @@
-
-You have been invited to join the Coolify Cloud: [Get Started]({{ $loginLink }})
-
diff --git a/resources/views/livewire/notifications/discord.blade.php b/resources/views/livewire/notifications/discord.blade.php
index af6f98b0a..6caf31176 100644
--- a/resources/views/livewire/notifications/discord.blade.php
+++ b/resources/views/livewire/notifications/discord.blade.php
@@ -3,7 +3,7 @@
Notifications | Coolify
-
- @if ($discordEnabled)
-
Subscribe to events
-
- @if (isDev())
-
- @endif
-
-
-
-
-
+
Notification Settings
+
+ Select events for which you would like to receive Discord notifications.
+