Merge branch 'next' into fix-postgres-init-scripts
This commit is contained in:
		| @@ -3,7 +3,6 @@ | |||||||
| /public/build | /public/build | ||||||
| /public/hot | /public/hot | ||||||
| /public/storage | /public/storage | ||||||
| /storage/*.key |  | ||||||
| /vendor | /vendor | ||||||
| .env | .env | ||||||
| .env.backup | .env.backup | ||||||
| @@ -25,3 +24,15 @@ yarn-error.log | |||||||
| .ignition.json | .ignition.json | ||||||
| .env.dusk.local | .env.dusk.local | ||||||
| docker/coolify-realtime/node_modules | 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 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -47,7 +47,7 @@ jobs: | |||||||
|         uses: docker/build-push-action@v6 |         uses: docker/build-push-action@v6 | ||||||
|         with: |         with: | ||||||
|           context: . |           context: . | ||||||
|           file: docker/prod/Dockerfile |           file: docker/production/Dockerfile | ||||||
|           platforms: linux/amd64 |           platforms: linux/amd64 | ||||||
|           push: true |           push: true | ||||||
|           tags: | |           tags: | | ||||||
| @@ -82,7 +82,7 @@ jobs: | |||||||
|         uses: docker/build-push-action@v6 |         uses: docker/build-push-action@v6 | ||||||
|         with: |         with: | ||||||
|           context: . |           context: . | ||||||
|           file: docker/prod/Dockerfile |           file: docker/production/Dockerfile | ||||||
|           platforms: linux/aarch64 |           platforms: linux/aarch64 | ||||||
|           push: true |           push: true | ||||||
|           tags: | |           tags: | | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								.github/workflows/coolify-staging-build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/coolify-staging-build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -42,7 +42,7 @@ jobs: | |||||||
|         uses: docker/build-push-action@v6 |         uses: docker/build-push-action@v6 | ||||||
|         with: |         with: | ||||||
|           context: . |           context: . | ||||||
|           file: docker/prod/Dockerfile |           file: docker/production/Dockerfile | ||||||
|           platforms: linux/amd64 |           platforms: linux/amd64 | ||||||
|           push: true |           push: true | ||||||
|           tags: | |           tags: | | ||||||
| @@ -75,7 +75,7 @@ jobs: | |||||||
|         uses: docker/build-push-action@v6 |         uses: docker/build-push-action@v6 | ||||||
|         with: |         with: | ||||||
|           context: . |           context: . | ||||||
|           file: docker/prod/Dockerfile |           file: docker/production/Dockerfile | ||||||
|           platforms: linux/aarch64 |           platforms: linux/aarch64 | ||||||
|           push: true |           push: true | ||||||
|           tags: | |           tags: | | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							| @@ -40,7 +40,8 @@ Special thanks to our biggest sponsors! | |||||||
| 
 | 
 | ||||||
| ### Special Sponsors | ### Special Sponsors | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| * [CCCareers](https://cccareers.org/) - A career development platform connecting coding bootcamp graduates with job opportunities in the tech industry. | * [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. | * [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 thanks to our biggest sponsors! | |||||||
| * [SupaGuide](https://supa.guide/?ref=coolify.io) - A comprehensive resource hub offering guides and tutorials for web development using Supabase. | * [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. | * [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. | * [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. | * [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. | * [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. | * [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 @@ Special thanks to our biggest sponsors! | |||||||
| <a href="https://github.com/monocursive"><img src="https://github.com/monocursive.png" width="60px" alt="Michael Mazurczak" /></a> | <a href="https://github.com/monocursive"><img src="https://github.com/monocursive.png" width="60px" alt="Michael Mazurczak" /></a> | ||||||
| <a href="https://formbricks.com/?utm_source=coolify.io"><img src="https://github.com/formbricks.png" width="60px" alt="Formbricks" /></a> | <a href="https://formbricks.com/?utm_source=coolify.io"><img src="https://github.com/formbricks.png" width="60px" alt="Formbricks" /></a> | ||||||
| <a href="https://startupfa.me?utm_source=coolify.io"><img src="https://github.com/startupfame.png" width="60px" alt="StartupFame" /></a> | <a href="https://startupfa.me?utm_source=coolify.io"><img src="https://github.com/startupfame.png" width="60px" alt="StartupFame" /></a> | ||||||
|  | <a href="https://bsky.app/profile/jyc.dev"><img src="https://github.com/jycouet.png" width="60px" alt="jyc.dev" /></a> | ||||||
|  | <a href="https://bitlaunch.io/?utm_source=coolify.io"><img src="https://github.com/bitlaunchio.png" width="60px" alt="BitLaunch" /></a> | ||||||
|  | <a href="https://internetgarden.co/?utm_source=coolify.io"><img src="./other/logos/internetgarden.ico" width="60px" alt="Internet Garden" /></a> | ||||||
| <a href="https://jonasjaeger.com?utm_source=coolify.io"><img src="https://github.com/toxin20.png" width="60px" alt="Jonas Jaeger" /></a> | <a href="https://jonasjaeger.com?utm_source=coolify.io"><img src="https://github.com/toxin20.png" width="60px" alt="Jonas Jaeger" /></a> | ||||||
| <a href="https://github.com/therealjp?utm_source=coolify.io"><img src="https://github.com/therealjp.png" width="60px" alt="JP" /></a> | <a href="https://github.com/therealjp?utm_source=coolify.io"><img src="https://github.com/therealjp.png" width="60px" alt="JP" /></a> | ||||||
| <a href="https://evercam.io/?utm_source=coolify.io"><img src="https://github.com/evercam.png" width="60px" alt="Evercam" /></a> | <a href="https://evercam.io/?utm_source=coolify.io"><img src="https://github.com/evercam.png" width="60px" alt="Evercam" /></a> | ||||||
|   | |||||||
| @@ -18,7 +18,6 @@ class CleanupUnreachableServers extends Command | |||||||
|         if ($servers->count() > 0) { |         if ($servers->count() > 0) { | ||||||
|             foreach ($servers as $server) { |             foreach ($servers as $server) { | ||||||
|                 echo "Cleanup unreachable server ($server->id) with name $server->name"; |                 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([ |                 $server->update([ | ||||||
|                     'ip' => '1.2.3.4', |                     'ip' => '1.2.3.4', | ||||||
|                 ]); |                 ]); | ||||||
|   | |||||||
| @@ -76,7 +76,5 @@ class Dev extends Command | |||||||
|         } else { |         } else { | ||||||
|             echo "Instance already initialized.\n"; |             echo "Instance already initialized.\n"; | ||||||
|         } |         } | ||||||
|         // Set permissions
 |  | ||||||
|         Process::run(['chmod', '-R', 'o+rwx', '.']); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,14 +2,12 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Console\Commands; | namespace App\Console\Commands; | ||||||
| 
 | 
 | ||||||
| use App\Jobs\SendConfirmationForWaitlistJob; |  | ||||||
| use App\Models\Application; | use App\Models\Application; | ||||||
| use App\Models\ApplicationPreview; | use App\Models\ApplicationPreview; | ||||||
| use App\Models\ScheduledDatabaseBackup; | use App\Models\ScheduledDatabaseBackup; | ||||||
| use App\Models\Server; | use App\Models\Server; | ||||||
| use App\Models\StandalonePostgresql; | use App\Models\StandalonePostgresql; | ||||||
| use App\Models\Team; | use App\Models\Team; | ||||||
| use App\Models\Waitlist; |  | ||||||
| use App\Notifications\Application\DeploymentFailed; | use App\Notifications\Application\DeploymentFailed; | ||||||
| use App\Notifications\Application\DeploymentSuccess; | use App\Notifications\Application\DeploymentSuccess; | ||||||
| use App\Notifications\Application\StatusChanged; | use App\Notifications\Application\StatusChanged; | ||||||
| @@ -64,8 +62,6 @@ class Emails extends Command | |||||||
|                 'backup-success' => 'Database - Backup Success', |                 'backup-success' => 'Database - Backup Success', | ||||||
|                 'backup-failed' => 'Database - Backup Failed', |                 'backup-failed' => 'Database - Backup Failed', | ||||||
|                 // 'invitation-link' => 'Invitation Link',
 |                 // '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-before-trial' => 'REAL - Registered Users Before Trial without Subscription', | ||||||
|                 'realusers-server-lost-connection' => 'REAL - Server Lost Connection', |                 'realusers-server-lost-connection' => 'REAL - Server Lost Connection', | ||||||
|             ], |             ], | ||||||
| @@ -187,7 +183,7 @@ class Emails extends Command | |||||||
|                         'team_id' => 0, |                         'team_id' => 0, | ||||||
|                     ]); |                     ]); | ||||||
|                 } |                 } | ||||||
|                 // $this->mail = (new BackupSuccess($backup->frequency, $db->name))->toMail();
 |                 //$this->mail = (new BackupSuccess($backup->frequency, $db->name))->toMail();
 | ||||||
|                 $this->sendEmail(); |                 $this->sendEmail(); | ||||||
|                 break; |                 break; | ||||||
|                 // case 'invitation-link':
 |                 // case 'invitation-link':
 | ||||||
| @@ -204,23 +200,6 @@ class Emails extends Command | |||||||
|                 //     $this->mail = (new InvitationLink($user))->toMail();
 |                 //     $this->mail = (new InvitationLink($user))->toMail();
 | ||||||
|                 //     $this->sendEmail();
 |                 //     $this->sendEmail();
 | ||||||
|                 //     break;
 |                 //     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': |             case 'realusers-before-trial': | ||||||
|                 $this->mail = new MailMessage; |                 $this->mail = new MailMessage; | ||||||
|                 $this->mail->view('emails.before-trial-conversion'); |                 $this->mail->view('emails.before-trial-conversion'); | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ class Horizon extends Command | |||||||
|     public function handle() |     public function handle() | ||||||
|     { |     { | ||||||
|         if (config('constants.horizon.is_horizon_enabled')) { |         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'); |             $this->call('horizon'); | ||||||
|             exit(0); |             exit(0); | ||||||
|         } else { |         } else { | ||||||
|   | |||||||
| @@ -55,10 +55,8 @@ class Init extends Command | |||||||
|         } else { |         } else { | ||||||
|             $this->cleanup_in_progress_application_deployments(); |             $this->cleanup_in_progress_application_deployments(); | ||||||
|         } |         } | ||||||
|         echo "[3]: Cleanup Redis keys.\n"; |  | ||||||
|         $this->call('cleanup:redis'); |         $this->call('cleanup:redis'); | ||||||
| 
 | 
 | ||||||
|         echo "[4]: Cleanup stucked resources.\n"; |  | ||||||
|         $this->call('cleanup:stucked-resources'); |         $this->call('cleanup:stucked-resources'); | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
| @@ -114,7 +112,6 @@ class Init extends Command | |||||||
| 
 | 
 | ||||||
|     private function optimize() |     private function optimize() | ||||||
|     { |     { | ||||||
|         echo "[1]: Optimizing Laravel (caching config, routes, views).\n"; |  | ||||||
|         Artisan::call('optimize:clear'); |         Artisan::call('optimize:clear'); | ||||||
|         Artisan::call('optimize'); |         Artisan::call('optimize'); | ||||||
|     } |     } | ||||||
| @@ -189,7 +186,6 @@ class Init extends Command | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 if ($commands->isNotEmpty()) { |                 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); |                     remote_process(command: $commands, type: ActivityTypes::INLINE->value, server: $server, ignore_errors: false); | ||||||
|                 } |                 } | ||||||
|             } catch (\Throwable $e) { |             } catch (\Throwable $e) { | ||||||
| @@ -232,15 +228,14 @@ class Init extends Command | |||||||
|         $settings = instanceSettings(); |         $settings = instanceSettings(); | ||||||
|         $do_not_track = data_get($settings, 'do_not_track'); |         $do_not_track = data_get($settings, 'do_not_track'); | ||||||
|         if ($do_not_track == true) { |         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; |             return; | ||||||
|         } |         } | ||||||
|         try { |         try { | ||||||
|             Http::get("https://undead.coolify.io/v4/alive?appId=$id&version=$version"); |             Http::get("https://undead.coolify.io/v4/alive?appId=$id&version=$version"); | ||||||
|             echo "[2]: Sending live signal!\n"; |  | ||||||
|         } catch (\Throwable $e) { |         } 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 @@ class Init extends Command | |||||||
|             } |             } | ||||||
|             $queued_inprogress_deployments = ApplicationDeploymentQueue::whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS->value, ApplicationDeploymentStatus::QUEUED->value])->get(); |             $queued_inprogress_deployments = ApplicationDeploymentQueue::whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS->value, ApplicationDeploymentStatus::QUEUED->value])->get(); | ||||||
|             foreach ($queued_inprogress_deployments as $deployment) { |             foreach ($queued_inprogress_deployments as $deployment) { | ||||||
|                 echo "Cleaning up deployment: {$deployment->id}\n"; |  | ||||||
|                 $deployment->status = ApplicationDeploymentStatus::FAILED->value; |                 $deployment->status = ApplicationDeploymentStatus::FAILED->value; | ||||||
|                 $deployment->save(); |                 $deployment->save(); | ||||||
|             } |             } | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								app/Console/Commands/Migration.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								app/Console/Commands/Migration.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Console\Commands; | ||||||
|  | 
 | ||||||
|  | use Illuminate\Console\Command; | ||||||
|  | 
 | ||||||
|  | class Migration extends Command | ||||||
|  | { | ||||||
|  |     protected $signature = 'start:migration'; | ||||||
|  | 
 | ||||||
|  |     protected $description = 'Start Migration'; | ||||||
|  | 
 | ||||||
|  |     public function handle() | ||||||
|  |     { | ||||||
|  |         if (config('constants.migration.is_migration_enabled')) { | ||||||
|  |             $this->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); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -59,9 +59,10 @@ class NotifyDemo extends Command | |||||||
|                 <div class="text-yellow-500"> Channels: </div> |                 <div class="text-yellow-500"> Channels: </div> | ||||||
|                 <ul class="text-coolify"> |                 <ul class="text-coolify"> | ||||||
|                     <li>email</li> |                     <li>email</li> | ||||||
|                     <li>slack</li> |  | ||||||
|                     <li>discord</li> |                     <li>discord</li> | ||||||
|                     <li>telegram</li> |                     <li>telegram</li> | ||||||
|  |                     <li>slack</li> | ||||||
|  |                     <li>pushover</li> | ||||||
|                 </ul> |                 </ul> | ||||||
|             </div> |             </div> | ||||||
|         </div> |         </div> | ||||||
| @@ -72,6 +73,6 @@ class NotifyDemo extends Command | |||||||
|         <div class="mr-1"> |         <div class="mr-1"> | ||||||
|             In which manner you wish a <strong class="text-coolify">coolified</strong> notification? |             In which manner you wish a <strong class="text-coolify">coolified</strong> notification? | ||||||
|         </div> |         </div> | ||||||
|         HTML, ['email', 'slack', 'discord', 'telegram']); |         HTML, ['email', 'discord', 'telegram', 'slack', 'pushover']); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ class Scheduler extends Command | |||||||
|     public function handle() |     public function handle() | ||||||
|     { |     { | ||||||
|         if (config('constants.horizon.is_scheduler_enabled')) { |         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'); |             $this->call('schedule:work'); | ||||||
|             exit(0); |             exit(0); | ||||||
|         } else { |         } else { | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								app/Console/Commands/Seeder.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								app/Console/Commands/Seeder.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Console\Commands; | ||||||
|  | 
 | ||||||
|  | use Illuminate\Console\Command; | ||||||
|  | 
 | ||||||
|  | class Seeder extends Command | ||||||
|  | { | ||||||
|  |     protected $signature = 'start:seeder'; | ||||||
|  | 
 | ||||||
|  |     protected $description = 'Start Seeder'; | ||||||
|  | 
 | ||||||
|  |     public function handle() | ||||||
|  |     { | ||||||
|  |         if (config('constants.seeder.is_seeder_enabled')) { | ||||||
|  |             $this->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); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,114 +0,0 @@ | |||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Console\Commands; |  | ||||||
| 
 |  | ||||||
| use App\Models\User; |  | ||||||
| use App\Models\Waitlist; |  | ||||||
| use Illuminate\Console\Command; |  | ||||||
| use Illuminate\Notifications\Messages\MailMessage; |  | ||||||
| use Illuminate\Support\Facades\Crypt; |  | ||||||
| use Illuminate\Support\Facades\Hash; |  | ||||||
| use Illuminate\Support\Str; |  | ||||||
| 
 |  | ||||||
| class WaitlistInvite extends Command |  | ||||||
| { |  | ||||||
|     public Waitlist|User|null $next_patient = null; |  | ||||||
| 
 |  | ||||||
|     public ?string $password = null; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * The name and signature of the console command. |  | ||||||
|      * |  | ||||||
|      * @var string |  | ||||||
|      */ |  | ||||||
|     protected $signature = 'waitlist:invite {--people=1} {--only-email} {email?}'; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * The console command description. |  | ||||||
|      * |  | ||||||
|      * @var string |  | ||||||
|      */ |  | ||||||
|     protected $description = 'Send invitation to the next user (or by email) in the waitlist'; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Execute the console command. |  | ||||||
|      */ |  | ||||||
|     public function handle() |  | ||||||
|     { |  | ||||||
|         $people = $this->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. 📧'); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,58 +0,0 @@ | |||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Console\Commands; |  | ||||||
| 
 |  | ||||||
| use App\Actions\Server\ServerCheck; |  | ||||||
| use App\Enums\ProxyStatus; |  | ||||||
| use App\Enums\ProxyTypes; |  | ||||||
| use App\Models\Server; |  | ||||||
| use Illuminate\Console\Command; |  | ||||||
| use Str; |  | ||||||
| 
 |  | ||||||
| class Weird extends Command |  | ||||||
| { |  | ||||||
|     protected $signature = 'weird {--number=1} {--run}'; |  | ||||||
| 
 |  | ||||||
|     protected $description = 'Weird stuff'; |  | ||||||
| 
 |  | ||||||
|     public function handle() |  | ||||||
|     { |  | ||||||
|         try { |  | ||||||
|             if (! isDev()) { |  | ||||||
|                 $this->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()); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -218,7 +218,7 @@ class Kernel extends ConsoleKernel | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             if ($service) { |             if ($service) { | ||||||
|                 if (str($service->status())->contains('running') === false) { |                 if (str($service->status)->contains('running') === false) { | ||||||
|                     continue; |                     continue; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -53,11 +53,7 @@ class ResourcesController extends Controller | |||||||
|         $resources = $resources->flatten(); |         $resources = $resources->flatten(); | ||||||
|         $resources = $resources->map(function ($resource) { |         $resources = $resources->map(function ($resource) { | ||||||
|             $payload = $resource->toArray(); |             $payload = $resource->toArray(); | ||||||
|             if ($resource->getMorphClass() === \App\Models\Service::class) { |             $payload['status'] = $resource->status; | ||||||
|                 $payload['status'] = $resource->status(); |  | ||||||
|             } else { |  | ||||||
|                 $payload['status'] = $resource->status; |  | ||||||
|             } |  | ||||||
|             $payload['type'] = $resource->type(); |             $payload['type'] = $resource->type(); | ||||||
| 
 | 
 | ||||||
|             return $payload; |             return $payload; | ||||||
|   | |||||||
| @@ -154,11 +154,7 @@ class ServersController extends Controller | |||||||
|                     'created_at' => $resource->created_at, |                     'created_at' => $resource->created_at, | ||||||
|                     'updated_at' => $resource->updated_at, |                     'updated_at' => $resource->updated_at, | ||||||
|                 ]; |                 ]; | ||||||
|                 if ($resource->type() === 'service') { |                 $payload['status'] = $resource->status; | ||||||
|                     $payload['status'] = $resource->status(); |  | ||||||
|                 } else { |  | ||||||
|                     $payload['status'] = $resource->status; |  | ||||||
|                 } |  | ||||||
| 
 | 
 | ||||||
|                 return $payload; |                 return $payload; | ||||||
|             }); |             }); | ||||||
| @@ -237,11 +233,7 @@ class ServersController extends Controller | |||||||
|                 'created_at' => $resource->created_at, |                 'created_at' => $resource->created_at, | ||||||
|                 'updated_at' => $resource->updated_at, |                 'updated_at' => $resource->updated_at, | ||||||
|             ]; |             ]; | ||||||
|             if ($resource->type() === 'service') { |             $payload['status'] = $resource->status; | ||||||
|                 $payload['status'] = $resource->status(); |  | ||||||
|             } else { |  | ||||||
|                 $payload['status'] = $resource->status; |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             return $payload; |             return $payload; | ||||||
|         }); |         }); | ||||||
|   | |||||||
| @@ -25,6 +25,8 @@ class ServicesController extends Controller | |||||||
|             $service->makeHidden([ |             $service->makeHidden([ | ||||||
|                 'docker_compose_raw', |                 'docker_compose_raw', | ||||||
|                 'docker_compose', |                 'docker_compose', | ||||||
|  |                 'value', | ||||||
|  |                 'real_value', | ||||||
|             ]); |             ]); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @@ -1070,7 +1072,7 @@ class ServicesController extends Controller | |||||||
|         if (! $service) { |         if (! $service) { | ||||||
|             return response()->json(['message' => 'Service not found.'], 404); |             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); |             return response()->json(['message' => 'Service is already running.'], 400); | ||||||
|         } |         } | ||||||
|         StartService::dispatch($service); |         StartService::dispatch($service); | ||||||
| @@ -1148,7 +1150,7 @@ class ServicesController extends Controller | |||||||
|         if (! $service) { |         if (! $service) { | ||||||
|             return response()->json(['message' => 'Service not found.'], 404); |             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); |             return response()->json(['message' => 'Service is already stopped.'], 400); | ||||||
|         } |         } | ||||||
|         StopService::dispatch($service); |         StopService::dispatch($service); | ||||||
|   | |||||||
| @@ -42,15 +42,13 @@ class Controller extends BaseController | |||||||
|     public function email_verify(EmailVerificationRequest $request) |     public function email_verify(EmailVerificationRequest $request) | ||||||
|     { |     { | ||||||
|         $request->fulfill(); |         $request->fulfill(); | ||||||
|         $name = request()->user()?->name; |  | ||||||
| 
 | 
 | ||||||
|         // send_internal_notification("User {$name} verified their email address.");
 |  | ||||||
|         return redirect(RouteServiceProvider::HOME); |         return redirect(RouteServiceProvider::HOME); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function forgot_password(Request $request) |     public function forgot_password(Request $request) | ||||||
|     { |     { | ||||||
|         if (is_transactional_emails_active()) { |         if (is_transactional_emails_enabled()) { | ||||||
|             $arrayOfRequest = $request->only(Fortify::email()); |             $arrayOfRequest = $request->only(Fortify::email()); | ||||||
|             $request->merge([ |             $request->merge([ | ||||||
|                 'email' => Str::lower($arrayOfRequest['email']), |                 'email' => Str::lower($arrayOfRequest['email']), | ||||||
|   | |||||||
| @@ -1,63 +0,0 @@ | |||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Http\Controllers\Webhook; |  | ||||||
| 
 |  | ||||||
| use App\Http\Controllers\Controller; |  | ||||||
| use App\Models\Waitlist as ModelsWaitlist; |  | ||||||
| use Exception; |  | ||||||
| use Illuminate\Http\Request; |  | ||||||
| 
 |  | ||||||
| class Waitlist extends Controller |  | ||||||
| { |  | ||||||
|     public function confirm(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) { |  | ||||||
|                 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'); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -2409,7 +2409,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); | |||||||
|             if (! $this->only_this_server) { |             if (! $this->only_this_server) { | ||||||
|                 $this->deploy_to_additional_destinations(); |                 $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)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -1,28 +0,0 @@ | |||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Jobs; |  | ||||||
| 
 |  | ||||||
| use App\Actions\License\CheckResaleLicense; |  | ||||||
| use Illuminate\Bus\Queueable; |  | ||||||
| use Illuminate\Contracts\Queue\ShouldBeEncrypted; |  | ||||||
| use Illuminate\Contracts\Queue\ShouldQueue; |  | ||||||
| use Illuminate\Foundation\Bus\Dispatchable; |  | ||||||
| use Illuminate\Queue\InteractsWithQueue; |  | ||||||
| use Illuminate\Queue\SerializesModels; |  | ||||||
| 
 |  | ||||||
| class CheckResaleLicenseJob implements ShouldBeEncrypted, ShouldQueue |  | ||||||
| { |  | ||||||
|     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; |  | ||||||
| 
 |  | ||||||
|     public function __construct() {} |  | ||||||
| 
 |  | ||||||
|     public function handle(): void |  | ||||||
|     { |  | ||||||
|         try { |  | ||||||
|             CheckResaleLicense::run(); |  | ||||||
|         } catch (\Throwable $e) { |  | ||||||
|             send_internal_notification('CheckResaleLicenseJob failed with: '.$e->getMessage()); |  | ||||||
|             throw $e; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -32,8 +32,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue | |||||||
| 
 | 
 | ||||||
|     public Server $server; |     public Server $server; | ||||||
| 
 | 
 | ||||||
|     public ScheduledDatabaseBackup $backup; |  | ||||||
| 
 |  | ||||||
|     public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database; |     public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database; | ||||||
| 
 | 
 | ||||||
|     public ?string $container_name = null; |     public ?string $container_name = null; | ||||||
| @@ -58,10 +56,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue | |||||||
| 
 | 
 | ||||||
|     public ?S3Storage $s3 = null; |     public ?S3Storage $s3 = null; | ||||||
| 
 | 
 | ||||||
|     public function __construct($backup) |     public function __construct(public ScheduledDatabaseBackup $backup) | ||||||
|     { |     { | ||||||
|         $this->onQueue('high'); |         $this->onQueue('high'); | ||||||
|         $this->backup = $backup; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function handle(): void |     public function handle(): void | ||||||
| @@ -306,7 +303,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue | |||||||
|                     if ($this->backup->save_s3) { |                     if ($this->backup->save_s3) { | ||||||
|                         $this->upload_to_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([ |                     $this->backup_log->update([ | ||||||
|                         'status' => 'success', |                         'status' => 'success', | ||||||
|                         'message' => $this->backup_output, |                         'message' => $this->backup_output, | ||||||
|   | |||||||
| @@ -4,7 +4,8 @@ namespace App\Jobs; | |||||||
| 
 | 
 | ||||||
| use App\Actions\Server\CleanupDocker; | use App\Actions\Server\CleanupDocker; | ||||||
| use App\Models\Server; | 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\Bus\Queueable; | ||||||
| use Illuminate\Contracts\Queue\ShouldBeEncrypted; | use Illuminate\Contracts\Queue\ShouldBeEncrypted; | ||||||
| use Illuminate\Contracts\Queue\ShouldQueue; | use Illuminate\Contracts\Queue\ShouldQueue; | ||||||
| @@ -12,7 +13,6 @@ use Illuminate\Foundation\Bus\Dispatchable; | |||||||
| use Illuminate\Queue\InteractsWithQueue; | use Illuminate\Queue\InteractsWithQueue; | ||||||
| use Illuminate\Queue\Middleware\WithoutOverlapping; | use Illuminate\Queue\Middleware\WithoutOverlapping; | ||||||
| use Illuminate\Queue\SerializesModels; | use Illuminate\Queue\SerializesModels; | ||||||
| use Illuminate\Support\Facades\Log; |  | ||||||
| 
 | 
 | ||||||
| class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue | class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue | ||||||
| { | { | ||||||
| @@ -38,35 +38,36 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue | |||||||
|                 return; |                 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(); |             $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); |                 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; |                 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) { |             if ($this->usageBefore >= $this->server->settings->docker_cleanup_threshold) { | ||||||
|                 CleanupDocker::run(server: $this->server); |                 CleanupDocker::run(server: $this->server); | ||||||
|                 $usageAfter = $this->server->getDiskUsage(); |                 $usageAfter = $this->server->getDiskUsage(); | ||||||
|                 if ($usageAfter < $this->usageBefore) { |                 $diskSaved = $this->usageBefore - $usageAfter; | ||||||
|                     $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); |                 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 { |                 } 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 { |             } 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) { |         } catch (\Throwable $e) { | ||||||
|             CleanupDocker::run(server: $this->server); |             $this->server->team?->notify(new DockerCleanupFailed($this->server, 'Docker cleanup job failed with the following error: '.$e->getMessage())); | ||||||
|             Log::error('DockerCleanupJob failed: '.$e->getMessage()); |  | ||||||
|             throw $e; |             throw $e; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ use App\Models\Server; | |||||||
| use App\Models\Service; | use App\Models\Service; | ||||||
| use App\Models\Team; | use App\Models\Team; | ||||||
| use App\Notifications\ScheduledTask\TaskFailed; | use App\Notifications\ScheduledTask\TaskFailed; | ||||||
|  | use App\Notifications\ScheduledTask\TaskSuccess; | ||||||
| use Illuminate\Bus\Queueable; | use Illuminate\Bus\Queueable; | ||||||
| use Illuminate\Contracts\Queue\ShouldQueue; | use Illuminate\Contracts\Queue\ShouldQueue; | ||||||
| use Illuminate\Foundation\Bus\Dispatchable; | use Illuminate\Foundation\Bus\Dispatchable; | ||||||
| @@ -111,6 +112,8 @@ class ScheduledTaskJob implements ShouldQueue | |||||||
|                         'message' => $this->task_output, |                         'message' => $this->task_output, | ||||||
|                     ]); |                     ]); | ||||||
| 
 | 
 | ||||||
|  |                     $this->team?->notify(new TaskSuccess($this->task, $this->task_output)); | ||||||
|  | 
 | ||||||
|                     return; |                     return; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @@ -125,7 +128,6 @@ class ScheduledTaskJob implements ShouldQueue | |||||||
|                 ]); |                 ]); | ||||||
|             } |             } | ||||||
|             $this->team?->notify(new TaskFailed($this->task, $e->getMessage())); |             $this->team?->notify(new TaskFailed($this->task, $e->getMessage())); | ||||||
|             // send_internal_notification('ScheduledTaskJob failed with: ' . $e->getMessage());
 |  | ||||||
|             throw $e; |             throw $e; | ||||||
|         } finally { |         } finally { | ||||||
|             ScheduledTaskDone::dispatch($this->team->id); |             ScheduledTaskDone::dispatch($this->team->id); | ||||||
|   | |||||||
| @@ -1,37 +0,0 @@ | |||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Jobs; |  | ||||||
| 
 |  | ||||||
| use Illuminate\Bus\Queueable; |  | ||||||
| use Illuminate\Contracts\Queue\ShouldBeEncrypted; |  | ||||||
| use Illuminate\Contracts\Queue\ShouldQueue; |  | ||||||
| use Illuminate\Foundation\Bus\Dispatchable; |  | ||||||
| use Illuminate\Notifications\Messages\MailMessage; |  | ||||||
| use Illuminate\Queue\InteractsWithQueue; |  | ||||||
| use Illuminate\Queue\SerializesModels; |  | ||||||
| 
 |  | ||||||
| class SendConfirmationForWaitlistJob implements ShouldBeEncrypted, ShouldQueue |  | ||||||
| { |  | ||||||
|     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; |  | ||||||
| 
 |  | ||||||
|     public function __construct(public string $email, public string $uuid) {} |  | ||||||
| 
 |  | ||||||
|     public function handle() |  | ||||||
|     { |  | ||||||
|         try { |  | ||||||
|             $mail = new MailMessage; |  | ||||||
|             $confirmation_url = base_url().'/webhooks/waitlist/confirm?email='.$this->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; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										50
									
								
								app/Jobs/SendMessageToPushoverJob.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								app/Jobs/SendMessageToPushoverJob.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Jobs; | ||||||
|  | 
 | ||||||
|  | use App\Notifications\Dto\PushoverMessage; | ||||||
|  | use Illuminate\Bus\Queueable; | ||||||
|  | use Illuminate\Contracts\Queue\ShouldBeEncrypted; | ||||||
|  | use Illuminate\Contracts\Queue\ShouldQueue; | ||||||
|  | use Illuminate\Foundation\Bus\Dispatchable; | ||||||
|  | use Illuminate\Queue\InteractsWithQueue; | ||||||
|  | use Illuminate\Queue\SerializesModels; | ||||||
|  | use Illuminate\Support\Facades\Http; | ||||||
|  | 
 | ||||||
|  | class SendMessageToPushoverJob implements ShouldBeEncrypted, ShouldQueue | ||||||
|  | { | ||||||
|  |     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * The number of times the job may be attempted. | ||||||
|  |      * | ||||||
|  |      * @var int | ||||||
|  |      */ | ||||||
|  |     public $tries = 5; | ||||||
|  | 
 | ||||||
|  |     public $backoff = 10; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * The maximum number of unhandled exceptions to allow before failing. | ||||||
|  |      */ | ||||||
|  |     public int $maxExceptions = 5; | ||||||
|  | 
 | ||||||
|  |     public function __construct( | ||||||
|  |         public PushoverMessage $message, | ||||||
|  |         public string $token, | ||||||
|  |         public string $user, | ||||||
|  |     ) { | ||||||
|  |         $this->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()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -32,7 +32,7 @@ class SendMessageToTelegramJob implements ShouldBeEncrypted, ShouldQueue | |||||||
|         public array $buttons, |         public array $buttons, | ||||||
|         public string $token, |         public string $token, | ||||||
|         public string $chatId, |         public string $chatId, | ||||||
|         public ?string $topicId = null, |         public ?string $threadId = null, | ||||||
|     ) { |     ) { | ||||||
|         $this->onQueue('high'); |         $this->onQueue('high'); | ||||||
|     } |     } | ||||||
| @@ -67,8 +67,8 @@ class SendMessageToTelegramJob implements ShouldBeEncrypted, ShouldQueue | |||||||
|             'chat_id' => $this->chatId, |             'chat_id' => $this->chatId, | ||||||
|             'text' => $this->text, |             'text' => $this->text, | ||||||
|         ]; |         ]; | ||||||
|         if ($this->topicId) { |         if ($this->threadId) { | ||||||
|             $payload['message_thread_id'] = $this->topicId; |             $payload['message_thread_id'] = $this->threadId; | ||||||
|         } |         } | ||||||
|         $response = Http::post($url, $payload); |         $response = Http::post($url, $payload); | ||||||
|         if ($response->failed()) { |         if ($response->failed()) { | ||||||
|   | |||||||
| @@ -173,8 +173,8 @@ class StripeProcessJob implements ShouldQueue | |||||||
|                     $userId = data_get($data, 'metadata.user_id'); |                     $userId = data_get($data, 'metadata.user_id'); | ||||||
|                     $customerId = data_get($data, 'customer'); |                     $customerId = data_get($data, 'customer'); | ||||||
|                     $status = data_get($data, 'status'); |                     $status = data_get($data, 'status'); | ||||||
|                     $subscriptionId = data_get($data, 'items.data.0.subscription'); |                     $subscriptionId = data_get($data, 'items.data.0.subscription') ?? data_get($data, 'id'); | ||||||
|                     $planId = data_get($data, 'items.data.0.plan.id'); |                     $planId = data_get($data, 'items.data.0.plan.id') ?? data_get($data, 'plan.id'); | ||||||
|                     if (Str::contains($excludedPlans, $planId)) { |                     if (Str::contains($excludedPlans, $planId)) { | ||||||
|                         send_internal_notification('Subscription excluded.'); |                         send_internal_notification('Subscription excluded.'); | ||||||
|                         break; |                         break; | ||||||
| @@ -218,9 +218,18 @@ class StripeProcessJob implements ShouldQueue | |||||||
|                         'stripe_cancel_at_period_end' => $cancelAtPeriodEnd, |                         'stripe_cancel_at_period_end' => $cancelAtPeriodEnd, | ||||||
|                     ]); |                     ]); | ||||||
|                     if ($status === 'paused' || $status === 'incomplete_expired') { |                     if ($status === 'paused' || $status === 'incomplete_expired') { | ||||||
|                         $subscription->update([ |                         if ($subscription->stripe_subscription_id === $subscriptionId) { | ||||||
|                             'stripe_invoice_paid' => false, |                             $subscription->update([ | ||||||
|                         ]); |                                 'stripe_invoice_paid' => false, | ||||||
|  |                             ]); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     if ($status === 'active') { | ||||||
|  |                         if ($subscription->stripe_subscription_id === $subscriptionId) { | ||||||
|  |                             $subscription->update([ | ||||||
|  |                                 'stripe_invoice_paid' => true, | ||||||
|  |                             ]); | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|                     if ($feedback) { |                     if ($feedback) { | ||||||
|                         $reason = "Cancellation feedback for {$customerId}: '".$feedback."'"; |                         $reason = "Cancellation feedback for {$customerId}: '".$feedback."'"; | ||||||
| @@ -228,13 +237,24 @@ class StripeProcessJob implements ShouldQueue | |||||||
|                             $reason .= ' with comment: \''.$comment."'"; |                             $reason .= ' with comment: \''.$comment."'"; | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  | 
 | ||||||
|                     break; |                     break; | ||||||
|                 case 'customer.subscription.deleted': |                 case 'customer.subscription.deleted': | ||||||
|                     // End subscription
 |  | ||||||
|                     $customerId = data_get($data, 'customer'); |                     $customerId = data_get($data, 'customer'); | ||||||
|                     $subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail(); |                     $subscriptionId = data_get($data, 'id'); | ||||||
|                     $team = data_get($subscription, 'team'); |                     $subscription = Subscription::where('stripe_customer_id', $customerId)->where('stripe_subscription_id', $subscriptionId)->first(); | ||||||
|                     $team?->subscriptionEnded(); |                     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; |                     break; | ||||||
|                 default: |                 default: | ||||||
|                     throw new \RuntimeException("Unhandled event type: {$type}"); |                     throw new \RuntimeException("Unhandled event type: {$type}"); | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Livewire\Notifications; | namespace App\Livewire\Notifications; | ||||||
| 
 | 
 | ||||||
|  | use App\Models\DiscordNotificationSettings; | ||||||
| use App\Models\Team; | use App\Models\Team; | ||||||
| use App\Notifications\Test; | use App\Notifications\Test; | ||||||
| use Livewire\Attributes\Validate; | use Livewire\Attributes\Validate; | ||||||
| @@ -11,6 +12,8 @@ class Discord extends Component | |||||||
| { | { | ||||||
|     public Team $team; |     public Team $team; | ||||||
| 
 | 
 | ||||||
|  |     public DiscordNotificationSettings $settings; | ||||||
|  | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[Validate(['boolean'])]
 | ||||||
|     public bool $discordEnabled = false; |     public bool $discordEnabled = false; | ||||||
| 
 | 
 | ||||||
| @@ -18,27 +21,46 @@ class Discord extends Component | |||||||
|     public ?string $discordWebhookUrl = null; |     public ?string $discordWebhookUrl = null; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[Validate(['boolean'])]
 | ||||||
|     public bool $discordNotificationsTest = false; |     public bool $deploymentSuccessDiscordNotifications = false; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[Validate(['boolean'])]
 | ||||||
|     public bool $discordNotificationsDeployments = false; |     public bool $deploymentFailureDiscordNotifications = true; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[Validate(['boolean'])]
 | ||||||
|     public bool $discordNotificationsStatusChanges = false; |     public bool $statusChangeDiscordNotifications = false; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[Validate(['boolean'])]
 | ||||||
|     public bool $discordNotificationsDatabaseBackups = false; |     public bool $backupSuccessDiscordNotifications = false; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[Validate(['boolean'])]
 | ||||||
|     public bool $discordNotificationsScheduledTasks = false; |     public bool $backupFailureDiscordNotifications = true; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[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() |     public function mount() | ||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
|             $this->team = auth()->user()->currentTeam(); |             $this->team = auth()->user()->currentTeam(); | ||||||
|  |             $this->settings = $this->team->discordNotificationSettings; | ||||||
|             $this->syncData(); |             $this->syncData(); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
| @@ -49,25 +71,40 @@ class Discord extends Component | |||||||
|     { |     { | ||||||
|         if ($toModel) { |         if ($toModel) { | ||||||
|             $this->validate(); |             $this->validate(); | ||||||
|             $this->team->discord_enabled = $this->discordEnabled; |             $this->settings->discord_enabled = $this->discordEnabled; | ||||||
|             $this->team->discord_webhook_url = $this->discordWebhookUrl; |             $this->settings->discord_webhook_url = $this->discordWebhookUrl; | ||||||
|             $this->team->discord_notifications_test = $this->discordNotificationsTest; | 
 | ||||||
|             $this->team->discord_notifications_deployments = $this->discordNotificationsDeployments; |             $this->settings->deployment_success_discord_notifications = $this->deploymentSuccessDiscordNotifications; | ||||||
|             $this->team->discord_notifications_status_changes = $this->discordNotificationsStatusChanges; |             $this->settings->deployment_failure_discord_notifications = $this->deploymentFailureDiscordNotifications; | ||||||
|             $this->team->discord_notifications_database_backups = $this->discordNotificationsDatabaseBackups; |             $this->settings->status_change_discord_notifications = $this->statusChangeDiscordNotifications; | ||||||
|             $this->team->discord_notifications_scheduled_tasks = $this->discordNotificationsScheduledTasks; |             $this->settings->backup_success_discord_notifications = $this->backupSuccessDiscordNotifications; | ||||||
|             $this->team->discord_notifications_server_disk_usage = $this->discordNotificationsServerDiskUsage; |             $this->settings->backup_failure_discord_notifications = $this->backupFailureDiscordNotifications; | ||||||
|             $this->team->save(); |             $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(); |             refreshSession(); | ||||||
|         } else { |         } else { | ||||||
|             $this->discordEnabled = $this->team->discord_enabled; |             $this->discordEnabled = $this->settings->discord_enabled; | ||||||
|             $this->discordWebhookUrl = $this->team->discord_webhook_url; |             $this->discordWebhookUrl = $this->settings->discord_webhook_url; | ||||||
|             $this->discordNotificationsTest = $this->team->discord_notifications_test; | 
 | ||||||
|             $this->discordNotificationsDeployments = $this->team->discord_notifications_deployments; |             $this->deploymentSuccessDiscordNotifications = $this->settings->deployment_success_discord_notifications; | ||||||
|             $this->discordNotificationsStatusChanges = $this->team->discord_notifications_status_changes; |             $this->deploymentFailureDiscordNotifications = $this->settings->deployment_failure_discord_notifications; | ||||||
|             $this->discordNotificationsDatabaseBackups = $this->team->discord_notifications_database_backups; |             $this->statusChangeDiscordNotifications = $this->settings->status_change_discord_notifications; | ||||||
|             $this->discordNotificationsScheduledTasks = $this->team->discord_notifications_scheduled_tasks; |             $this->backupSuccessDiscordNotifications = $this->settings->backup_success_discord_notifications; | ||||||
|             $this->discordNotificationsServerDiskUsage = $this->team->discord_notifications_server_disk_usage; |             $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 @@ class Discord extends Component | |||||||
|     public function sendTestNotification() |     public function sendTestNotification() | ||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
|             $this->team->notify(new Test); |             $this->team->notify(new Test(channel: 'discord')); | ||||||
|             $this->dispatch('success', 'Test notification sent.'); |             $this->dispatch('success', 'Test notification sent.'); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Livewire\Notifications; | namespace App\Livewire\Notifications; | ||||||
| 
 | 
 | ||||||
|  | use App\Models\EmailNotificationSettings; | ||||||
| use App\Models\Team; | use App\Models\Team; | ||||||
| use App\Notifications\Test; | use App\Notifications\Test; | ||||||
| use Illuminate\Support\Facades\RateLimiter; | use Illuminate\Support\Facades\RateLimiter; | ||||||
| @@ -11,17 +12,20 @@ use Livewire\Component; | |||||||
| 
 | 
 | ||||||
| class Email extends Component | class Email extends Component | ||||||
| { | { | ||||||
|  |     protected $listeners = ['refresh' => '$refresh']; | ||||||
|  | 
 | ||||||
|  |     #[Locked]
 | ||||||
|     public Team $team; |     public Team $team; | ||||||
| 
 | 
 | ||||||
|  |     #[Locked]
 | ||||||
|  |     public EmailNotificationSettings $settings; | ||||||
|  | 
 | ||||||
|     #[Locked]
 |     #[Locked]
 | ||||||
|     public string $emails; |     public string $emails; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[Validate(['boolean'])]
 | ||||||
|     public bool $smtpEnabled = false; |     public bool $smtpEnabled = false; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['boolean'])]
 |  | ||||||
|     public bool $useInstanceEmailSettings = false; |  | ||||||
| 
 |  | ||||||
|     #[Validate(['nullable', 'email'])]
 |     #[Validate(['nullable', 'email'])]
 | ||||||
|     public ?string $smtpFromAddress = null; |     public ?string $smtpFromAddress = null; | ||||||
| 
 | 
 | ||||||
| @@ -34,11 +38,11 @@ class Email extends Component | |||||||
|     #[Validate(['nullable', 'string'])]
 |     #[Validate(['nullable', 'string'])]
 | ||||||
|     public ?string $smtpHost = null; |     public ?string $smtpHost = null; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['nullable', 'numeric'])]
 |     #[Validate(['nullable', 'numeric', 'min:1', 'max:65535'])]
 | ||||||
|     public ?int $smtpPort = null; |     public ?int $smtpPort = null; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['nullable', 'string', 'in:tls,ssl,none'])]
 |     #[Validate(['nullable', 'string', 'in:tls,ssl,none'])]
 | ||||||
|     public ?string $smtpEncryption = null; |     public ?string $smtpEncryption = 'tls'; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['nullable', 'string'])]
 |     #[Validate(['nullable', 'string'])]
 | ||||||
|     public ?string $smtpUsername = null; |     public ?string $smtpUsername = null; | ||||||
| @@ -50,29 +54,50 @@ class Email extends Component | |||||||
|     public ?int $smtpTimeout = null; |     public ?int $smtpTimeout = null; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[Validate(['boolean'])]
 | ||||||
|     public bool $smtpNotificationsTest = false; |     public bool $resendEnabled = 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; |  | ||||||
| 
 | 
 | ||||||
|     #[Validate(['nullable', 'string'])]
 |     #[Validate(['nullable', 'string'])]
 | ||||||
|     public ?string $resendApiKey = null; |     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'])]
 |     #[Validate(['nullable', 'email'])]
 | ||||||
|     public ?string $testEmailAddress = null; |     public ?string $testEmailAddress = null; | ||||||
| 
 | 
 | ||||||
| @@ -81,7 +106,9 @@ class Email extends Component | |||||||
|         try { |         try { | ||||||
|             $this->team = auth()->user()->currentTeam(); |             $this->team = auth()->user()->currentTeam(); | ||||||
|             $this->emails = auth()->user()->email; |             $this->emails = auth()->user()->email; | ||||||
|  |             $this->settings = $this->team->emailNotificationSettings; | ||||||
|             $this->syncData(); |             $this->syncData(); | ||||||
|  |             $this->testEmailAddress = auth()->user()->email; | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|         } |         } | ||||||
| @@ -91,47 +118,191 @@ class Email extends Component | |||||||
|     { |     { | ||||||
|         if ($toModel) { |         if ($toModel) { | ||||||
|             $this->validate(); |             $this->validate(); | ||||||
|             $this->team->smtp_enabled = $this->smtpEnabled; |             $this->settings->smtp_enabled = $this->smtpEnabled; | ||||||
|             $this->team->smtp_from_address = $this->smtpFromAddress; |             $this->settings->smtp_from_address = $this->smtpFromAddress; | ||||||
|             $this->team->smtp_from_name = $this->smtpFromName; |             $this->settings->smtp_from_name = $this->smtpFromName; | ||||||
|             $this->team->smtp_host = $this->smtpHost; |             $this->settings->smtp_recipients = $this->smtpRecipients; | ||||||
|             $this->team->smtp_port = $this->smtpPort; |             $this->settings->smtp_host = $this->smtpHost; | ||||||
|             $this->team->smtp_encryption = $this->smtpEncryption; |             $this->settings->smtp_port = $this->smtpPort; | ||||||
|             $this->team->smtp_username = $this->smtpUsername; |             $this->settings->smtp_encryption = $this->smtpEncryption; | ||||||
|             $this->team->smtp_password = $this->smtpPassword; |             $this->settings->smtp_username = $this->smtpUsername; | ||||||
|             $this->team->smtp_timeout = $this->smtpTimeout; |             $this->settings->smtp_password = $this->smtpPassword; | ||||||
|             $this->team->smtp_recipients = $this->smtpRecipients; |             $this->settings->smtp_timeout = $this->smtpTimeout; | ||||||
|             $this->team->smtp_notifications_test = $this->smtpNotificationsTest; | 
 | ||||||
|             $this->team->smtp_notifications_deployments = $this->smtpNotificationsDeployments; |             $this->settings->resend_enabled = $this->resendEnabled; | ||||||
|             $this->team->smtp_notifications_status_changes = $this->smtpNotificationsStatusChanges; |             $this->settings->resend_api_key = $this->resendApiKey; | ||||||
|             $this->team->smtp_notifications_database_backups = $this->smtpNotificationsDatabaseBackups; | 
 | ||||||
|             $this->team->smtp_notifications_scheduled_tasks = $this->smtpNotificationsScheduledTasks; |             $this->settings->use_instance_email_settings = $this->useInstanceEmailSettings; | ||||||
|             $this->team->smtp_notifications_server_disk_usage = $this->smtpNotificationsServerDiskUsage; | 
 | ||||||
|             $this->team->use_instance_email_settings = $this->useInstanceEmailSettings; |             $this->settings->deployment_success_email_notifications = $this->deploymentSuccessEmailNotifications; | ||||||
|             $this->team->resend_enabled = $this->resendEnabled; |             $this->settings->deployment_failure_email_notifications = $this->deploymentFailureEmailNotifications; | ||||||
|             $this->team->resend_api_key = $this->resendApiKey; |             $this->settings->status_change_email_notifications = $this->statusChangeEmailNotifications; | ||||||
|             $this->team->save(); |             $this->settings->backup_success_email_notifications = $this->backupSuccessEmailNotifications; | ||||||
|             refreshSession(); |             $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 { |         } else { | ||||||
|             $this->smtpEnabled = $this->team->smtp_enabled; |             $this->smtpEnabled = $this->settings->smtp_enabled; | ||||||
|             $this->smtpFromAddress = $this->team->smtp_from_address; |             $this->smtpFromAddress = $this->settings->smtp_from_address; | ||||||
|             $this->smtpFromName = $this->team->smtp_from_name; |             $this->smtpFromName = $this->settings->smtp_from_name; | ||||||
|             $this->smtpHost = $this->team->smtp_host; |             $this->smtpRecipients = $this->settings->smtp_recipients; | ||||||
|             $this->smtpPort = $this->team->smtp_port; |             $this->smtpHost = $this->settings->smtp_host; | ||||||
|             $this->smtpEncryption = $this->team->smtp_encryption; |             $this->smtpPort = $this->settings->smtp_port; | ||||||
|             $this->smtpUsername = $this->team->smtp_username; |             $this->smtpEncryption = $this->settings->smtp_encryption; | ||||||
|             $this->smtpPassword = $this->team->smtp_password; |             $this->smtpUsername = $this->settings->smtp_username; | ||||||
|             $this->smtpTimeout = $this->team->smtp_timeout; |             $this->smtpPassword = $this->settings->smtp_password; | ||||||
|             $this->smtpRecipients = $this->team->smtp_recipients; |             $this->smtpTimeout = $this->settings->smtp_timeout; | ||||||
|             $this->smtpNotificationsTest = $this->team->smtp_notifications_test; | 
 | ||||||
|             $this->smtpNotificationsDeployments = $this->team->smtp_notifications_deployments; |             $this->resendEnabled = $this->settings->resend_enabled; | ||||||
|             $this->smtpNotificationsStatusChanges = $this->team->smtp_notifications_status_changes; |             $this->resendApiKey = $this->settings->resend_api_key; | ||||||
|             $this->smtpNotificationsDatabaseBackups = $this->team->smtp_notifications_database_backups; | 
 | ||||||
|             $this->smtpNotificationsScheduledTasks = $this->team->smtp_notifications_scheduled_tasks; |             $this->useInstanceEmailSettings = $this->settings->use_instance_email_settings; | ||||||
|             $this->smtpNotificationsServerDiskUsage = $this->team->smtp_notifications_server_disk_usage; | 
 | ||||||
|             $this->useInstanceEmailSettings = $this->team->use_instance_email_settings; |             $this->deploymentSuccessEmailNotifications = $this->settings->deployment_success_email_notifications; | ||||||
|             $this->resendEnabled = $this->team->resend_enabled; |             $this->deploymentFailureEmailNotifications = $this->settings->deployment_failure_email_notifications; | ||||||
|             $this->resendApiKey = $this->team->resend_api_key; |             $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 @@ class Email extends Component | |||||||
|                 'test-email:'.$this->team->id, |                 'test-email:'.$this->team->id, | ||||||
|                 $perMinute = 0, |                 $perMinute = 0, | ||||||
|                 function () { |                 function () { | ||||||
|                     $this->team?->notify(new Test($this->testEmailAddress)); |                     $this->team?->notify(new Test($this->testEmailAddress, 'email')); | ||||||
|                     $this->dispatch('success', 'Test Email sent.'); |                     $this->dispatch('success', 'Test Email sent.'); | ||||||
|                 }, |                 }, | ||||||
|                 $decaySeconds = 10, |                 $decaySeconds = 10, | ||||||
| @@ -163,70 +334,6 @@ class Email extends Component | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     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() |     public function copyFromInstanceSettings() | ||||||
|     { |     { | ||||||
|         $settings = instanceSettings(); |         $settings = instanceSettings(); | ||||||
|   | |||||||
							
								
								
									
										184
									
								
								app/Livewire/Notifications/Pushover.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								app/Livewire/Notifications/Pushover.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,184 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Livewire\Notifications; | ||||||
|  | 
 | ||||||
|  | use App\Models\PushoverNotificationSettings; | ||||||
|  | use App\Models\Team; | ||||||
|  | use App\Notifications\Test; | ||||||
|  | use Livewire\Attributes\Locked; | ||||||
|  | use Livewire\Attributes\Validate; | ||||||
|  | use Livewire\Component; | ||||||
|  | 
 | ||||||
|  | class Pushover extends Component | ||||||
|  | { | ||||||
|  |     protected $listeners = ['refresh' => '$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'); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -2,15 +2,23 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Livewire\Notifications; | namespace App\Livewire\Notifications; | ||||||
| 
 | 
 | ||||||
|  | use App\Models\SlackNotificationSettings; | ||||||
| use App\Models\Team; | use App\Models\Team; | ||||||
| use App\Notifications\Test; | use App\Notifications\Test; | ||||||
|  | use Livewire\Attributes\Locked; | ||||||
| use Livewire\Attributes\Validate; | use Livewire\Attributes\Validate; | ||||||
| use Livewire\Component; | use Livewire\Component; | ||||||
| 
 | 
 | ||||||
| class Slack extends Component | class Slack extends Component | ||||||
| { | { | ||||||
|  |     protected $listeners = ['refresh' => '$refresh']; | ||||||
|  | 
 | ||||||
|  |     #[Locked]
 | ||||||
|     public Team $team; |     public Team $team; | ||||||
| 
 | 
 | ||||||
|  |     #[Locked]
 | ||||||
|  |     public SlackNotificationSettings $settings; | ||||||
|  | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[Validate(['boolean'])]
 | ||||||
|     public bool $slackEnabled = false; |     public bool $slackEnabled = false; | ||||||
| 
 | 
 | ||||||
| @@ -18,27 +26,46 @@ class Slack extends Component | |||||||
|     public ?string $slackWebhookUrl = null; |     public ?string $slackWebhookUrl = null; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[Validate(['boolean'])]
 | ||||||
|     public bool $slackNotificationsTest = false; |     public bool $deploymentSuccessSlackNotifications = false; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[Validate(['boolean'])]
 | ||||||
|     public bool $slackNotificationsDeployments = false; |     public bool $deploymentFailureSlackNotifications = true; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[Validate(['boolean'])]
 | ||||||
|     public bool $slackNotificationsStatusChanges = false; |     public bool $statusChangeSlackNotifications = false; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[Validate(['boolean'])]
 | ||||||
|     public bool $slackNotificationsDatabaseBackups = false; |     public bool $backupSuccessSlackNotifications = false; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[Validate(['boolean'])]
 | ||||||
|     public bool $slackNotificationsScheduledTasks = false; |     public bool $backupFailureSlackNotifications = true; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[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() |     public function mount() | ||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
|             $this->team = auth()->user()->currentTeam(); |             $this->team = auth()->user()->currentTeam(); | ||||||
|  |             $this->settings = $this->team->slackNotificationSettings; | ||||||
|             $this->syncData(); |             $this->syncData(); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
| @@ -49,25 +76,40 @@ class Slack extends Component | |||||||
|     { |     { | ||||||
|         if ($toModel) { |         if ($toModel) { | ||||||
|             $this->validate(); |             $this->validate(); | ||||||
|             $this->team->slack_enabled = $this->slackEnabled; |             $this->settings->slack_enabled = $this->slackEnabled; | ||||||
|             $this->team->slack_webhook_url = $this->slackWebhookUrl; |             $this->settings->slack_webhook_url = $this->slackWebhookUrl; | ||||||
|             $this->team->slack_notifications_test = $this->slackNotificationsTest; | 
 | ||||||
|             $this->team->slack_notifications_deployments = $this->slackNotificationsDeployments; |             $this->settings->deployment_success_slack_notifications = $this->deploymentSuccessSlackNotifications; | ||||||
|             $this->team->slack_notifications_status_changes = $this->slackNotificationsStatusChanges; |             $this->settings->deployment_failure_slack_notifications = $this->deploymentFailureSlackNotifications; | ||||||
|             $this->team->slack_notifications_database_backups = $this->slackNotificationsDatabaseBackups; |             $this->settings->status_change_slack_notifications = $this->statusChangeSlackNotifications; | ||||||
|             $this->team->slack_notifications_scheduled_tasks = $this->slackNotificationsScheduledTasks; |             $this->settings->backup_success_slack_notifications = $this->backupSuccessSlackNotifications; | ||||||
|             $this->team->slack_notifications_server_disk_usage = $this->slackNotificationsServerDiskUsage; |             $this->settings->backup_failure_slack_notifications = $this->backupFailureSlackNotifications; | ||||||
|             $this->team->save(); |             $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(); |             refreshSession(); | ||||||
|         } else { |         } else { | ||||||
|             $this->slackEnabled = $this->team->slack_enabled; |             $this->slackEnabled = $this->settings->slack_enabled; | ||||||
|             $this->slackWebhookUrl = $this->team->slack_webhook_url; |             $this->slackWebhookUrl = $this->settings->slack_webhook_url; | ||||||
|             $this->slackNotificationsTest = $this->team->slack_notifications_test; | 
 | ||||||
|             $this->slackNotificationsDeployments = $this->team->slack_notifications_deployments; |             $this->deploymentSuccessSlackNotifications = $this->settings->deployment_success_slack_notifications; | ||||||
|             $this->slackNotificationsStatusChanges = $this->team->slack_notifications_status_changes; |             $this->deploymentFailureSlackNotifications = $this->settings->deployment_failure_slack_notifications; | ||||||
|             $this->slackNotificationsDatabaseBackups = $this->team->slack_notifications_database_backups; |             $this->statusChangeSlackNotifications = $this->settings->status_change_slack_notifications; | ||||||
|             $this->slackNotificationsScheduledTasks = $this->team->slack_notifications_scheduled_tasks; |             $this->backupSuccessSlackNotifications = $this->settings->backup_success_slack_notifications; | ||||||
|             $this->slackNotificationsServerDiskUsage = $this->team->slack_notifications_server_disk_usage; |             $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 @@ class Slack extends Component | |||||||
|             $this->slackEnabled = false; |             $this->slackEnabled = false; | ||||||
| 
 | 
 | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|  |         } finally { | ||||||
|  |             $this->dispatch('refresh'); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -93,6 +137,8 @@ class Slack extends Component | |||||||
|             $this->syncData(true); |             $this->syncData(true); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|  |         } finally { | ||||||
|  |             $this->dispatch('refresh'); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -117,7 +163,7 @@ class Slack extends Component | |||||||
|     public function sendTestNotification() |     public function sendTestNotification() | ||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
|             $this->team->notify(new Test); |             $this->team->notify(new Test(channel: 'slack')); | ||||||
|             $this->dispatch('success', 'Test notification sent.'); |             $this->dispatch('success', 'Test notification sent.'); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|   | |||||||
| @@ -3,14 +3,22 @@ | |||||||
| namespace App\Livewire\Notifications; | namespace App\Livewire\Notifications; | ||||||
| 
 | 
 | ||||||
| use App\Models\Team; | use App\Models\Team; | ||||||
|  | use App\Models\TelegramNotificationSettings; | ||||||
| use App\Notifications\Test; | use App\Notifications\Test; | ||||||
|  | use Livewire\Attributes\Locked; | ||||||
| use Livewire\Attributes\Validate; | use Livewire\Attributes\Validate; | ||||||
| use Livewire\Component; | use Livewire\Component; | ||||||
| 
 | 
 | ||||||
| class Telegram extends Component | class Telegram extends Component | ||||||
| { | { | ||||||
|  |     protected $listeners = ['refresh' => '$refresh']; | ||||||
|  | 
 | ||||||
|  |     #[Locked]
 | ||||||
|     public Team $team; |     public Team $team; | ||||||
| 
 | 
 | ||||||
|  |     #[Locked]
 | ||||||
|  |     public TelegramNotificationSettings $settings; | ||||||
|  | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[Validate(['boolean'])]
 | ||||||
|     public bool $telegramEnabled = false; |     public bool $telegramEnabled = false; | ||||||
| 
 | 
 | ||||||
| @@ -21,42 +29,82 @@ class Telegram extends Component | |||||||
|     public ?string $telegramChatId = null; |     public ?string $telegramChatId = null; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[Validate(['boolean'])]
 | ||||||
|     public bool $telegramNotificationsTest = false; |     public bool $deploymentSuccessTelegramNotifications = false; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[Validate(['boolean'])]
 | ||||||
|     public bool $telegramNotificationsDeployments = false; |     public bool $deploymentFailureTelegramNotifications = true; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[Validate(['boolean'])]
 | ||||||
|     public bool $telegramNotificationsStatusChanges = false; |     public bool $statusChangeTelegramNotifications = false; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[Validate(['boolean'])]
 | ||||||
|     public bool $telegramNotificationsDatabaseBackups = false; |     public bool $backupSuccessTelegramNotifications = false; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[Validate(['boolean'])]
 | ||||||
|     public bool $telegramNotificationsScheduledTasks = false; |     public bool $backupFailureTelegramNotifications = true; | ||||||
| 
 |  | ||||||
|     #[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; |  | ||||||
| 
 | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[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() |     public function mount() | ||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
|             $this->team = auth()->user()->currentTeam(); |             $this->team = auth()->user()->currentTeam(); | ||||||
|  |             $this->settings = $this->team->telegramNotificationSettings; | ||||||
|             $this->syncData(); |             $this->syncData(); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
| @@ -67,39 +115,68 @@ class Telegram extends Component | |||||||
|     { |     { | ||||||
|         if ($toModel) { |         if ($toModel) { | ||||||
|             $this->validate(); |             $this->validate(); | ||||||
|             $this->team->telegram_enabled = $this->telegramEnabled; |             $this->settings->telegram_enabled = $this->telegramEnabled; | ||||||
|             $this->team->telegram_token = $this->telegramToken; |             $this->settings->telegram_token = $this->telegramToken; | ||||||
|             $this->team->telegram_chat_id = $this->telegramChatId; |             $this->settings->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->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() |     public function instantSave() | ||||||
| @@ -108,6 +185,8 @@ class Telegram extends Component | |||||||
|             $this->syncData(true); |             $this->syncData(true); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|  |         } finally { | ||||||
|  |             $this->dispatch('refresh'); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -137,6 +216,8 @@ class Telegram extends Component | |||||||
|             $this->telegramEnabled = false; |             $this->telegramEnabled = false; | ||||||
| 
 | 
 | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|  |         } finally { | ||||||
|  |             $this->dispatch('refresh'); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -150,7 +231,7 @@ class Telegram extends Component | |||||||
|     public function sendTestNotification() |     public function sendTestNotification() | ||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
|             $this->team->notify(new Test); |             $this->team->notify(new Test(channel: 'telegram')); | ||||||
|             $this->dispatch('success', 'Test notification sent.'); |             $this->dispatch('success', 'Test notification sent.'); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|   | |||||||
| @@ -327,7 +327,7 @@ class General extends Component | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function set_redirect() |     public function setRedirect() | ||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
|             $has_www = collect($this->application->fqdns)->filter(fn ($fqdn) => str($fqdn)->contains('www.'))->count(); |             $has_www = collect($this->application->fqdns)->filter(fn ($fqdn) => str($fqdn)->contains('www.'))->count(); | ||||||
| @@ -360,10 +360,10 @@ class General extends Component | |||||||
|             if ($warning) { |             if ($warning) { | ||||||
|                 $this->dispatch('warning', __('warning.sslipdomain')); |                 $this->dispatch('warning', __('warning.sslipdomain')); | ||||||
|             } |             } | ||||||
|             $this->resetDefaultLabels(); |             // $this->resetDefaultLabels();
 | ||||||
| 
 | 
 | ||||||
|             if ($this->application->isDirty('redirect')) { |             if ($this->application->isDirty('redirect')) { | ||||||
|                 $this->set_redirect(); |                 $this->setRedirect(); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             $this->checkFqdns(); |             $this->checkFqdns(); | ||||||
|   | |||||||
| @@ -9,11 +9,9 @@ class BackupNow extends Component | |||||||
| { | { | ||||||
|     public $backup; |     public $backup; | ||||||
| 
 | 
 | ||||||
|     public function backup_now() |     public function backupNow() | ||||||
|     { |     { | ||||||
|         dispatch(new DatabaseBackupJob( |         DatabaseBackupJob::dispatch($this->backup); | ||||||
|             backup: $this->backup |  | ||||||
|         )); |  | ||||||
|         $this->dispatch('success', 'Backup queued. It will be available in a few minutes.'); |         $this->dispatch('success', 'Backup queued. It will be available in a few minutes.'); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -46,125 +46,84 @@ class Index extends Component | |||||||
|             return redirect()->route('dashboard'); |             return redirect()->route('dashboard'); | ||||||
|         } |         } | ||||||
|         $this->project = $project; |         $this->project = $project; | ||||||
|         $this->environment = $environment; |         $this->environment = $environment->loadCount([ | ||||||
|         $this->applications = $this->environment->applications->load(['tags']); |             '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) { |         $this->applications = $this->applications->map(function ($application) { | ||||||
|             if (data_get($application, 'environment.project.uuid')) { |             $application->hrefLink = route('project.application.configuration', [ | ||||||
|                 $application->hrefLink = route('project.application.configuration', [ |                 'project_uuid' => $this->project->uuid, | ||||||
|                     'project_uuid' => data_get($application, 'environment.project.uuid'), |                 'application_uuid' => $application->uuid, | ||||||
|                     'environment_name' => data_get($application, 'environment.name'), |                 'environment_name' => $this->environment->name, | ||||||
|                     'application_uuid' => data_get($application, 'uuid'), |             ]); | ||||||
|                 ]); |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             return $application; |             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; |         // Load all database resources in a single query per type
 | ||||||
|         }); |         $databaseTypes = [ | ||||||
|         $this->redis = $this->environment->redis->load(['tags'])->sortBy('name'); |             'postgresqls' => 'postgresqls', | ||||||
|         $this->redis = $this->redis->map(function ($redis) { |             'redis' => 'redis', | ||||||
|             if (data_get($redis, 'environment.project.uuid')) { |             'mongodbs' => 'mongodbs', | ||||||
|                 $redis->hrefLink = route('project.database.configuration', [ |             'mysqls' => 'mysqls', | ||||||
|                     'project_uuid' => data_get($redis, 'environment.project.uuid'), |             'mariadbs' => 'mariadbs', | ||||||
|                     'environment_name' => data_get($redis, 'environment.name'), |             'keydbs' => 'keydbs', | ||||||
|                     'database_uuid' => data_get($redis, 'uuid'), |             'dragonflies' => 'dragonflies', | ||||||
|                 ]); |             'clickhouses' => 'clickhouses', | ||||||
|             } |         ]; | ||||||
| 
 | 
 | ||||||
|             return $redis; |         // Load all server-related data first to prevent duplicate queries
 | ||||||
|         }); |         $serverData = $this->environment->applications() | ||||||
|         $this->mongodbs = $this->environment->mongodbs->load(['tags'])->sortBy('name'); |             ->with(['destination.server.settings']) | ||||||
|         $this->mongodbs = $this->mongodbs->map(function ($mongodb) { |             ->get() | ||||||
|             if (data_get($mongodb, 'environment.project.uuid')) { |             ->pluck('destination.server') | ||||||
|                 $mongodb->hrefLink = route('project.database.configuration', [ |             ->filter() | ||||||
|                     'project_uuid' => data_get($mongodb, 'environment.project.uuid'), |             ->unique('id'); | ||||||
|                     'environment_name' => data_get($mongodb, 'environment.name'), |  | ||||||
|                     'database_uuid' => data_get($mongodb, 'uuid'), |  | ||||||
|                 ]); |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             return $mongodb; |         foreach ($databaseTypes as $property => $relation) { | ||||||
|         }); |             $this->{$property} = $this->environment->{$relation}()->with([ | ||||||
|         $this->mysqls = $this->environment->mysqls->load(['tags'])->sortBy('name'); |                 'tags', | ||||||
|         $this->mysqls = $this->mysqls->map(function ($mysql) { |                 'destination.server.settings', | ||||||
|             if (data_get($mysql, 'environment.project.uuid')) { |             ])->get()->sortBy('name'); | ||||||
|                 $mysql->hrefLink = route('project.database.configuration', [ |             $this->{$property} = $this->{$property}->map(function ($db) { | ||||||
|                     'project_uuid' => data_get($mysql, 'environment.project.uuid'), |                 $db->hrefLink = route('project.database.configuration', [ | ||||||
|                     'environment_name' => data_get($mysql, 'environment.name'), |                     'project_uuid' => $this->project->uuid, | ||||||
|                     'database_uuid' => data_get($mysql, 'uuid'), |                     'database_uuid' => $db->uuid, | ||||||
|  |                     'environment_name' => $this->environment->name, | ||||||
|                 ]); |                 ]); | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             return $mysql; |                 return $db; | ||||||
|         }); |             }); | ||||||
|         $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 $mariadb; |         // Load services with their tags and server
 | ||||||
|         }); |         $this->services = $this->environment->services()->with([ | ||||||
|         $this->keydbs = $this->environment->keydbs->load(['tags'])->sortBy('name'); |             'tags', | ||||||
|         $this->keydbs = $this->keydbs->map(function ($keydb) { |             'destination.server.settings', | ||||||
|             if (data_get($keydb, 'environment.project.uuid')) { |         ])->get()->sortBy('name'); | ||||||
|                 $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'); |  | ||||||
|         $this->services = $this->services->map(function ($service) { |         $this->services = $this->services->map(function ($service) { | ||||||
|             if (data_get($service, 'environment.project.uuid')) { |             $service->hrefLink = route('project.service.configuration', [ | ||||||
|                 $service->hrefLink = route('project.service.configuration', [ |                 'project_uuid' => $this->project->uuid, | ||||||
|                     'project_uuid' => data_get($service, 'environment.project.uuid'), |                 'service_uuid' => $service->uuid, | ||||||
|                     'environment_name' => data_get($service, 'environment.name'), |                 'environment_name' => $this->environment->name, | ||||||
|                     'service_uuid' => data_get($service, 'uuid'), |             ]); | ||||||
|                 ]); |  | ||||||
|                 $service->status = $service->status(); |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             return $service; |             return $service; | ||||||
|         }); |         }); | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ class Navbar extends Component | |||||||
| 
 | 
 | ||||||
|     public function mount() |     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->service->isConfigurationChanged(true); | ||||||
|             $this->dispatch('configurationChanged'); |             $this->dispatch('configurationChanged'); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -42,9 +42,11 @@ class ResourceOperations extends Component | |||||||
|         $uuid = (string) new Cuid2; |         $uuid = (string) new Cuid2; | ||||||
|         $server = $new_destination->server; |         $server = $new_destination->server; | ||||||
|         if ($this->resource->getMorphClass() === \App\Models\Application::class) { |         if ($this->resource->getMorphClass() === \App\Models\Application::class) { | ||||||
|  |             $name = 'clone-of-'.str($this->resource->name)->limit(20).'-'.$uuid; | ||||||
|  | 
 | ||||||
|             $new_resource = $this->resource->replicate()->fill([ |             $new_resource = $this->resource->replicate()->fill([ | ||||||
|                 'uuid' => $uuid, |                 'uuid' => $uuid, | ||||||
|                 'name' => $this->resource->name.'-clone-'.$uuid, |                 'name' => $name, | ||||||
|                 'fqdn' => generateFqdn($server, $uuid), |                 'fqdn' => generateFqdn($server, $uuid), | ||||||
|                 'status' => 'exited', |                 'status' => 'exited', | ||||||
|                 'destination_id' => $new_destination->id, |                 'destination_id' => $new_destination->id, | ||||||
| @@ -64,8 +66,12 @@ class ResourceOperations extends Component | |||||||
|             } |             } | ||||||
|             $persistentVolumes = $this->resource->persistentStorages()->get(); |             $persistentVolumes = $this->resource->persistentStorages()->get(); | ||||||
|             foreach ($persistentVolumes as $volume) { |             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([ |                 $newPersistentVolume = $volume->replicate()->fill([ | ||||||
|                     'name' => $new_resource->uuid.'-'.str($volume->name)->afterLast('-'), |                     'name' => $volumeName, | ||||||
|                     'resource_id' => $new_resource->id, |                     'resource_id' => $new_resource->id, | ||||||
|                 ]); |                 ]); | ||||||
|                 $newPersistentVolume->save(); |                 $newPersistentVolume->save(); | ||||||
|   | |||||||
| @@ -23,9 +23,6 @@ class Index extends Component | |||||||
|     #[Validate('nullable|string|max:255')]
 |     #[Validate('nullable|string|max:255')]
 | ||||||
|     public ?string $fqdn = null; |     public ?string $fqdn = null; | ||||||
| 
 | 
 | ||||||
|     #[Validate('nullable|string|max:255')]
 |  | ||||||
|     public ?string $resale_license = null; |  | ||||||
| 
 |  | ||||||
|     #[Validate('required|integer|min:1025|max:65535')]
 |     #[Validate('required|integer|min:1025|max:65535')]
 | ||||||
|     public int $public_port_min; |     public int $public_port_min; | ||||||
| 
 | 
 | ||||||
| @@ -83,7 +80,6 @@ class Index extends Component | |||||||
|         } else { |         } else { | ||||||
|             $this->settings = instanceSettings(); |             $this->settings = instanceSettings(); | ||||||
|             $this->fqdn = $this->settings->fqdn; |             $this->fqdn = $this->settings->fqdn; | ||||||
|             $this->resale_license = $this->settings->resale_license; |  | ||||||
|             $this->public_port_min = $this->settings->public_port_min; |             $this->public_port_min = $this->settings->public_port_min; | ||||||
|             $this->public_port_max = $this->settings->public_port_max; |             $this->public_port_max = $this->settings->public_port_max; | ||||||
|             $this->custom_dns_servers = $this->settings->custom_dns_servers; |             $this->custom_dns_servers = $this->settings->custom_dns_servers; | ||||||
| @@ -122,7 +118,6 @@ class Index extends Component | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $this->settings->fqdn = $this->fqdn; |         $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_min = $this->public_port_min; | ||||||
|         $this->settings->public_port_max = $this->public_port_max; |         $this->settings->public_port_max = $this->public_port_max; | ||||||
|         $this->settings->custom_dns_servers = $this->custom_dns_servers; |         $this->settings->custom_dns_servers = $this->custom_dns_servers; | ||||||
|   | |||||||
| @@ -3,6 +3,10 @@ | |||||||
| namespace App\Livewire; | namespace App\Livewire; | ||||||
| 
 | 
 | ||||||
| use App\Models\InstanceSettings; | 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\Attributes\Validate; | ||||||
| use Livewire\Component; | use Livewire\Component; | ||||||
| 
 | 
 | ||||||
| @@ -10,9 +14,21 @@ class SettingsEmail extends Component | |||||||
| { | { | ||||||
|     public InstanceSettings $settings; |     public InstanceSettings $settings; | ||||||
| 
 | 
 | ||||||
|  |     #[Locked]
 | ||||||
|  |     public Team $team; | ||||||
|  | 
 | ||||||
|     #[Validate(['boolean'])]
 |     #[Validate(['boolean'])]
 | ||||||
|     public bool $smtpEnabled = false; |     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'])]
 |     #[Validate(['nullable', 'string'])]
 | ||||||
|     public ?string $smtpHost = null; |     public ?string $smtpHost = null; | ||||||
| 
 | 
 | ||||||
| @@ -20,29 +36,26 @@ class SettingsEmail extends Component | |||||||
|     public ?int $smtpPort = null; |     public ?int $smtpPort = null; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['nullable', 'string', 'in:tls,ssl,none'])]
 |     #[Validate(['nullable', 'string', 'in:tls,ssl,none'])]
 | ||||||
|     public ?string $smtpEncryption = null; |     public ?string $smtpEncryption = 'tls'; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['nullable', 'string'])]
 |     #[Validate(['nullable', 'string'])]
 | ||||||
|     public ?string $smtpUsername = null; |     public ?string $smtpUsername = null; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['nullable'])]
 |     #[Validate(['nullable', 'string'])]
 | ||||||
|     public ?string $smtpPassword = null; |     public ?string $smtpPassword = null; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['nullable', 'numeric'])]
 |     #[Validate(['nullable', 'numeric'])]
 | ||||||
|     public ?int $smtpTimeout = null; |     public ?int $smtpTimeout = null; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['nullable', 'email'])]
 |  | ||||||
|     public ?string $smtpFromAddress = null; |  | ||||||
| 
 |  | ||||||
|     #[Validate(['nullable', 'string'])]
 |  | ||||||
|     public ?string $smtpFromName = null; |  | ||||||
| 
 |  | ||||||
|     #[Validate(['boolean'])]
 |     #[Validate(['boolean'])]
 | ||||||
|     public bool $resendEnabled = false; |     public bool $resendEnabled = false; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['nullable', 'string'])]
 |     #[Validate(['nullable', 'string'])]
 | ||||||
|     public ?string $resendApiKey = null; |     public ?string $resendApiKey = null; | ||||||
| 
 | 
 | ||||||
|  |     #[Validate(['nullable', 'email'])]
 | ||||||
|  |     public ?string $testEmailAddress = null; | ||||||
|  | 
 | ||||||
|     public function mount() |     public function mount() | ||||||
|     { |     { | ||||||
|         if (isInstanceAdmin() === false) { |         if (isInstanceAdmin() === false) { | ||||||
| @@ -50,6 +63,8 @@ class SettingsEmail extends Component | |||||||
|         } |         } | ||||||
|         $this->settings = instanceSettings(); |         $this->settings = instanceSettings(); | ||||||
|         $this->syncData(); |         $this->syncData(); | ||||||
|  |         $this->team = auth()->user()->currentTeam(); | ||||||
|  |         $this->testEmailAddress = auth()->user()->email; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function syncData(bool $toModel = false) |     public function syncData(bool $toModel = false) | ||||||
| @@ -90,7 +105,7 @@ class SettingsEmail extends Component | |||||||
|         try { |         try { | ||||||
|             $this->resetErrorBag(); |             $this->resetErrorBag(); | ||||||
|             $this->syncData(true); |             $this->syncData(true); | ||||||
|             $this->dispatch('success', 'Settings saved.'); |             $this->dispatch('success', 'Transactional email settings updated.'); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|         } |         } | ||||||
| @@ -99,19 +114,129 @@ class SettingsEmail extends Component | |||||||
|     public function instantSave(string $type) |     public function instantSave(string $type) | ||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
|  |             $this->resetErrorBag(); | ||||||
|  | 
 | ||||||
|             if ($type === 'SMTP') { |             if ($type === 'SMTP') { | ||||||
|                 $this->resendEnabled = false; |                 $this->submitSmtp(); | ||||||
|             } else { |             } elseif ($type === 'Resend') { | ||||||
|                 $this->smtpEnabled = false; |                 $this->submitResend(); | ||||||
|             } |  | ||||||
|             $this->syncData(true); |  | ||||||
|             if ($this->smtpEnabled || $this->resendEnabled) { |  | ||||||
|                 $this->dispatch('success', "{$type} enabled."); |  | ||||||
|             } else { |  | ||||||
|                 $this->dispatch('success', "{$type} disabled."); |  | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|  |             if ($type === 'SMTP') { | ||||||
|  |                 $this->smtpEnabled = false; | ||||||
|  |             } elseif ($type === 'Resend') { | ||||||
|  |                 $this->resendEnabled = false; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             return handleError($e, $this); |             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); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ class SettingsOauth extends Component | |||||||
|             $carry["oauth_settings_map.$setting->provider.client_secret"] = 'nullable'; |             $carry["oauth_settings_map.$setting->provider.client_secret"] = 'nullable'; | ||||||
|             $carry["oauth_settings_map.$setting->provider.redirect_uri"] = 'nullable'; |             $carry["oauth_settings_map.$setting->provider.redirect_uri"] = 'nullable'; | ||||||
|             $carry["oauth_settings_map.$setting->provider.tenant"] = 'nullable'; |             $carry["oauth_settings_map.$setting->provider.tenant"] = 'nullable'; | ||||||
|  |             $carry["oauth_settings_map.$setting->provider.base_url"] = 'nullable'; | ||||||
| 
 | 
 | ||||||
|             return $carry; |             return $carry; | ||||||
|         }, []); |         }, []); | ||||||
| @@ -34,16 +35,30 @@ class SettingsOauth extends Component | |||||||
|         }, []); |         }, []); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function updateOauthSettings() |     private function updateOauthSettings(?string $provider = null) | ||||||
|     { |     { | ||||||
|         foreach (array_values($this->oauth_settings_map) as &$setting) { |         if ($provider) { | ||||||
|             $setting->save(); |             $oauth = $this->oauth_settings_map[$provider]; | ||||||
|  |             if (! $oauth->couldBeEnabled()) { | ||||||
|  |                 $oauth->update(['enabled' => false]); | ||||||
|  |                 throw new \Exception('OAuth settings are not complete for '.$oauth->provider.'.<br/>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() |     public function submit() | ||||||
|   | |||||||
| @@ -1,70 +0,0 @@ | |||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Livewire\Waitlist; |  | ||||||
| 
 |  | ||||||
| use App\Jobs\SendConfirmationForWaitlistJob; |  | ||||||
| use App\Models\User; |  | ||||||
| use App\Models\Waitlist; |  | ||||||
| use Illuminate\Support\Str; |  | ||||||
| use Livewire\Component; |  | ||||||
| 
 |  | ||||||
| class Index extends Component |  | ||||||
| { |  | ||||||
|     public string $email; |  | ||||||
| 
 |  | ||||||
|     public int $users = 0; |  | ||||||
| 
 |  | ||||||
|     public int $waitingInLine = 0; |  | ||||||
| 
 |  | ||||||
|     protected $rules = [ |  | ||||||
|         'email' => '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. <br>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. <br>Please check your email to verify your email address.'); |  | ||||||
| 
 |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|                 $this->dispatch('error', 'You are already on the waitlist. <br>You will be notified when your turn comes. <br>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); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -115,6 +115,12 @@ class Application extends BaseModel | |||||||
| 
 | 
 | ||||||
|     protected static function booted() |     protected static function booted() | ||||||
|     { |     { | ||||||
|  |         static::addGlobalScope('withRelations', function ($builder) { | ||||||
|  |             $builder->withCount([ | ||||||
|  |                 'additional_servers', | ||||||
|  |                 'additional_networks', | ||||||
|  |             ]); | ||||||
|  |         }); | ||||||
|         static::saving(function ($application) { |         static::saving(function ($application) { | ||||||
|             $payload = []; |             $payload = []; | ||||||
|             if ($application->isDirty('fqdn')) { |             if ($application->isDirty('fqdn')) { | ||||||
| @@ -327,7 +333,7 @@ class Application extends BaseModel | |||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function failedTaskLink($task_uuid) |     public function taskLink($task_uuid) | ||||||
|     { |     { | ||||||
|         if (data_get($this, 'environment.project.uuid')) { |         if (data_get($this, 'environment.project.uuid')) { | ||||||
|             $route = route('project.application.scheduled-tasks', [ |             $route = route('project.application.scheduled-tasks', [ | ||||||
| @@ -551,20 +557,21 @@ class Application extends BaseModel | |||||||
|     { |     { | ||||||
|         return Attribute::make( |         return Attribute::make( | ||||||
|             get: function () { |             get: function () { | ||||||
|                 if ($this->additional_servers->count() === 0) { |                 if (! $this->relationLoaded('additional_servers') || $this->additional_servers->count() === 0) { | ||||||
|                     return $this->destination->server->isFunctional(); |                     return $this->destination?->server?->isFunctional() ?? false; | ||||||
|                 } 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; |  | ||||||
|                 } |                 } | ||||||
|  | 
 | ||||||
|  |                 $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 @@ class Application extends BaseModel | |||||||
|                 $currentPath = ''; |                 $currentPath = ''; | ||||||
|                 foreach ($parts as $part) { |                 foreach ($parts as $part) { | ||||||
|                     $currentPath .= ($currentPath ? '/' : '').$part; |                     $currentPath .= ($currentPath ? '/' : '').$part; | ||||||
|                     $paths->push($currentPath); |                     if (str($currentPath)->isNotEmpty()) { | ||||||
|  |                         $paths->push($currentPath); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 return $paths; |                 return $paths; | ||||||
| @@ -1341,7 +1350,7 @@ class Application extends BaseModel | |||||||
|                 "mkdir -p /tmp/{$uuid}", |                 "mkdir -p /tmp/{$uuid}", | ||||||
|                 "cd /tmp/{$uuid}", |                 "cd /tmp/{$uuid}", | ||||||
|                 $cloneCommand, |                 $cloneCommand, | ||||||
|                 'git sparse-checkout init --cone', |                 'git sparse-checkout init', | ||||||
|                 "git sparse-checkout set {$fileList->implode(' ')}", |                 "git sparse-checkout set {$fileList->implode(' ')}", | ||||||
|                 'git read-tree -mu HEAD', |                 'git read-tree -mu HEAD', | ||||||
|                 "cat .$workdir$composeFile", |                 "cat .$workdir$composeFile", | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ abstract class BaseModel extends Model | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function name(): Attribute |     public function sanitizedName(): Attribute | ||||||
|     { |     { | ||||||
|         return new Attribute( |         return new Attribute( | ||||||
|             get: fn () => sanitize_string($this->getRawOriginal('name')), |             get: fn () => sanitize_string($this->getRawOriginal('name')), | ||||||
|   | |||||||
							
								
								
									
										59
									
								
								app/Models/DiscordNotificationSettings.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								app/Models/DiscordNotificationSettings.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Models; | ||||||
|  | 
 | ||||||
|  | use Illuminate\Database\Eloquent\Model; | ||||||
|  | use Illuminate\Notifications\Notifiable; | ||||||
|  | 
 | ||||||
|  | class DiscordNotificationSettings extends Model | ||||||
|  | { | ||||||
|  |     use Notifiable; | ||||||
|  | 
 | ||||||
|  |     public $timestamps = false; | ||||||
|  | 
 | ||||||
|  |     protected $fillable = [ | ||||||
|  |         'team_id', | ||||||
|  | 
 | ||||||
|  |         'discord_enabled', | ||||||
|  |         'discord_webhook_url', | ||||||
|  | 
 | ||||||
|  |         'deployment_success_discord_notifications', | ||||||
|  |         'deployment_failure_discord_notifications', | ||||||
|  |         'status_change_discord_notifications', | ||||||
|  |         'backup_success_discord_notifications', | ||||||
|  |         'backup_failure_discord_notifications', | ||||||
|  |         'scheduled_task_success_discord_notifications', | ||||||
|  |         'scheduled_task_failure_discord_notifications', | ||||||
|  |         'docker_cleanup_discord_notifications', | ||||||
|  |         'server_disk_usage_discord_notifications', | ||||||
|  |         'server_reachable_discord_notifications', | ||||||
|  |         'server_unreachable_discord_notifications', | ||||||
|  |     ]; | ||||||
|  | 
 | ||||||
|  |     protected $casts = [ | ||||||
|  |         'discord_enabled' => '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; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										79
									
								
								app/Models/EmailNotificationSettings.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								app/Models/EmailNotificationSettings.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Models; | ||||||
|  | 
 | ||||||
|  | use Illuminate\Database\Eloquent\Model; | ||||||
|  | 
 | ||||||
|  | class EmailNotificationSettings extends Model | ||||||
|  | { | ||||||
|  |     public $timestamps = false; | ||||||
|  | 
 | ||||||
|  |     protected $fillable = [ | ||||||
|  |         'team_id', | ||||||
|  | 
 | ||||||
|  |         'smtp_enabled', | ||||||
|  |         'smtp_from_address', | ||||||
|  |         'smtp_from_name', | ||||||
|  |         'smtp_recipients', | ||||||
|  |         'smtp_host', | ||||||
|  |         'smtp_port', | ||||||
|  |         'smtp_encryption', | ||||||
|  |         'smtp_username', | ||||||
|  |         'smtp_password', | ||||||
|  |         'smtp_timeout', | ||||||
|  | 
 | ||||||
|  |         'resend_enabled', | ||||||
|  |         'resend_api_key', | ||||||
|  | 
 | ||||||
|  |         'use_instance_email_settings', | ||||||
|  | 
 | ||||||
|  |         'deployment_success_email_notifications', | ||||||
|  |         'deployment_failure_email_notifications', | ||||||
|  |         'status_change_email_notifications', | ||||||
|  |         'backup_success_email_notifications', | ||||||
|  |         'backup_failure_email_notifications', | ||||||
|  |         'scheduled_task_success_email_notifications', | ||||||
|  |         'scheduled_task_failure_email_notifications', | ||||||
|  |         'server_disk_usage_email_notifications', | ||||||
|  |     ]; | ||||||
|  | 
 | ||||||
|  |     protected $casts = [ | ||||||
|  |         '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', | ||||||
|  | 
 | ||||||
|  |         '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; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -16,8 +16,19 @@ class InstanceSettings extends Model implements SendsEmail | |||||||
|     protected $guarded = []; |     protected $guarded = []; | ||||||
| 
 | 
 | ||||||
|     protected $casts = [ |     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_password' => 'encrypted', | ||||||
|  |         'smtp_timeout' => 'integer', | ||||||
|  | 
 | ||||||
|  |         'resend_enabled' => 'boolean', | ||||||
|  |         'resend_api_key' => 'encrypted', | ||||||
|  | 
 | ||||||
|         'allowed_ip_ranges' => 'array', |         'allowed_ip_ranges' => 'array', | ||||||
|         'is_auto_update_enabled' => 'boolean', |         'is_auto_update_enabled' => 'boolean', | ||||||
|         'auto_update_frequency' => 'string', |         'auto_update_frequency' => 'string', | ||||||
| @@ -81,7 +92,7 @@ class InstanceSettings extends Model implements SendsEmail | |||||||
|         return InstanceSettings::findOrFail(0); |         return InstanceSettings::findOrFail(0); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function getRecepients($notification) |     public function getRecipients($notification) | ||||||
|     { |     { | ||||||
|         $recipients = data_get($notification, 'emails', null); |         $recipients = data_get($notification, 'emails', null); | ||||||
|         if (is_null($recipients) || $recipients === '') { |         if (is_null($recipients) || $recipients === '') { | ||||||
|   | |||||||
| @@ -11,6 +11,8 @@ class OauthSetting extends Model | |||||||
| { | { | ||||||
|     use HasFactory; |     use HasFactory; | ||||||
| 
 | 
 | ||||||
|  |     protected $fillable = ['provider', 'client_id', 'client_secret', 'redirect_uri', 'tenant', 'base_url', 'enabled']; | ||||||
|  | 
 | ||||||
|     protected function clientSecret(): Attribute |     protected function clientSecret(): Attribute | ||||||
|     { |     { | ||||||
|         return Attribute::make( |         return Attribute::make( | ||||||
| @@ -18,4 +20,16 @@ class OauthSetting extends Model | |||||||
|             set: fn (?string $value) => empty($value) ? null : Crypt::encryptString($value), |             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); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										61
									
								
								app/Models/PushoverNotificationSettings.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								app/Models/PushoverNotificationSettings.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Models; | ||||||
|  | 
 | ||||||
|  | use Illuminate\Database\Eloquent\Model; | ||||||
|  | use Illuminate\Notifications\Notifiable; | ||||||
|  | 
 | ||||||
|  | class PushoverNotificationSettings extends Model | ||||||
|  | { | ||||||
|  |     use Notifiable; | ||||||
|  | 
 | ||||||
|  |     public $timestamps = false; | ||||||
|  | 
 | ||||||
|  |     protected $fillable = [ | ||||||
|  |         'team_id', | ||||||
|  | 
 | ||||||
|  |         'pushover_enabled', | ||||||
|  |         'pushover_user_key', | ||||||
|  |         'pushover_api_token', | ||||||
|  | 
 | ||||||
|  |         'deployment_success_pushover_notifications', | ||||||
|  |         'deployment_failure_pushover_notifications', | ||||||
|  |         'status_change_pushover_notifications', | ||||||
|  |         'backup_success_pushover_notifications', | ||||||
|  |         'backup_failure_pushover_notifications', | ||||||
|  |         'scheduled_task_success_pushover_notifications', | ||||||
|  |         'scheduled_task_failure_pushover_notifications', | ||||||
|  |         'docker_cleanup_pushover_notifications', | ||||||
|  |         'server_disk_usage_pushover_notifications', | ||||||
|  |         'server_reachable_pushover_notifications', | ||||||
|  |         'server_unreachable_pushover_notifications', | ||||||
|  |     ]; | ||||||
|  | 
 | ||||||
|  |     protected $casts = [ | ||||||
|  |         'pushover_enabled' => '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; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -59,7 +59,7 @@ class S3Storage extends BaseModel | |||||||
|             $this->is_usable = true; |             $this->is_usable = true; | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             $this->is_usable = false; |             $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 = new MailMessage; | ||||||
|                 $mail->subject('Coolify: S3 Storage Connection Error'); |                 $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])]); |                 $mail->view('emails.s3-connection-error', ['name' => $this->name, 'reason' => $e->getMessage(), 'url' => route('storage.show', ['storage_uuid' => $this->uuid])]); | ||||||
|   | |||||||
| @@ -1042,7 +1042,7 @@ $schema://$host { | |||||||
|         $this->unreachable_notification_sent = false; |         $this->unreachable_notification_sent = false; | ||||||
|         $this->save(); |         $this->save(); | ||||||
|         $this->refresh(); |         $this->refresh(); | ||||||
|         // $this->team->notify(new Reachable($this));
 |         $this->team->notify(new Reachable($this)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function sendUnreachableNotification() |     public function sendUnreachableNotification() | ||||||
| @@ -1050,7 +1050,7 @@ $schema://$host { | |||||||
|         $this->unreachable_notification_sent = true; |         $this->unreachable_notification_sent = true; | ||||||
|         $this->save(); |         $this->save(); | ||||||
|         $this->refresh(); |         $this->refresh(); | ||||||
|         // $this->team->notify(new Unreachable($this));
 |         $this->team->notify(new Unreachable($this)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function validateConnection(bool $justCheckingNewKey = false) |     public function validateConnection(bool $justCheckingNewKey = false) | ||||||
|   | |||||||
| @@ -46,7 +46,7 @@ class Service extends BaseModel | |||||||
| 
 | 
 | ||||||
|     protected $guarded = []; |     protected $guarded = []; | ||||||
| 
 | 
 | ||||||
|     protected $appends = ['server_status']; |     protected $appends = ['server_status', 'status']; | ||||||
| 
 | 
 | ||||||
|     protected static function booted() |     protected static function booted() | ||||||
|     { |     { | ||||||
| @@ -105,12 +105,12 @@ class Service extends BaseModel | |||||||
| 
 | 
 | ||||||
|     public function isRunning() |     public function isRunning() | ||||||
|     { |     { | ||||||
|         return (bool) str($this->status())->contains('running'); |         return (bool) str($this->status)->contains('running'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function isExited() |     public function isExited() | ||||||
|     { |     { | ||||||
|         return (bool) str($this->status())->contains('exited'); |         return (bool) str($this->status)->contains('exited'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function type() |     public function type() | ||||||
| @@ -213,7 +213,7 @@ class Service extends BaseModel | |||||||
|         instant_remote_process(["docker network rm {$uuid}"], $server, false); |         instant_remote_process(["docker network rm {$uuid}"], $server, false); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function status() |     public function getStatusAttribute() | ||||||
|     { |     { | ||||||
|         $applications = $this->applications; |         $applications = $this->applications; | ||||||
|         $databases = $this->databases; |         $databases = $this->databases; | ||||||
| @@ -1140,7 +1140,7 @@ class Service extends BaseModel | |||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function failedTaskLink($task_uuid) |     public function taskLink($task_uuid) | ||||||
|     { |     { | ||||||
|         if (data_get($this, 'environment.project.uuid')) { |         if (data_get($this, 'environment.project.uuid')) { | ||||||
|             $route = route('project.service.scheduled-tasks', [ |             $route = route('project.service.scheduled-tasks', [ | ||||||
|   | |||||||
							
								
								
									
										59
									
								
								app/Models/SlackNotificationSettings.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								app/Models/SlackNotificationSettings.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Models; | ||||||
|  | 
 | ||||||
|  | use Illuminate\Database\Eloquent\Model; | ||||||
|  | use Illuminate\Notifications\Notifiable; | ||||||
|  | 
 | ||||||
|  | class SlackNotificationSettings extends Model | ||||||
|  | { | ||||||
|  |     use Notifiable; | ||||||
|  | 
 | ||||||
|  |     public $timestamps = false; | ||||||
|  | 
 | ||||||
|  |     protected $fillable = [ | ||||||
|  |         'team_id', | ||||||
|  | 
 | ||||||
|  |         'slack_enabled', | ||||||
|  |         'slack_webhook_url', | ||||||
|  | 
 | ||||||
|  |         'deployment_success_slack_notifications', | ||||||
|  |         'deployment_failure_slack_notifications', | ||||||
|  |         'status_change_slack_notifications', | ||||||
|  |         'backup_success_slack_notifications', | ||||||
|  |         'backup_failure_slack_notifications', | ||||||
|  |         'scheduled_task_success_slack_notifications', | ||||||
|  |         'scheduled_task_failure_slack_notifications', | ||||||
|  |         'docker_cleanup_slack_notifications', | ||||||
|  |         'server_disk_usage_slack_notifications', | ||||||
|  |         'server_reachable_slack_notifications', | ||||||
|  |         'server_unreachable_slack_notifications', | ||||||
|  |     ]; | ||||||
|  | 
 | ||||||
|  |     protected $casts = [ | ||||||
|  |         'slack_enabled' => '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; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -4,7 +4,9 @@ namespace App\Models; | |||||||
| 
 | 
 | ||||||
| use App\Notifications\Channels\SendsDiscord; | use App\Notifications\Channels\SendsDiscord; | ||||||
| use App\Notifications\Channels\SendsEmail; | use App\Notifications\Channels\SendsEmail; | ||||||
|  | use App\Notifications\Channels\SendsPushover; | ||||||
| use App\Notifications\Channels\SendsSlack; | use App\Notifications\Channels\SendsSlack; | ||||||
|  | use App\Traits\HasNotificationSettings; | ||||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | use Illuminate\Database\Eloquent\Casts\Attribute; | ||||||
| use Illuminate\Database\Eloquent\Model; | use Illuminate\Database\Eloquent\Model; | ||||||
| use Illuminate\Notifications\Notifiable; | use Illuminate\Notifications\Notifiable; | ||||||
| @@ -20,49 +22,8 @@ use OpenApi\Attributes as OA; | |||||||
|         'personal_team' => ['type' => 'boolean', 'description' => 'Whether the team is personal or not.'], |         '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.'], |         '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.'], |         '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.'], |         '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.'], |         '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( |         'members' => new OA\Property( | ||||||
|             property: 'members', |             property: 'members', | ||||||
|             type: 'array', |             type: 'array', | ||||||
| @@ -71,20 +32,27 @@ use OpenApi\Attributes as OA; | |||||||
|         ), |         ), | ||||||
|     ] |     ] | ||||||
| )] | )] | ||||||
| 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 $guarded = []; | ||||||
| 
 | 
 | ||||||
|     protected $casts = [ |     protected $casts = [ | ||||||
|         'personal_team' => 'boolean', |         'personal_team' => 'boolean', | ||||||
|         'smtp_password' => 'encrypted', |  | ||||||
|         'resend_api_key' => 'encrypted', |  | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     protected static function booted() |     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) { |         static::saving(function ($team) { | ||||||
|             if (auth()->user()?->isMember()) { |             if (auth()->user()?->isMember()) { | ||||||
|                 throw new \Exception('You are not allowed to update this team.'); |                 throw new \Exception('You are not allowed to update this team.'); | ||||||
| @@ -115,34 +83,6 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsSlack | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     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() |     public static function serverLimitReached() | ||||||
|     { |     { | ||||||
|         $serverLimit = Team::serverLimit(); |         $serverLimit = Team::serverLimit(); | ||||||
| @@ -196,10 +136,75 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsSlack | |||||||
| 
 | 
 | ||||||
|                 return $serverLimit ?? 2; |                 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() |     public function environment_variables() | ||||||
|     { |     { | ||||||
|         return $this->hasMany(SharedEnvironmentVariable::class)->whereNull('project_id')->whereNull('environment_id'); |         return $this->hasMany(SharedEnvironmentVariable::class)->whereNull('project_id')->whereNull('environment_id'); | ||||||
| @@ -263,32 +268,28 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsSlack | |||||||
|         return $this->hasMany(S3Storage::class)->where('is_usable', true); |         return $this->hasMany(S3Storage::class)->where('is_usable', true); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function subscriptionEnded() |     public function emailNotificationSettings() | ||||||
|     { |     { | ||||||
|         $this->subscription->update([ |         return $this->hasOne(EmailNotificationSettings::class); | ||||||
|             '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 isAnyNotificationEnabled() |     public function discordNotificationSettings() | ||||||
|     { |     { | ||||||
|         if (isCloud()) { |         return $this->hasOne(DiscordNotificationSettings::class); | ||||||
|             return true; |     } | ||||||
|         } |  | ||||||
|         if ($this->smtp_enabled || $this->resend_enabled || $this->discord_enabled || $this->telegram_enabled || $this->use_instance_email_settings) { |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         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); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										85
									
								
								app/Models/TelegramNotificationSettings.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								app/Models/TelegramNotificationSettings.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Models; | ||||||
|  | 
 | ||||||
|  | use Illuminate\Database\Eloquent\Model; | ||||||
|  | use Illuminate\Notifications\Notifiable; | ||||||
|  | 
 | ||||||
|  | class TelegramNotificationSettings extends Model | ||||||
|  | { | ||||||
|  |     use Notifiable; | ||||||
|  | 
 | ||||||
|  |     public $timestamps = false; | ||||||
|  | 
 | ||||||
|  |     protected $fillable = [ | ||||||
|  |         'team_id', | ||||||
|  | 
 | ||||||
|  |         'telegram_enabled', | ||||||
|  |         'telegram_token', | ||||||
|  |         'telegram_chat_id', | ||||||
|  | 
 | ||||||
|  |         'deployment_success_telegram_notifications', | ||||||
|  |         'deployment_failure_telegram_notifications', | ||||||
|  |         'status_change_telegram_notifications', | ||||||
|  |         'backup_success_telegram_notifications', | ||||||
|  |         'backup_failure_telegram_notifications', | ||||||
|  |         'scheduled_task_success_telegram_notifications', | ||||||
|  |         'scheduled_task_failure_telegram_notifications', | ||||||
|  |         'docker_cleanup_telegram_notifications', | ||||||
|  |         'server_disk_usage_telegram_notifications', | ||||||
|  |         'server_reachable_telegram_notifications', | ||||||
|  |         'server_unreachable_telegram_notifications', | ||||||
|  | 
 | ||||||
|  |         'telegram_notifications_deployment_success_thread_id', | ||||||
|  |         'telegram_notifications_deployment_failure_thread_id', | ||||||
|  |         'telegram_notifications_status_change_thread_id', | ||||||
|  |         'telegram_notifications_backup_success_thread_id', | ||||||
|  |         'telegram_notifications_backup_failure_thread_id', | ||||||
|  |         'telegram_notifications_scheduled_task_success_thread_id', | ||||||
|  |         'telegram_notifications_scheduled_task_failure_thread_id', | ||||||
|  |         'telegram_notifications_docker_cleanup_thread_id', | ||||||
|  |         'telegram_notifications_server_disk_usage_thread_id', | ||||||
|  |         'telegram_notifications_server_reachable_thread_id', | ||||||
|  |         'telegram_notifications_server_unreachable_thread_id', | ||||||
|  |     ]; | ||||||
|  | 
 | ||||||
|  |     protected $casts = [ | ||||||
|  |         'telegram_enabled' => '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; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -114,7 +114,7 @@ class User extends Authenticatable implements SendsEmail | |||||||
|         return $this->belongsToMany(Team::class)->withPivot('role'); |         return $this->belongsToMany(Team::class)->withPivot('role'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function getRecepients($notification) |     public function getRecipients($notification) | ||||||
|     { |     { | ||||||
|         return $this->email; |         return $this->email; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,12 +0,0 @@ | |||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Models; |  | ||||||
| 
 |  | ||||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; |  | ||||||
| 
 |  | ||||||
| class Waitlist extends BaseModel |  | ||||||
| { |  | ||||||
|     use HasFactory; |  | ||||||
| 
 |  | ||||||
|     protected $guarded = []; |  | ||||||
| } |  | ||||||
| @@ -6,6 +6,7 @@ use App\Models\Application; | |||||||
| use App\Models\ApplicationPreview; | use App\Models\ApplicationPreview; | ||||||
| use App\Notifications\CustomEmailNotification; | use App\Notifications\CustomEmailNotification; | ||||||
| use App\Notifications\Dto\DiscordMessage; | use App\Notifications\Dto\DiscordMessage; | ||||||
|  | use App\Notifications\Dto\PushoverMessage; | ||||||
| use App\Notifications\Dto\SlackMessage; | use App\Notifications\Dto\SlackMessage; | ||||||
| use Illuminate\Notifications\Messages\MailMessage; | use Illuminate\Notifications\Messages\MailMessage; | ||||||
| 
 | 
 | ||||||
| @@ -45,7 +46,7 @@ class DeploymentFailed extends CustomEmailNotification | |||||||
| 
 | 
 | ||||||
|     public function via(object $notifiable): array |     public function via(object $notifiable): array | ||||||
|     { |     { | ||||||
|         return setNotificationChannels($notifiable, 'deployments'); |         return $notifiable->getEnabledChannels('deployment_failure'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function toMail(): MailMessage |     public function toMail(): MailMessage | ||||||
| @@ -130,6 +131,31 @@ class DeploymentFailed extends CustomEmailNotification | |||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     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 |     public function toSlack(): SlackMessage | ||||||
|     { |     { | ||||||
|         if ($this->preview) { |         if ($this->preview) { | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ use App\Models\Application; | |||||||
| use App\Models\ApplicationPreview; | use App\Models\ApplicationPreview; | ||||||
| use App\Notifications\CustomEmailNotification; | use App\Notifications\CustomEmailNotification; | ||||||
| use App\Notifications\Dto\DiscordMessage; | use App\Notifications\Dto\DiscordMessage; | ||||||
|  | use App\Notifications\Dto\PushoverMessage; | ||||||
| use App\Notifications\Dto\SlackMessage; | use App\Notifications\Dto\SlackMessage; | ||||||
| use Illuminate\Notifications\Messages\MailMessage; | use Illuminate\Notifications\Messages\MailMessage; | ||||||
| 
 | 
 | ||||||
| @@ -45,13 +46,7 @@ class DeploymentSuccess extends CustomEmailNotification | |||||||
| 
 | 
 | ||||||
|     public function via(object $notifiable): array |     public function via(object $notifiable): array | ||||||
|     { |     { | ||||||
|         $channels = setNotificationChannels($notifiable, 'deployments'); |         return $notifiable->getEnabledChannels('deployment_success'); | ||||||
|         if (isCloud()) { |  | ||||||
|             // TODO: Make batch notifications work with email
 |  | ||||||
|             $channels = array_diff($channels, [\App\Notifications\Channels\EmailChannel::class]); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return $channels; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function toMail(): MailMessage |     public function toMail(): MailMessage | ||||||
| @@ -145,6 +140,42 @@ class DeploymentSuccess extends CustomEmailNotification | |||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     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 |     public function toSlack(): SlackMessage | ||||||
|     { |     { | ||||||
|         if ($this->preview) { |         if ($this->preview) { | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ namespace App\Notifications\Application; | |||||||
| use App\Models\Application; | use App\Models\Application; | ||||||
| use App\Notifications\CustomEmailNotification; | use App\Notifications\CustomEmailNotification; | ||||||
| use App\Notifications\Dto\DiscordMessage; | use App\Notifications\Dto\DiscordMessage; | ||||||
|  | use App\Notifications\Dto\PushoverMessage; | ||||||
| use App\Notifications\Dto\SlackMessage; | use App\Notifications\Dto\SlackMessage; | ||||||
| use Illuminate\Notifications\Messages\MailMessage; | use Illuminate\Notifications\Messages\MailMessage; | ||||||
| 
 | 
 | ||||||
| @@ -35,7 +36,7 @@ class StatusChanged extends CustomEmailNotification | |||||||
| 
 | 
 | ||||||
|     public function via(object $notifiable): array |     public function via(object $notifiable): array | ||||||
|     { |     { | ||||||
|         return setNotificationChannels($notifiable, 'status_changes'); |         return $notifiable->getEnabledChannels('status_change'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function toMail(): MailMessage |     public function toMail(): MailMessage | ||||||
| @@ -77,6 +78,23 @@ class StatusChanged extends CustomEmailNotification | |||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     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 |     public function toSlack(): SlackMessage | ||||||
|     { |     { | ||||||
|         $title = 'Application stopped'; |         $title = 'Application stopped'; | ||||||
|   | |||||||
| @@ -13,10 +13,13 @@ class DiscordChannel | |||||||
|     public function send(SendsDiscord $notifiable, Notification $notification): void |     public function send(SendsDiscord $notifiable, Notification $notification): void | ||||||
|     { |     { | ||||||
|         $message = $notification->toDiscord(); |         $message = $notification->toDiscord(); | ||||||
|         $webhookUrl = $notifiable->routeNotificationForDiscord(); | 
 | ||||||
|         if (! $webhookUrl) { |         $discordSettings = $notifiable->discordNotificationSettings; | ||||||
|  | 
 | ||||||
|  |         if (! $discordSettings || ! $discordSettings->isEnabled() || ! $discordSettings->discord_webhook_url) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         SendMessageToDiscordJob::dispatch($message, $webhookUrl); | 
 | ||||||
|  |         SendMessageToDiscordJob::dispatch($message, $discordSettings->discord_webhook_url); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ class EmailChannel | |||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
|             $this->bootConfigs($notifiable); |             $this->bootConfigs($notifiable); | ||||||
|             $recipients = $notifiable->getRecepients($notification); |             $recipients = $notifiable->getRecipients($notification); | ||||||
|             if (count($recipients) === 0) { |             if (count($recipients) === 0) { | ||||||
|                 throw new Exception('No email recipients found'); |                 throw new Exception('No email recipients found'); | ||||||
|             } |             } | ||||||
| @@ -46,7 +46,9 @@ class EmailChannel | |||||||
| 
 | 
 | ||||||
|     private function bootConfigs($notifiable): 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(); |             $type = set_transanctional_email_settings(); | ||||||
|             if (! $type) { |             if (! $type) { | ||||||
|                 throw new Exception('No email settings found.'); |                 throw new Exception('No email settings found.'); | ||||||
| @@ -54,24 +56,27 @@ class EmailChannel | |||||||
| 
 | 
 | ||||||
|             return; |             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')); |         config()->set('mail.from.address', $emailSettings->smtp_from_address ?? 'test@example.com'); | ||||||
|         if (data_get($notifiable, 'resend_enabled')) { |         config()->set('mail.from.name', $emailSettings->smtp_from_name ?? 'Test'); | ||||||
|  | 
 | ||||||
|  |         if ($emailSettings->resend_enabled) { | ||||||
|             config()->set('mail.default', 'resend'); |             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.default', 'smtp'); | ||||||
|             config()->set('mail.mailers.smtp', [ |             config()->set('mail.mailers.smtp', [ | ||||||
|                 'transport' => 'smtp', |                 'transport' => 'smtp', | ||||||
|                 'host' => data_get($notifiable, 'smtp_host'), |                 'host' => $emailSettings->smtp_host, | ||||||
|                 'port' => data_get($notifiable, 'smtp_port'), |                 'port' => $emailSettings->smtp_port, | ||||||
|                 'encryption' => data_get($notifiable, 'smtp_encryption') === 'none' ? null : data_get($notifiable, 'smtp_encryption'), |                 'encryption' => $emailSettings->smtp_encryption === 'none' ? null : $emailSettings->smtp_encryption, | ||||||
|                 'username' => data_get($notifiable, 'smtp_username'), |                 'username' => $emailSettings->smtp_username, | ||||||
|                 'password' => data_get($notifiable, 'smtp_password'), |                 'password' => $emailSettings->smtp_password, | ||||||
|                 'timeout' => data_get($notifiable, 'smtp_timeout'), |                 'timeout' => $emailSettings->smtp_timeout, | ||||||
|                 'local_domain' => null, |                 'local_domain' => null, | ||||||
|                 'auto_tls' => data_get($notifiable, 'smtp_encryption') === 'none' ? '0' : '', |                 'auto_tls' => $emailSettings->smtp_encryption === 'none' ? '0' : '', | ||||||
|             ]); |             ]); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
							
								
								
									
										21
									
								
								app/Notifications/Channels/PushoverChannel.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								app/Notifications/Channels/PushoverChannel.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Notifications\Channels; | ||||||
|  | 
 | ||||||
|  | use App\Jobs\SendMessageToPushoverJob; | ||||||
|  | use Illuminate\Notifications\Notification; | ||||||
|  | 
 | ||||||
|  | class PushoverChannel | ||||||
|  | { | ||||||
|  |     public function send(SendsPushover $notifiable, Notification $notification): void | ||||||
|  |     { | ||||||
|  |         $message = $notification->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); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -4,5 +4,5 @@ namespace App\Notifications\Channels; | |||||||
| 
 | 
 | ||||||
| interface SendsEmail | interface SendsEmail | ||||||
| { | { | ||||||
|     public function getRecepients($notification); |     public function getRecipients($notification); | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								app/Notifications/Channels/SendsPushover.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								app/Notifications/Channels/SendsPushover.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Notifications\Channels; | ||||||
|  | 
 | ||||||
|  | interface SendsPushover | ||||||
|  | { | ||||||
|  |     public function routeNotificationForPushover(); | ||||||
|  | } | ||||||
| @@ -13,10 +13,12 @@ class SlackChannel | |||||||
|     public function send(SendsSlack $notifiable, Notification $notification): void |     public function send(SendsSlack $notifiable, Notification $notification): void | ||||||
|     { |     { | ||||||
|         $message = $notification->toSlack(); |         $message = $notification->toSlack(); | ||||||
|         $webhookUrl = $notifiable->routeNotificationForSlack(); |         $slackSettings = $notifiable->slackNotificationSettings; | ||||||
|         if (! $webhookUrl) { | 
 | ||||||
|  |         if (! $slackSettings || ! $slackSettings->isEnabled() || ! $slackSettings->slack_webhook_url) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         SendMessageToSlackJob::dispatch($message, $webhookUrl); | 
 | ||||||
|  |         SendMessageToSlackJob::dispatch($message, $slackSettings->slack_webhook_url); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,38 +9,39 @@ class TelegramChannel | |||||||
|     public function send($notifiable, $notification): void |     public function send($notifiable, $notification): void | ||||||
|     { |     { | ||||||
|         $data = $notification->toTelegram($notifiable); |         $data = $notification->toTelegram($notifiable); | ||||||
|         $telegramData = $notifiable->routeNotificationForTelegram(); |         $settings = $notifiable->telegramNotificationSettings; | ||||||
|  | 
 | ||||||
|         $message = data_get($data, 'message'); |         $message = data_get($data, 'message'); | ||||||
|         $buttons = data_get($data, 'buttons', []); |         $buttons = data_get($data, 'buttons', []); | ||||||
|         $telegramToken = data_get($telegramData, 'token'); |         $telegramToken = $settings->telegram_token; | ||||||
|         $chatId = data_get($telegramData, 'chat_id'); |         $chatId = $settings->telegram_chat_id; | ||||||
|         $topicId = null; | 
 | ||||||
|         $topicsInstance = get_class($notification); |         $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) { |         if (! $telegramToken || ! $chatId || ! $message) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         SendMessageToTelegramJob::dispatch($message, $buttons, $telegramToken, $chatId, $topicId); | 
 | ||||||
|  |         SendMessageToTelegramJob::dispatch($message, $buttons, $telegramToken, $chatId, $threadId); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,7 +7,6 @@ use Exception; | |||||||
| use Illuminate\Mail\Message; | use Illuminate\Mail\Message; | ||||||
| use Illuminate\Notifications\Notification; | use Illuminate\Notifications\Notification; | ||||||
| use Illuminate\Support\Facades\Mail; | use Illuminate\Support\Facades\Mail; | ||||||
| use Log; |  | ||||||
| 
 | 
 | ||||||
| class TransactionalEmailChannel | class TransactionalEmailChannel | ||||||
| { | { | ||||||
| @@ -15,8 +14,6 @@ class TransactionalEmailChannel | |||||||
|     { |     { | ||||||
|         $settings = instanceSettings(); |         $settings = instanceSettings(); | ||||||
|         if (! data_get($settings, 'smtp_enabled') && ! data_get($settings, 'resend_enabled')) { |         if (! data_get($settings, 'smtp_enabled') && ! data_get($settings, 'resend_enabled')) { | ||||||
|             Log::info('SMTP/Resend not enabled'); |  | ||||||
| 
 |  | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         $email = $notifiable->email; |         $email = $notifiable->email; | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ namespace App\Notifications\Container; | |||||||
| use App\Models\Server; | use App\Models\Server; | ||||||
| use App\Notifications\CustomEmailNotification; | use App\Notifications\CustomEmailNotification; | ||||||
| use App\Notifications\Dto\DiscordMessage; | use App\Notifications\Dto\DiscordMessage; | ||||||
|  | use App\Notifications\Dto\PushoverMessage; | ||||||
| use App\Notifications\Dto\SlackMessage; | use App\Notifications\Dto\SlackMessage; | ||||||
| use Illuminate\Notifications\Messages\MailMessage; | use Illuminate\Notifications\Messages\MailMessage; | ||||||
| 
 | 
 | ||||||
| @@ -17,7 +18,7 @@ class ContainerRestarted extends CustomEmailNotification | |||||||
| 
 | 
 | ||||||
|     public function via(object $notifiable): array |     public function via(object $notifiable): array | ||||||
|     { |     { | ||||||
|         return setNotificationChannels($notifiable, 'status_changes'); |         return $notifiable->getEnabledChannels('status_change'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function toMail(): MailMessage |     public function toMail(): MailMessage | ||||||
| @@ -68,6 +69,24 @@ class ContainerRestarted extends CustomEmailNotification | |||||||
|         return $payload; |         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 |     public function toSlack(): SlackMessage | ||||||
|     { |     { | ||||||
|         $title = 'Resource restarted'; |         $title = 'Resource restarted'; | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ namespace App\Notifications\Container; | |||||||
| use App\Models\Server; | use App\Models\Server; | ||||||
| use App\Notifications\CustomEmailNotification; | use App\Notifications\CustomEmailNotification; | ||||||
| use App\Notifications\Dto\DiscordMessage; | use App\Notifications\Dto\DiscordMessage; | ||||||
|  | use App\Notifications\Dto\PushoverMessage; | ||||||
| use App\Notifications\Dto\SlackMessage; | use App\Notifications\Dto\SlackMessage; | ||||||
| use Illuminate\Notifications\Messages\MailMessage; | use Illuminate\Notifications\Messages\MailMessage; | ||||||
| 
 | 
 | ||||||
| @@ -17,7 +18,7 @@ class ContainerStopped extends CustomEmailNotification | |||||||
| 
 | 
 | ||||||
|     public function via(object $notifiable): array |     public function via(object $notifiable): array | ||||||
|     { |     { | ||||||
|         return setNotificationChannels($notifiable, 'status_changes'); |         return $notifiable->getEnabledChannels('status_change'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function toMail(): MailMessage |     public function toMail(): MailMessage | ||||||
| @@ -68,6 +69,25 @@ class ContainerStopped extends CustomEmailNotification | |||||||
|         return $payload; |         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 |     public function toSlack(): SlackMessage | ||||||
|     { |     { | ||||||
|         $title = 'Resource stopped'; |         $title = 'Resource stopped'; | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ namespace App\Notifications\Database; | |||||||
| use App\Models\ScheduledDatabaseBackup; | use App\Models\ScheduledDatabaseBackup; | ||||||
| use App\Notifications\CustomEmailNotification; | use App\Notifications\CustomEmailNotification; | ||||||
| use App\Notifications\Dto\DiscordMessage; | use App\Notifications\Dto\DiscordMessage; | ||||||
|  | use App\Notifications\Dto\PushoverMessage; | ||||||
| use App\Notifications\Dto\SlackMessage; | use App\Notifications\Dto\SlackMessage; | ||||||
| use Illuminate\Notifications\Messages\MailMessage; | use Illuminate\Notifications\Messages\MailMessage; | ||||||
| 
 | 
 | ||||||
| @@ -23,13 +24,13 @@ class BackupFailed extends CustomEmailNotification | |||||||
| 
 | 
 | ||||||
|     public function via(object $notifiable): array |     public function via(object $notifiable): array | ||||||
|     { |     { | ||||||
|         return setNotificationChannels($notifiable, 'database_backups'); |         return $notifiable->getEnabledChannels('backup_failure'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function toMail(): MailMessage |     public function toMail(): MailMessage | ||||||
|     { |     { | ||||||
|         $mail = new 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', [ |         $mail->view('emails.backup-failed', [ | ||||||
|             'name' => $this->name, |             'name' => $this->name, | ||||||
|             'database_name' => $this->database_name, |             'database_name' => $this->database_name, | ||||||
| @@ -64,6 +65,15 @@ class BackupFailed extends CustomEmailNotification | |||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     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<br/><br/><b>Frequency:</b> {$this->frequency} .<br/><b>Reason:</b> {$this->output}", | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function toSlack(): SlackMessage |     public function toSlack(): SlackMessage | ||||||
|     { |     { | ||||||
|         $title = 'Database backup failed'; |         $title = 'Database backup failed'; | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ namespace App\Notifications\Database; | |||||||
| use App\Models\ScheduledDatabaseBackup; | use App\Models\ScheduledDatabaseBackup; | ||||||
| use App\Notifications\CustomEmailNotification; | use App\Notifications\CustomEmailNotification; | ||||||
| use App\Notifications\Dto\DiscordMessage; | use App\Notifications\Dto\DiscordMessage; | ||||||
|  | use App\Notifications\Dto\PushoverMessage; | ||||||
| use App\Notifications\Dto\SlackMessage; | use App\Notifications\Dto\SlackMessage; | ||||||
| use Illuminate\Notifications\Messages\MailMessage; | use Illuminate\Notifications\Messages\MailMessage; | ||||||
| 
 | 
 | ||||||
| @@ -24,7 +25,7 @@ class BackupSuccess extends CustomEmailNotification | |||||||
| 
 | 
 | ||||||
|     public function via(object $notifiable): array |     public function via(object $notifiable): array | ||||||
|     { |     { | ||||||
|         return setNotificationChannels($notifiable, 'database_backups'); |         return $notifiable->getEnabledChannels('backup_success'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function toMail(): MailMessage |     public function toMail(): MailMessage | ||||||
| @@ -62,6 +63,17 @@ class BackupSuccess extends CustomEmailNotification | |||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  |     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.<br/><br/><b>Frequency:</b> {$this->frequency}.", | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     public function toSlack(): SlackMessage |     public function toSlack(): SlackMessage | ||||||
|     { |     { | ||||||
|         $title = 'Database backup successful'; |         $title = 'Database backup successful'; | ||||||
|   | |||||||
							
								
								
									
										50
									
								
								app/Notifications/Dto/PushoverMessage.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								app/Notifications/Dto/PushoverMessage.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Notifications\Dto; | ||||||
|  | 
 | ||||||
|  | use Illuminate\Support\Facades\Log; | ||||||
|  | 
 | ||||||
|  | class PushoverMessage | ||||||
|  | { | ||||||
|  |     public function __construct( | ||||||
|  |         public string $title, | ||||||
|  |         public string $message, | ||||||
|  |         public array $buttons = [], | ||||||
|  |         public string $level = 'info', | ||||||
|  |     ) {} | ||||||
|  | 
 | ||||||
|  |     public function getLevelIcon(): string | ||||||
|  |     { | ||||||
|  |         return match ($this->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'] .= " <a href='" . $buttonUrl . "'>" . $text . '</a>'; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         Log::info('Pushover message', $payload); | ||||||
|  | 
 | ||||||
|  |         return $payload; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -2,10 +2,8 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Notifications\Internal; | 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\DiscordMessage; | ||||||
|  | use App\Notifications\Dto\PushoverMessage; | ||||||
| use App\Notifications\Dto\SlackMessage; | use App\Notifications\Dto\SlackMessage; | ||||||
| use Illuminate\Bus\Queueable; | use Illuminate\Bus\Queueable; | ||||||
| use Illuminate\Contracts\Queue\ShouldQueue; | use Illuminate\Contracts\Queue\ShouldQueue; | ||||||
| @@ -24,22 +22,7 @@ class GeneralNotification extends Notification implements ShouldQueue | |||||||
| 
 | 
 | ||||||
|     public function via(object $notifiable): array |     public function via(object $notifiable): array | ||||||
|     { |     { | ||||||
|         $channels = []; |         return $notifiable->getEnabledChannels('general'); | ||||||
|         $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; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function toDiscord(): DiscordMessage |     public function toDiscord(): DiscordMessage | ||||||
| @@ -58,6 +41,15 @@ class GeneralNotification extends Notification implements ShouldQueue | |||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function toPushover(): PushoverMessage | ||||||
|  |     { | ||||||
|  |         return new PushoverMessage( | ||||||
|  |             title: 'General Notification', | ||||||
|  |             level: 'info', | ||||||
|  |             message: $this->message, | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function toSlack(): SlackMessage |     public function toSlack(): SlackMessage | ||||||
|     { |     { | ||||||
|         return new SlackMessage( |         return new SlackMessage( | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ namespace App\Notifications\ScheduledTask; | |||||||
| use App\Models\ScheduledTask; | use App\Models\ScheduledTask; | ||||||
| use App\Notifications\CustomEmailNotification; | use App\Notifications\CustomEmailNotification; | ||||||
| use App\Notifications\Dto\DiscordMessage; | use App\Notifications\Dto\DiscordMessage; | ||||||
|  | use App\Notifications\Dto\PushoverMessage; | ||||||
| use App\Notifications\Dto\SlackMessage; | use App\Notifications\Dto\SlackMessage; | ||||||
| use Illuminate\Notifications\Messages\MailMessage; | use Illuminate\Notifications\Messages\MailMessage; | ||||||
| 
 | 
 | ||||||
| @@ -16,15 +17,15 @@ class TaskFailed extends CustomEmailNotification | |||||||
|     { |     { | ||||||
|         $this->onQueue('high'); |         $this->onQueue('high'); | ||||||
|         if ($task->application) { |         if ($task->application) { | ||||||
|             $this->url = $task->application->failedTaskLink($task->uuid); |             $this->url = $task->application->taskLink($task->uuid); | ||||||
|         } elseif ($task->service) { |         } elseif ($task->service) { | ||||||
|             $this->url = $task->service->failedTaskLink($task->uuid); |             $this->url = $task->service->taskLink($task->uuid); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function via(object $notifiable): array |     public function via(object $notifiable): array | ||||||
|     { |     { | ||||||
|         return setNotificationChannels($notifiable, 'scheduled_tasks'); |         return $notifiable->getEnabledChannels('scheduled_task_failure'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function toMail(): MailMessage |     public function toMail(): MailMessage | ||||||
| @@ -70,6 +71,30 @@ class TaskFailed extends CustomEmailNotification | |||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function toPushover(): PushoverMessage | ||||||
|  |     { | ||||||
|  |         $message = "Scheduled task ({$this->task->name}) failed<br/>"; | ||||||
|  | 
 | ||||||
|  |         if ($this->output) { | ||||||
|  |             $message .= "<br/><b>Error Output:</b>{$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 |     public function toSlack(): SlackMessage | ||||||
|     { |     { | ||||||
|         $title = 'Scheduled task failed'; |         $title = 'Scheduled task failed'; | ||||||
|   | |||||||
							
								
								
									
										108
									
								
								app/Notifications/ScheduledTask/TaskSuccess.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								app/Notifications/ScheduledTask/TaskSuccess.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,108 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Notifications\ScheduledTask; | ||||||
|  | 
 | ||||||
|  | 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; | ||||||
|  | 
 | ||||||
|  | class TaskSuccess extends CustomEmailNotification | ||||||
|  | { | ||||||
|  |     public ?string $url = null; | ||||||
|  | 
 | ||||||
|  |     public function __construct(public ScheduledTask $task, public string $output) | ||||||
|  |     { | ||||||
|  |         $this->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() | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,78 +0,0 @@ | |||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Notifications\Server; |  | ||||||
| 
 |  | ||||||
| use App\Models\Server; |  | ||||||
| use App\Notifications\Channels\DiscordChannel; |  | ||||||
| use App\Notifications\Channels\TelegramChannel; |  | ||||||
| use App\Notifications\CustomEmailNotification; |  | ||||||
| use App\Notifications\Dto\DiscordMessage; |  | ||||||
| use App\Notifications\Dto\SlackMessage; |  | ||||||
| 
 |  | ||||||
| class DockerCleanup extends CustomEmailNotification |  | ||||||
| { |  | ||||||
|     public function __construct(public Server $server, public string $message) |  | ||||||
|     { |  | ||||||
|         $this->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() |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										69
									
								
								app/Notifications/Server/DockerCleanupFailed.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								app/Notifications/Server/DockerCleanupFailed.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Notifications\Server; | ||||||
|  | 
 | ||||||
|  | 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; | ||||||
|  | 
 | ||||||
|  | class DockerCleanupFailed extends CustomEmailNotification | ||||||
|  | { | ||||||
|  |     public function __construct(public Server $server, public string $message) | ||||||
|  |     { | ||||||
|  |         $this->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() | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										69
									
								
								app/Notifications/Server/DockerCleanupSuccess.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								app/Notifications/Server/DockerCleanupSuccess.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Notifications\Server; | ||||||
|  | 
 | ||||||
|  | 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; | ||||||
|  | 
 | ||||||
|  | class DockerCleanupSuccess extends CustomEmailNotification | ||||||
|  | { | ||||||
|  |     public function __construct(public Server $server, public string $message) | ||||||
|  |     { | ||||||
|  |         $this->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() | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -3,12 +3,9 @@ | |||||||
| namespace App\Notifications\Server; | namespace App\Notifications\Server; | ||||||
| 
 | 
 | ||||||
| use App\Models\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\CustomEmailNotification; | ||||||
| use App\Notifications\Dto\DiscordMessage; | use App\Notifications\Dto\DiscordMessage; | ||||||
|  | use App\Notifications\Dto\PushoverMessage; | ||||||
| use App\Notifications\Dto\SlackMessage; | use App\Notifications\Dto\SlackMessage; | ||||||
| use Illuminate\Notifications\Messages\MailMessage; | use Illuminate\Notifications\Messages\MailMessage; | ||||||
| 
 | 
 | ||||||
| @@ -21,25 +18,7 @@ class ForceDisabled extends CustomEmailNotification | |||||||
| 
 | 
 | ||||||
|     public function via(object $notifiable): array |     public function via(object $notifiable): array | ||||||
|     { |     { | ||||||
|         $channels = []; |         return $notifiable->getEnabledChannels('server_force_disabled'); | ||||||
|         $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 |     public function toMail(): MailMessage | ||||||
| @@ -73,6 +52,15 @@ class ForceDisabled extends CustomEmailNotification | |||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     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.<br/>Please update your subscription to enable the server again [here](https://app.coolify.io/subscriptions).", | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function toSlack(): SlackMessage |     public function toSlack(): SlackMessage | ||||||
|     { |     { | ||||||
|         $title = 'Server disabled'; |         $title = 'Server disabled'; | ||||||
|   | |||||||
| @@ -3,12 +3,9 @@ | |||||||
| namespace App\Notifications\Server; | namespace App\Notifications\Server; | ||||||
| 
 | 
 | ||||||
| use App\Models\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\CustomEmailNotification; | ||||||
| use App\Notifications\Dto\DiscordMessage; | use App\Notifications\Dto\DiscordMessage; | ||||||
|  | use App\Notifications\Dto\PushoverMessage; | ||||||
| use App\Notifications\Dto\SlackMessage; | use App\Notifications\Dto\SlackMessage; | ||||||
| use Illuminate\Notifications\Messages\MailMessage; | use Illuminate\Notifications\Messages\MailMessage; | ||||||
| 
 | 
 | ||||||
| @@ -21,25 +18,7 @@ class ForceEnabled extends CustomEmailNotification | |||||||
| 
 | 
 | ||||||
|     public function via(object $notifiable): array |     public function via(object $notifiable): array | ||||||
|     { |     { | ||||||
|         $channels = []; |         return $notifiable->getEnabledChannels('server_force_enabled'); | ||||||
|         $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 |     public function toMail(): MailMessage | ||||||
| @@ -69,6 +48,15 @@ class ForceEnabled extends CustomEmailNotification | |||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function toPushover(): PushoverMessage | ||||||
|  |     { | ||||||
|  |         return new PushoverMessage( | ||||||
|  |             title: 'Server enabled', | ||||||
|  |             level: 'success', | ||||||
|  |             message: "Server ({$this->server->name}) enabled again!", | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function toSlack(): SlackMessage |     public function toSlack(): SlackMessage | ||||||
|     { |     { | ||||||
|         return new SlackMessage( |         return new SlackMessage( | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ namespace App\Notifications\Server; | |||||||
| use App\Models\Server; | use App\Models\Server; | ||||||
| use App\Notifications\CustomEmailNotification; | use App\Notifications\CustomEmailNotification; | ||||||
| use App\Notifications\Dto\DiscordMessage; | use App\Notifications\Dto\DiscordMessage; | ||||||
|  | use App\Notifications\Dto\PushoverMessage; | ||||||
| use App\Notifications\Dto\SlackMessage; | use App\Notifications\Dto\SlackMessage; | ||||||
| use Illuminate\Notifications\Messages\MailMessage; | use Illuminate\Notifications\Messages\MailMessage; | ||||||
| 
 | 
 | ||||||
| @@ -17,7 +18,7 @@ class HighDiskUsage extends CustomEmailNotification | |||||||
| 
 | 
 | ||||||
|     public function via(object $notifiable): array |     public function via(object $notifiable): array | ||||||
|     { |     { | ||||||
|         return setNotificationChannels($notifiable, 'server_disk_usage'); |         return $notifiable->getEnabledChannels('server_disk_usage'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function toMail(): MailMessage |     public function toMail(): MailMessage | ||||||
| @@ -57,6 +58,19 @@ class HighDiskUsage extends CustomEmailNotification | |||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function toPushover(): PushoverMessage | ||||||
|  |     { | ||||||
|  |         return new PushoverMessage( | ||||||
|  |             title: 'High disk usage detected', | ||||||
|  |             level: 'warning', | ||||||
|  |             message: "Server '{$this->server->name}' high disk usage detected!<br/><br/><b>Disk usage:</b> {$this->disk_usage}%.<br/><b>Threshold:</b> {$this->server_disk_usage_notification_threshold}%.<br/>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 |     public function toSlack(): SlackMessage | ||||||
|     { |     { | ||||||
|         $description = "Server '{$this->server->name}' high disk usage detected!\n"; |         $description = "Server '{$this->server->name}' high disk usage detected!\n"; | ||||||
|   | |||||||
| @@ -3,12 +3,9 @@ | |||||||
| namespace App\Notifications\Server; | namespace App\Notifications\Server; | ||||||
| 
 | 
 | ||||||
| use App\Models\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\CustomEmailNotification; | ||||||
| use App\Notifications\Dto\DiscordMessage; | use App\Notifications\Dto\DiscordMessage; | ||||||
|  | use App\Notifications\Dto\PushoverMessage; | ||||||
| use App\Notifications\Dto\SlackMessage; | use App\Notifications\Dto\SlackMessage; | ||||||
| use Illuminate\Notifications\Messages\MailMessage; | use Illuminate\Notifications\Messages\MailMessage; | ||||||
| 
 | 
 | ||||||
| @@ -30,25 +27,7 @@ class Reachable extends CustomEmailNotification | |||||||
|             return []; |             return []; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $channels = []; |         return $notifiable->getEnabledChannels('server_reachable'); | ||||||
|         $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 |     public function toMail(): MailMessage | ||||||
| @@ -71,6 +50,15 @@ class Reachable extends CustomEmailNotification | |||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     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 |     public function toTelegram(): array | ||||||
|     { |     { | ||||||
|         return [ |         return [ | ||||||
|   | |||||||
| @@ -3,12 +3,9 @@ | |||||||
| namespace App\Notifications\Server; | namespace App\Notifications\Server; | ||||||
| 
 | 
 | ||||||
| use App\Models\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\CustomEmailNotification; | ||||||
| use App\Notifications\Dto\DiscordMessage; | use App\Notifications\Dto\DiscordMessage; | ||||||
|  | use App\Notifications\Dto\PushoverMessage; | ||||||
| use App\Notifications\Dto\SlackMessage; | use App\Notifications\Dto\SlackMessage; | ||||||
| use Illuminate\Notifications\Messages\MailMessage; | use Illuminate\Notifications\Messages\MailMessage; | ||||||
| 
 | 
 | ||||||
| @@ -30,26 +27,7 @@ class Unreachable extends CustomEmailNotification | |||||||
|             return []; |             return []; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $channels = []; |         return $notifiable->getEnabledChannels('server_unreachable'); | ||||||
|         $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 |     public function toMail(): ?MailMessage | ||||||
| @@ -83,6 +61,15 @@ class Unreachable extends CustomEmailNotification | |||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function toPushover(): PushoverMessage | ||||||
|  |     { | ||||||
|  |         return new PushoverMessage( | ||||||
|  |             title: 'Server unreachable', | ||||||
|  |             level: 'error', | ||||||
|  |             message: "Your server '{$this->server->name}' is unreachable.<br/>All automations & integrations are turned off!<br/><br/><b>IMPORTANT:</b> We automatically try to revive your server and turn on all automations & integrations.", | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function toSlack(): SlackMessage |     public function toSlack(): SlackMessage | ||||||
|     { |     { | ||||||
|         $description = "Your server '{$this->server->name}' is unreachable.\n"; |         $description = "Your server '{$this->server->name}' is unreachable.\n"; | ||||||
|   | |||||||
| @@ -2,7 +2,13 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Notifications; | namespace App\Notifications; | ||||||
| 
 | 
 | ||||||
|  | use App\Notifications\Channels\DiscordChannel; | ||||||
|  | use App\Notifications\Channels\EmailChannel; | ||||||
|  | use App\Notifications\Channels\SlackChannel; | ||||||
|  | use App\Notifications\Channels\TelegramChannel; | ||||||
|  | use App\Notifications\Channels\PushoverChannel; | ||||||
| use App\Notifications\Dto\DiscordMessage; | use App\Notifications\Dto\DiscordMessage; | ||||||
|  | use App\Notifications\Dto\PushoverMessage; | ||||||
| use App\Notifications\Dto\SlackMessage; | use App\Notifications\Dto\SlackMessage; | ||||||
| use Illuminate\Bus\Queueable; | use Illuminate\Bus\Queueable; | ||||||
| use Illuminate\Contracts\Queue\ShouldQueue; | use Illuminate\Contracts\Queue\ShouldQueue; | ||||||
| @@ -16,20 +22,33 @@ class Test extends Notification implements ShouldQueue | |||||||
| 
 | 
 | ||||||
|     public $tries = 5; |     public $tries = 5; | ||||||
| 
 | 
 | ||||||
|     public function __construct(public ?string $emails = null) |     public function __construct(public ?string $emails = null, public ?string $channel = null) | ||||||
|     { |     { | ||||||
|         $this->onQueue('high'); |         $this->onQueue('high'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function via(object $notifiable): array |     public function via(object $notifiable): array | ||||||
|     { |     { | ||||||
|         return setNotificationChannels($notifiable, 'test'); |         if ($this->channel) { | ||||||
|  |             $channels = match ($this->channel) { | ||||||
|  |                 'email' => [EmailChannel::class], | ||||||
|  |                 'discord' => [DiscordChannel::class], | ||||||
|  |                 'telegram' => [TelegramChannel::class], | ||||||
|  |                 'slack' => [SlackChannel::class], | ||||||
|  |                 'pushover' => [PushoverChannel::class], | ||||||
|  |                 default => [], | ||||||
|  |             }; | ||||||
|  |         } else { | ||||||
|  |             $channels = $notifiable->getEnabledChannels('test'); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return $channels; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function middleware(object $notifiable, string $channel) |     public function middleware(object $notifiable, string $channel) | ||||||
|     { |     { | ||||||
|         return match ($channel) { |         return match ($channel) { | ||||||
|             \App\Notifications\Channels\EmailChannel::class => [new RateLimited('email')], |             EmailChannel::class => [new RateLimited('email')], | ||||||
|             default => [], |             default => [], | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| @@ -69,6 +88,20 @@ class Test extends Notification implements ShouldQueue | |||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function toPushover(): PushoverMessage | ||||||
|  |     { | ||||||
|  |         return new PushoverMessage( | ||||||
|  |             title: 'Test Pushover Notification', | ||||||
|  |             message: 'This is a test Pushover notification from Coolify.', | ||||||
|  |             buttons: [ | ||||||
|  |                 [ | ||||||
|  |                     'text' => 'Go to your dashboard', | ||||||
|  |                     'url' => base_url(), | ||||||
|  |                 ], | ||||||
|  |             ], | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function toSlack(): SlackMessage |     public function toSlack(): SlackMessage | ||||||
|     { |     { | ||||||
|         return new SlackMessage( |         return new SlackMessage( | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
| namespace App\Providers; | namespace App\Providers; | ||||||
| 
 | 
 | ||||||
| use App\Models\PersonalAccessToken; | use App\Models\PersonalAccessToken; | ||||||
|  | use Illuminate\Support\Facades\Event; | ||||||
| use Illuminate\Support\Facades\Http; | use Illuminate\Support\Facades\Http; | ||||||
| use Illuminate\Support\ServiceProvider; | use Illuminate\Support\ServiceProvider; | ||||||
| use Illuminate\Validation\Rules\Password; | use Illuminate\Validation\Rules\Password; | ||||||
| @@ -19,6 +20,9 @@ class AppServiceProvider extends ServiceProvider | |||||||
| 
 | 
 | ||||||
|     public function boot(): void |     public function boot(): void | ||||||
|     { |     { | ||||||
|  |         Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) { | ||||||
|  |             $event->extendSocialite('authentik', \SocialiteProviders\Authentik\Provider::class); | ||||||
|  |         }); | ||||||
|         Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class); |         Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class); | ||||||
| 
 | 
 | ||||||
|         Password::defaults(function () { |         Password::defaults(function () { | ||||||
|   | |||||||
| @@ -9,6 +9,9 @@ use App\Listeners\ProxyStartedNotification; | |||||||
| use Illuminate\Foundation\Events\MaintenanceModeDisabled; | use Illuminate\Foundation\Events\MaintenanceModeDisabled; | ||||||
| use Illuminate\Foundation\Events\MaintenanceModeEnabled; | use Illuminate\Foundation\Events\MaintenanceModeEnabled; | ||||||
| use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; | use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; | ||||||
|  | use SocialiteProviders\Authentik\AuthentikExtendSocialite; | ||||||
|  | use SocialiteProviders\Azure\AzureExtendSocialite; | ||||||
|  | use SocialiteProviders\Manager\SocialiteWasCalled; | ||||||
| 
 | 
 | ||||||
| class EventServiceProvider extends ServiceProvider | class EventServiceProvider extends ServiceProvider | ||||||
| { | { | ||||||
| @@ -19,8 +22,9 @@ class EventServiceProvider extends ServiceProvider | |||||||
|         MaintenanceModeDisabled::class => [ |         MaintenanceModeDisabled::class => [ | ||||||
|             MaintenanceModeDisabledNotification::class, |             MaintenanceModeDisabledNotification::class, | ||||||
|         ], |         ], | ||||||
|         \SocialiteProviders\Manager\SocialiteWasCalled::class => [ |         SocialiteWasCalled::class => [ | ||||||
|             \SocialiteProviders\Azure\AzureExtendSocialite::class.'@handle', |             AzureExtendSocialite::class.'@handle', | ||||||
|  |             AuthentikExtendSocialite::class.'@handle', | ||||||
|         ], |         ], | ||||||
|         ProxyStarted::class => [ |         ProxyStarted::class => [ | ||||||
|             ProxyStartedNotification::class, |             ProxyStartedNotification::class, | ||||||
|   | |||||||
| @@ -50,13 +50,10 @@ class FortifyServiceProvider extends ServiceProvider | |||||||
|             if (! $settings->is_registration_enabled) { |             if (! $settings->is_registration_enabled) { | ||||||
|                 return redirect()->route('login'); |                 return redirect()->route('login'); | ||||||
|             } |             } | ||||||
|             if (config('constants.waitlist.enabled')) { | 
 | ||||||
|                 return redirect()->route('waitlist.index'); |             return view('auth.register', [ | ||||||
|             } else { |                 'isFirstUser' => $isFirstUser, | ||||||
|                 return view('auth.register', [ |             ]); | ||||||
|                     'isFirstUser' => $isFirstUser, |  | ||||||
|                 ]); |  | ||||||
|             } |  | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         Fortify::loginView(function () { |         Fortify::loginView(function () { | ||||||
|   | |||||||
							
								
								
									
										93
									
								
								app/Traits/HasNotificationSettings.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								app/Traits/HasNotificationSettings.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Traits; | ||||||
|  | 
 | ||||||
|  | use App\Notifications\Channels\DiscordChannel; | ||||||
|  | use App\Notifications\Channels\EmailChannel; | ||||||
|  | use App\Notifications\Channels\SlackChannel; | ||||||
|  | use App\Notifications\Channels\TelegramChannel; | ||||||
|  | use App\Notifications\Channels\PushoverChannel; | ||||||
|  | use Illuminate\Database\Eloquent\Model; | ||||||
|  | 
 | ||||||
|  | trait HasNotificationSettings | ||||||
|  | { | ||||||
|  |     protected $alwaysSendEvents = [ | ||||||
|  |         'server_force_enabled', | ||||||
|  |         'server_force_disabled', | ||||||
|  |         'general', | ||||||
|  |         'test', | ||||||
|  |     ]; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get settings model for specific channel | ||||||
|  |      */ | ||||||
|  |     public function getNotificationSettings(string $channel): ?Model | ||||||
|  |     { | ||||||
|  |         return match ($channel) { | ||||||
|  |             'email' => $this->emailNotificationSettings, | ||||||
|  |             'discord' => $this->discordNotificationSettings, | ||||||
|  |             'telegram' => $this->telegramNotificationSettings, | ||||||
|  |             'slack' => $this->slackNotificationSettings, | ||||||
|  |             'pushover' => $this->pushoverNotificationSettings, | ||||||
|  |             default => null, | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Check if a notification channel is enabled | ||||||
|  |      */ | ||||||
|  |     public function isNotificationEnabled(string $channel): bool | ||||||
|  |     { | ||||||
|  |         $settings = $this->getNotificationSettings($channel); | ||||||
|  | 
 | ||||||
|  |         return $settings?->isEnabled() ?? false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Check if a specific notification type is enabled for a channel | ||||||
|  |      */ | ||||||
|  |     public function isNotificationTypeEnabled(string $channel, string $event): bool | ||||||
|  |     { | ||||||
|  |         $settings = $this->getNotificationSettings($channel); | ||||||
|  | 
 | ||||||
|  |         if (! $settings || ! $this->isNotificationEnabled($channel)) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (in_array($event, $this->alwaysSendEvents)) { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         $settingKey = "{$event}_{$channel}_notifications"; | ||||||
|  | 
 | ||||||
|  |         return (bool) $settings->$settingKey; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get all enabled notification channels for an event | ||||||
|  |      */ | ||||||
|  |     public function getEnabledChannels(string $event): array | ||||||
|  |     { | ||||||
|  |         $channels = []; | ||||||
|  | 
 | ||||||
|  |         $channelMap = [ | ||||||
|  |             'email' => EmailChannel::class, | ||||||
|  |             'discord' => DiscordChannel::class, | ||||||
|  |             'telegram' => TelegramChannel::class, | ||||||
|  |             'slack' => SlackChannel::class, | ||||||
|  |             'pushover' => PushoverChannel::class, | ||||||
|  |         ]; | ||||||
|  | 
 | ||||||
|  |         if ($event === 'general') { | ||||||
|  |             unset($channelMap['email']); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         foreach ($channelMap as $channel => $channelClass) { | ||||||
|  |             if ($this->isNotificationEnabled($channel) && $this->isNotificationTypeEnabled($channel, $event)) { | ||||||
|  |                 $channels[] = $channelClass; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return $channels; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -17,7 +17,7 @@ class Services extends Component | |||||||
|         public string $complexStatus = 'exited', |         public string $complexStatus = 'exited', | ||||||
|         public bool $showRefreshButton = true |         public bool $showRefreshButton = true | ||||||
|     ) { |     ) { | ||||||
|         $this->complexStatus = $service->status(); |         $this->complexStatus = $service->status; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|   | |||||||
							
								
								
									
										87
									
								
								bootstrap/helpers/notifications.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								bootstrap/helpers/notifications.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | use App\Models\InstanceSettings; | ||||||
|  | use App\Models\Team; | ||||||
|  | use App\Notifications\Internal\GeneralNotification; | ||||||
|  | use Illuminate\Mail\Message; | ||||||
|  | use Illuminate\Notifications\Messages\MailMessage; | ||||||
|  | use Illuminate\Support\Facades\Mail; | ||||||
|  | 
 | ||||||
|  | function is_transactional_emails_enabled(): bool | ||||||
|  | { | ||||||
|  |     $settings = instanceSettings(); | ||||||
|  | 
 | ||||||
|  |     return $settings->smtp_enabled || $settings->resend_enabled; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function send_internal_notification(string $message): void | ||||||
|  | { | ||||||
|  |     try { | ||||||
|  |         $team = Team::find(0); | ||||||
|  |         $team?->notify(new GeneralNotification($message)); | ||||||
|  |     } catch (\Throwable $e) { | ||||||
|  |         ray($e->getMessage()); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null): void | ||||||
|  | { | ||||||
|  |     $settings = instanceSettings(); | ||||||
|  |     $type = set_transanctional_email_settings($settings); | ||||||
|  |     if (! $type) { | ||||||
|  |         throw new Exception('No email settings found.'); | ||||||
|  |     } | ||||||
|  |     if ($cc) { | ||||||
|  |         Mail::send( | ||||||
|  |             [], | ||||||
|  |             [], | ||||||
|  |             fn (Message $message) => $message | ||||||
|  |                 ->to($email) | ||||||
|  |                 ->replyTo($email) | ||||||
|  |                 ->cc($cc) | ||||||
|  |                 ->subject($mail->subject) | ||||||
|  |                 ->html((string) $mail->render()) | ||||||
|  |         ); | ||||||
|  |     } else { | ||||||
|  |         Mail::send( | ||||||
|  |             [], | ||||||
|  |             [], | ||||||
|  |             fn (Message $message) => $message | ||||||
|  |                 ->to($email) | ||||||
|  |                 ->subject($mail->subject) | ||||||
|  |                 ->html((string) $mail->render()) | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function set_transanctional_email_settings(?InstanceSettings $settings = null): ?string //
 | ||||||
|  | { | ||||||
|  |     if (! $settings) { | ||||||
|  |         $settings = instanceSettings(); | ||||||
|  |     } | ||||||
|  |     config()->set('mail.from.address', data_get($settings, 'smtp_from_address')); | ||||||
|  |     config()->set('mail.from.name', data_get($settings, 'smtp_from_name')); | ||||||
|  |     if (data_get($settings, 'resend_enabled')) { | ||||||
|  |         config()->set('mail.default', 'resend'); | ||||||
|  |         config()->set('resend.api_key', data_get($settings, 'resend_api_key')); | ||||||
|  | 
 | ||||||
|  |         return 'resend'; | ||||||
|  |     } | ||||||
|  |     if (data_get($settings, 'smtp_enabled')) { | ||||||
|  |         config()->set('mail.default', 'smtp'); | ||||||
|  |         config()->set('mail.mailers.smtp', [ | ||||||
|  |             'transport' => 'smtp', | ||||||
|  |             'host' => data_get($settings, 'smtp_host'), | ||||||
|  |             'port' => data_get($settings, 'smtp_port'), | ||||||
|  |             'encryption' => data_get($settings, 'smtp_encryption'), | ||||||
|  |             'username' => data_get($settings, 'smtp_username'), | ||||||
|  |             'password' => data_get($settings, 'smtp_password'), | ||||||
|  |             'timeout' => data_get($settings, 'smtp_timeout'), | ||||||
|  |             'local_domain' => null, | ||||||
|  |         ]); | ||||||
|  | 
 | ||||||
|  |         return 'smtp'; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return null; | ||||||
|  | } | ||||||
| @@ -25,23 +25,15 @@ use App\Models\StandalonePostgresql; | |||||||
| use App\Models\StandaloneRedis; | use App\Models\StandaloneRedis; | ||||||
| use App\Models\Team; | use App\Models\Team; | ||||||
| use App\Models\User; | use App\Models\User; | ||||||
| use App\Notifications\Channels\DiscordChannel; |  | ||||||
| use App\Notifications\Channels\EmailChannel; |  | ||||||
| use App\Notifications\Channels\SlackChannel; |  | ||||||
| use App\Notifications\Channels\TelegramChannel; |  | ||||||
| use App\Notifications\Internal\GeneralNotification; |  | ||||||
| use Carbon\CarbonImmutable; | use Carbon\CarbonImmutable; | ||||||
| use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException; | use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException; | ||||||
| use Illuminate\Database\UniqueConstraintViolationException; | use Illuminate\Database\UniqueConstraintViolationException; | ||||||
| use Illuminate\Mail\Message; |  | ||||||
| use Illuminate\Notifications\Messages\MailMessage; |  | ||||||
| use Illuminate\Process\Pool; | use Illuminate\Process\Pool; | ||||||
| use Illuminate\Support\Collection; | use Illuminate\Support\Collection; | ||||||
| use Illuminate\Support\Facades\Auth; | use Illuminate\Support\Facades\Auth; | ||||||
| use Illuminate\Support\Facades\Cache; | use Illuminate\Support\Facades\Cache; | ||||||
| use Illuminate\Support\Facades\File; | use Illuminate\Support\Facades\File; | ||||||
| use Illuminate\Support\Facades\Http; | use Illuminate\Support\Facades\Http; | ||||||
| use Illuminate\Support\Facades\Mail; |  | ||||||
| use Illuminate\Support\Facades\Process; | use Illuminate\Support\Facades\Process; | ||||||
| use Illuminate\Support\Facades\RateLimiter; | use Illuminate\Support\Facades\RateLimiter; | ||||||
| use Illuminate\Support\Facades\Request; | use Illuminate\Support\Facades\Request; | ||||||
| @@ -267,43 +259,6 @@ function generate_application_name(string $git_repository, string $git_branch, ? | |||||||
|     return Str::kebab("$git_repository:$git_branch-$cuid"); |     return Str::kebab("$git_repository:$git_branch-$cuid"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function is_transactional_emails_active(): bool |  | ||||||
| { |  | ||||||
|     return isEmailEnabled(\App\Models\InstanceSettings::get()); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function set_transanctional_email_settings(?InstanceSettings $settings = null): ?string |  | ||||||
| { |  | ||||||
|     if (! $settings) { |  | ||||||
|         $settings = instanceSettings(); |  | ||||||
|     } |  | ||||||
|     config()->set('mail.from.address', data_get($settings, 'smtp_from_address')); |  | ||||||
|     config()->set('mail.from.name', data_get($settings, 'smtp_from_name')); |  | ||||||
|     if (data_get($settings, 'resend_enabled')) { |  | ||||||
|         config()->set('mail.default', 'resend'); |  | ||||||
|         config()->set('resend.api_key', data_get($settings, 'resend_api_key')); |  | ||||||
| 
 |  | ||||||
|         return 'resend'; |  | ||||||
|     } |  | ||||||
|     if (data_get($settings, 'smtp_enabled')) { |  | ||||||
|         config()->set('mail.default', 'smtp'); |  | ||||||
|         config()->set('mail.mailers.smtp', [ |  | ||||||
|             'transport' => 'smtp', |  | ||||||
|             'host' => data_get($settings, 'smtp_host'), |  | ||||||
|             'port' => data_get($settings, 'smtp_port'), |  | ||||||
|             'encryption' => data_get($settings, 'smtp_encryption'), |  | ||||||
|             'username' => data_get($settings, 'smtp_username'), |  | ||||||
|             'password' => data_get($settings, 'smtp_password'), |  | ||||||
|             'timeout' => data_get($settings, 'smtp_timeout'), |  | ||||||
|             'local_domain' => null, |  | ||||||
|         ]); |  | ||||||
| 
 |  | ||||||
|         return 'smtp'; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return null; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function base_ip(): string | function base_ip(): string | ||||||
| { | { | ||||||
|     if (isDev()) { |     if (isDev()) { | ||||||
| @@ -414,85 +369,7 @@ function validate_timezone(string $timezone): bool | |||||||
| { | { | ||||||
|     return in_array($timezone, timezone_identifiers_list()); |     return in_array($timezone, timezone_identifiers_list()); | ||||||
| } | } | ||||||
| function send_internal_notification(string $message): void |  | ||||||
| { |  | ||||||
|     try { |  | ||||||
|         $team = Team::find(0); |  | ||||||
|         $team?->notify(new GeneralNotification($message)); |  | ||||||
|     } catch (\Throwable $e) { |  | ||||||
|         ray($e->getMessage()); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null): void |  | ||||||
| { |  | ||||||
|     $settings = instanceSettings(); |  | ||||||
|     $type = set_transanctional_email_settings($settings); |  | ||||||
|     if (! $type) { |  | ||||||
|         throw new Exception('No email settings found.'); |  | ||||||
|     } |  | ||||||
|     if ($cc) { |  | ||||||
|         Mail::send( |  | ||||||
|             [], |  | ||||||
|             [], |  | ||||||
|             fn (Message $message) => $message |  | ||||||
|                 ->to($email) |  | ||||||
|                 ->replyTo($email) |  | ||||||
|                 ->cc($cc) |  | ||||||
|                 ->subject($mail->subject) |  | ||||||
|                 ->html((string) $mail->render()) |  | ||||||
|         ); |  | ||||||
|     } else { |  | ||||||
|         Mail::send( |  | ||||||
|             [], |  | ||||||
|             [], |  | ||||||
|             fn (Message $message) => $message |  | ||||||
|                 ->to($email) |  | ||||||
|                 ->subject($mail->subject) |  | ||||||
|                 ->html((string) $mail->render()) |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| function isTestEmailEnabled($notifiable) |  | ||||||
| { |  | ||||||
|     if (data_get($notifiable, 'use_instance_email_settings') && isInstanceAdmin()) { |  | ||||||
|         return true; |  | ||||||
|     } elseif (data_get($notifiable, 'smtp_enabled') || data_get($notifiable, 'resend_enabled') && auth()->user()->isAdminFromSession()) { |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     return false; |  | ||||||
| } |  | ||||||
| function isEmailEnabled($notifiable) |  | ||||||
| { |  | ||||||
|     return data_get($notifiable, 'smtp_enabled') || data_get($notifiable, 'resend_enabled') || data_get($notifiable, 'use_instance_email_settings'); |  | ||||||
| } |  | ||||||
| function setNotificationChannels($notifiable, $event) |  | ||||||
| { |  | ||||||
|     $channels = []; |  | ||||||
|     $isEmailEnabled = isEmailEnabled($notifiable); |  | ||||||
|     $isSlackEnabled = data_get($notifiable, 'slack_enabled'); |  | ||||||
|     $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); |  | ||||||
|     $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); |  | ||||||
|     $isSubscribedToEmailEvent = data_get($notifiable, "smtp_notifications_$event"); |  | ||||||
|     $isSubscribedToDiscordEvent = data_get($notifiable, "discord_notifications_$event"); |  | ||||||
|     $isSubscribedToTelegramEvent = data_get($notifiable, "telegram_notifications_$event"); |  | ||||||
|     $isSubscribedToSlackEvent = data_get($notifiable, "slack_notifications_$event"); |  | ||||||
| 
 |  | ||||||
|     if ($isDiscordEnabled && $isSubscribedToDiscordEvent) { |  | ||||||
|         $channels[] = DiscordChannel::class; |  | ||||||
|     } |  | ||||||
|     if ($isEmailEnabled && $isSubscribedToEmailEvent) { |  | ||||||
|         $channels[] = EmailChannel::class; |  | ||||||
|     } |  | ||||||
|     if ($isTelegramEnabled && $isSubscribedToTelegramEvent) { |  | ||||||
|         $channels[] = TelegramChannel::class; |  | ||||||
|     } |  | ||||||
|     if ($isSlackEnabled && $isSubscribedToSlackEvent) { |  | ||||||
|         $channels[] = SlackChannel::class; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return $channels; |  | ||||||
| } |  | ||||||
| function parseEnvFormatToArray($env_file_contents) | function parseEnvFormatToArray($env_file_contents) | ||||||
| { | { | ||||||
|     $env_array = []; |     $env_array = []; | ||||||
| @@ -947,6 +824,12 @@ function generateEnvValue(string $command, Service|Application|null $service = n | |||||||
|         case 'PASSWORD_64': |         case 'PASSWORD_64': | ||||||
|             $generatedValue = Str::password(length: 64, symbols: false); |             $generatedValue = Str::password(length: 64, symbols: false); | ||||||
|             break; |             break; | ||||||
|  |         case 'PASSWORDWITHSYMBOLS': | ||||||
|  |             $generatedValue = Str::password(symbols: true); | ||||||
|  |             break; | ||||||
|  |         case 'PASSWORDWITHSYMBOLS_64': | ||||||
|  |             $generatedValue = Str::password(length: 64, symbols: true); | ||||||
|  |             break; | ||||||
|             // This is not base64, it's just a random string
 |             // This is not base64, it's just a random string
 | ||||||
|         case 'BASE64_64': |         case 'BASE64_64': | ||||||
|             $generatedValue = Str::random(64); |             $generatedValue = Str::random(64); | ||||||
|   | |||||||
| @@ -18,6 +18,17 @@ function get_socialite_provider(string $provider) | |||||||
|         return Socialite::driver('azure')->setConfig($azure_config); |         return Socialite::driver('azure')->setConfig($azure_config); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if ($provider == 'authentik') { | ||||||
|  |         $authentik_config = new \SocialiteProviders\Manager\Config( | ||||||
|  |             $oauth_setting->client_id, | ||||||
|  |             $oauth_setting->client_secret, | ||||||
|  |             $oauth_setting->redirect_uri, | ||||||
|  |             ['base_url' => $oauth_setting->base_url], | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         return Socialite::driver('authentik')->setConfig($authentik_config); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     $config = [ |     $config = [ | ||||||
|         'client_id' => $oauth_setting->client_id, |         'client_id' => $oauth_setting->client_id, | ||||||
|         'client_secret' => $oauth_setting->client_secret, |         'client_secret' => $oauth_setting->client_secret, | ||||||
|   | |||||||
| @@ -67,7 +67,6 @@ function allowedPathsForUnsubscribedAccounts() | |||||||
|         'subscription/new', |         'subscription/new', | ||||||
|         'login', |         'login', | ||||||
|         'logout', |         'logout', | ||||||
|         'waitlist', |  | ||||||
|         'force-password-reset', |         'force-password-reset', | ||||||
|         'livewire/update', |         'livewire/update', | ||||||
|     ]; |     ]; | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ | |||||||
|     "require": { |     "require": { | ||||||
|         "php": "^8.2", |         "php": "^8.2", | ||||||
|         "3sidedcube/laravel-redoc": "^1.0", |         "3sidedcube/laravel-redoc": "^1.0", | ||||||
|         "danharrin/livewire-rate-limiting": "^1.1", |         "danharrin/livewire-rate-limiting": "2.0.0", | ||||||
|         "doctrine/dbal": "^4.2", |         "doctrine/dbal": "^4.2", | ||||||
|         "guzzlehttp/guzzle": "^7.5.0", |         "guzzlehttp/guzzle": "^7.5.0", | ||||||
|         "laravel/fortify": "^1.16.0", |         "laravel/fortify": "^1.16.0", | ||||||
| @@ -39,6 +39,7 @@ | |||||||
|         "pusher/pusher-php-server": "^7.2", |         "pusher/pusher-php-server": "^7.2", | ||||||
|         "resend/resend-laravel": "^0.15.0", |         "resend/resend-laravel": "^0.15.0", | ||||||
|         "sentry/sentry-laravel": "^4.6", |         "sentry/sentry-laravel": "^4.6", | ||||||
|  |         "socialiteproviders/authentik": "^5.2", | ||||||
|         "socialiteproviders/microsoft-azure": "^5.1", |         "socialiteproviders/microsoft-azure": "^5.1", | ||||||
|         "spatie/laravel-activitylog": "^4.7.3", |         "spatie/laravel-activitylog": "^4.7.3", | ||||||
|         "spatie/laravel-data": "^4.11", |         "spatie/laravel-data": "^4.11", | ||||||
|   | |||||||
							
								
								
									
										1760
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1760
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -2,7 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| return [ | return [ | ||||||
|     'coolify' => [ |     'coolify' => [ | ||||||
|         'version' => '4.0.0-beta.377', |         'version' => '4.0.0-beta.380', | ||||||
|         'self_hosted' => env('SELF_HOSTED', true), |         'self_hosted' => env('SELF_HOSTED', true), | ||||||
|         'autoupdate' => env('AUTOUPDATE'), |         'autoupdate' => env('AUTOUPDATE'), | ||||||
|         'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), |         'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), | ||||||
| @@ -33,6 +33,14 @@ return [ | |||||||
|         'app_key' => env('PUSHER_APP_KEY'), |         'app_key' => env('PUSHER_APP_KEY'), | ||||||
|     ], |     ], | ||||||
| 
 | 
 | ||||||
|  |     'migration' => [ | ||||||
|  |         'is_migration_enabled' => env('MIGRATION_ENABLED', true), | ||||||
|  |     ], | ||||||
|  | 
 | ||||||
|  |     'seeder' => [ | ||||||
|  |         'is_seeder_enabled' => env('SEEDER_ENABLED', true), | ||||||
|  |     ], | ||||||
|  | 
 | ||||||
|     'horizon' => [ |     'horizon' => [ | ||||||
|         'is_horizon_enabled' => env('HORIZON_ENABLED', true), |         'is_horizon_enabled' => env('HORIZON_ENABLED', true), | ||||||
|         'is_scheduler_enabled' => env('SCHEDULER_ENABLED', true), |         'is_scheduler_enabled' => env('SCHEDULER_ENABLED', true), | ||||||
| @@ -77,11 +85,6 @@ return [ | |||||||
|         ], |         ], | ||||||
|     ], |     ], | ||||||
| 
 | 
 | ||||||
|     'waitlist' => [ |  | ||||||
|         'enabled' => env('WAITLIST', false), |  | ||||||
|         'expiration' => 10, |  | ||||||
|     ], |  | ||||||
| 
 |  | ||||||
|     'sentry' => [ |     'sentry' => [ | ||||||
|         'sentry_dsn' => env('SENTRY_DSN'), |         'sentry_dsn' => env('SENTRY_DSN'), | ||||||
|     ], |     ], | ||||||
|   | |||||||
| @@ -38,4 +38,11 @@ return [ | |||||||
|         'tenant' => env('AZURE_TENANT_ID'), |         'tenant' => env('AZURE_TENANT_ID'), | ||||||
|         'proxy' => env('AZURE_PROXY'), |         'proxy' => env('AZURE_PROXY'), | ||||||
|     ], |     ], | ||||||
|  | 
 | ||||||
|  |     'authentik' => [ | ||||||
|  |         'base_url' => env('AUTHENTIK_BASE_URL'), | ||||||
|  |         'client_id' => env('AUTHENTIK_CLIENT_ID'), | ||||||
|  |         'client_secret' => env('AUTHENTIK_CLIENT_SECRET'), | ||||||
|  |         'redirect' => env('AUTHENTIK_REDIRECT_URI'), | ||||||
|  |     ], | ||||||
| ]; | ]; | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 🏔️ Peak
					🏔️ Peak