diff --git a/app/Console/Commands/CleanupUnreachableServers.php b/app/Console/Commands/CleanupUnreachableServers.php index df0c6b81b..def01b265 100644 --- a/app/Console/Commands/CleanupUnreachableServers.php +++ b/app/Console/Commands/CleanupUnreachableServers.php @@ -18,7 +18,6 @@ class CleanupUnreachableServers extends Command if ($servers->count() > 0) { foreach ($servers as $server) { echo "Cleanup unreachable server ($server->id) with name $server->name"; - // send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up..."); $server->update([ 'ip' => '1.2.3.4', ]); diff --git a/app/Console/Commands/Emails.php b/app/Console/Commands/Emails.php index f0e0e7fa0..33ddf3019 100644 --- a/app/Console/Commands/Emails.php +++ b/app/Console/Commands/Emails.php @@ -2,14 +2,12 @@ namespace App\Console\Commands; -use App\Jobs\SendConfirmationForWaitlistJob; use App\Models\Application; use App\Models\ApplicationPreview; use App\Models\ScheduledDatabaseBackup; use App\Models\Server; use App\Models\StandalonePostgresql; use App\Models\Team; -use App\Models\Waitlist; use App\Notifications\Application\DeploymentFailed; use App\Notifications\Application\DeploymentSuccess; use App\Notifications\Application\StatusChanged; @@ -64,8 +62,6 @@ class Emails extends Command 'backup-success' => 'Database - Backup Success', 'backup-failed' => 'Database - Backup Failed', // 'invitation-link' => 'Invitation Link', - 'waitlist-invitation-link' => 'Waitlist Invitation Link', - 'waitlist-confirmation' => 'Waitlist Confirmation', 'realusers-before-trial' => 'REAL - Registered Users Before Trial without Subscription', 'realusers-server-lost-connection' => 'REAL - Server Lost Connection', ], @@ -187,7 +183,7 @@ class Emails extends Command 'team_id' => 0, ]); } - // $this->mail = (new BackupSuccess($backup->frequency, $db->name))->toMail(); + //$this->mail = (new BackupSuccess($backup->frequency, $db->name))->toMail(); $this->sendEmail(); break; // case 'invitation-link': @@ -204,23 +200,6 @@ class Emails extends Command // $this->mail = (new InvitationLink($user))->toMail(); // $this->sendEmail(); // break; - case 'waitlist-invitation-link': - $this->mail = new MailMessage; - $this->mail->view('emails.waitlist-invitation', [ - 'loginLink' => 'https://coolify.io', - ]); - $this->mail->subject('Congratulations! You are invited to join Coolify Cloud.'); - $this->sendEmail(); - break; - case 'waitlist-confirmation': - $found = Waitlist::where('email', $this->email)->first(); - if ($found) { - SendConfirmationForWaitlistJob::dispatch($this->email, $found->uuid); - } else { - throw new Exception('Waitlist not found'); - } - - break; case 'realusers-before-trial': $this->mail = new MailMessage; $this->mail->view('emails.before-trial-conversion'); diff --git a/app/Console/Commands/WaitlistInvite.php b/app/Console/Commands/WaitlistInvite.php deleted file mode 100644 index 2e330068c..000000000 --- a/app/Console/Commands/WaitlistInvite.php +++ /dev/null @@ -1,114 +0,0 @@ -option('people'); - for ($i = 0; $i < $people; $i++) { - $this->main(); - } - } - - private function main() - { - if ($this->argument('email')) { - if ($this->option('only-email')) { - $this->next_patient = User::whereEmail($this->argument('email'))->first(); - $this->password = Str::password(); - $this->next_patient->update([ - 'password' => Hash::make($this->password), - 'force_password_reset' => true, - ]); - } else { - $this->next_patient = Waitlist::where('email', $this->argument('email'))->first(); - } - if (! $this->next_patient) { - $this->error("{$this->argument('email')} not found in the waitlist."); - - return; - } - } else { - $this->next_patient = Waitlist::orderBy('created_at', 'asc')->where('verified', true)->first(); - } - if ($this->next_patient) { - if ($this->option('only-email')) { - $this->send_email(); - - return; - } - $this->register_user(); - $this->remove_from_waitlist(); - $this->send_email(); - } else { - $this->info('No verified user found in the waitlist. 👀'); - } - } - - private function register_user() - { - $already_registered = User::whereEmail($this->next_patient->email)->first(); - if (! $already_registered) { - $this->password = Str::password(); - User::create([ - 'name' => str($this->next_patient->email)->before('@'), - 'email' => $this->next_patient->email, - 'password' => Hash::make($this->password), - 'force_password_reset' => true, - ]); - $this->info("User registered ({$this->next_patient->email}) successfully. 🎉"); - } else { - throw new \Exception('User already registered'); - } - } - - private function remove_from_waitlist() - { - $this->next_patient->delete(); - $this->info('User removed from waitlist successfully.'); - } - - private function send_email() - { - $token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password"); - $loginLink = route('auth.link', ['token' => $token]); - $mail = new MailMessage; - $mail->view('emails.waitlist-invitation', [ - 'loginLink' => $loginLink, - ]); - $mail->subject('Congratulations! You are invited to join Coolify Cloud.'); - send_user_an_email($mail, $this->next_patient->email); - $this->info('Email sent successfully. 📧'); - } -} diff --git a/app/Console/Commands/Weird.php b/app/Console/Commands/Weird.php deleted file mode 100644 index e471a5f96..000000000 --- a/app/Console/Commands/Weird.php +++ /dev/null @@ -1,58 +0,0 @@ -error('This command can only be run in development mode'); - - return; - } - $run = $this->option('run'); - if ($run) { - $servers = Server::all(); - foreach ($servers as $server) { - ServerCheck::dispatch($server); - } - - return; - } - $number = $this->option('number'); - for ($i = 0; $i < $number; $i++) { - $uuid = Str::uuid(); - $server = Server::create([ - 'name' => 'localhost-'.$uuid, - 'description' => 'This is a test docker container in development mode', - 'ip' => 'coolify-testing-host', - 'team_id' => 0, - 'private_key_id' => 1, - 'proxy' => [ - 'type' => ProxyTypes::NONE->value, - 'status' => ProxyStatus::EXITED->value, - ], - ]); - $server->settings->update([ - 'is_usable' => true, - 'is_reachable' => true, - ]); - } - } catch (\Exception $e) { - $this->error($e->getMessage()); - } - } -} diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 9f1e4eeb8..522683efa 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -42,15 +42,13 @@ class Controller extends BaseController public function email_verify(EmailVerificationRequest $request) { $request->fulfill(); - $name = request()->user()?->name; - // send_internal_notification("User {$name} verified their email address."); return redirect(RouteServiceProvider::HOME); } public function forgot_password(Request $request) { - if (is_transactional_emails_active()) { + if (is_transactional_emails_enabled()) { $arrayOfRequest = $request->only(Fortify::email()); $request->merge([ 'email' => Str::lower($arrayOfRequest['email']), diff --git a/app/Http/Controllers/Webhook/Waitlist.php b/app/Http/Controllers/Webhook/Waitlist.php deleted file mode 100644 index dec8ca72d..000000000 --- a/app/Http/Controllers/Webhook/Waitlist.php +++ /dev/null @@ -1,63 +0,0 @@ -get('email'); - $confirmation_code = request()->get('confirmation_code'); - try { - $found = ModelsWaitlist::where('uuid', $confirmation_code)->where('email', $email)->first(); - if ($found) { - if (! $found->verified) { - if ($found->created_at > now()->subMinutes(config('constants.waitlist.expiration'))) { - $found->verified = true; - $found->save(); - send_internal_notification('Waitlist confirmed: '.$email); - - return 'Thank you for confirming your email address. We will notify you when you are next in line.'; - } else { - $found->delete(); - send_internal_notification('Waitlist expired: '.$email); - - return 'Your confirmation code has expired. Please sign up again.'; - } - } - } - - return redirect()->route('dashboard'); - } catch (Exception $e) { - send_internal_notification('Waitlist confirmation failed: '.$e->getMessage()); - - return redirect()->route('dashboard'); - } - } - - public function cancel(Request $request) - { - $email = request()->get('email'); - $confirmation_code = request()->get('confirmation_code'); - try { - $found = ModelsWaitlist::where('uuid', $confirmation_code)->where('email', $email)->first(); - if ($found && ! $found->verified) { - $found->delete(); - send_internal_notification('Waitlist cancelled: '.$email); - - return 'Your email address has been removed from the waitlist.'; - } - - return redirect()->route('dashboard'); - } catch (Exception $e) { - send_internal_notification('Waitlist cancellation failed: '.$e->getMessage()); - - return redirect()->route('dashboard'); - } - } -} diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 04e71c4e3..6b677fa0e 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -2409,7 +2409,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); if (! $this->only_this_server) { $this->deploy_to_additional_destinations(); } - //$this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview)); + $this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview)); } } diff --git a/app/Jobs/CheckResaleLicenseJob.php b/app/Jobs/CheckResaleLicenseJob.php deleted file mode 100644 index 7479867b6..000000000 --- a/app/Jobs/CheckResaleLicenseJob.php +++ /dev/null @@ -1,28 +0,0 @@ -getMessage()); - throw $e; - } - } -} diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php index ee702202f..06aec5e49 100644 --- a/app/Jobs/DatabaseBackupJob.php +++ b/app/Jobs/DatabaseBackupJob.php @@ -306,7 +306,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue if ($this->backup->save_s3) { $this->upload_to_s3(); } - //$this->team?->notify(new BackupSuccess($this->backup, $this->database, $database)); + + $this->team->notify(new BackupSuccess($this->backup, $this->database, $database)); + $this->backup_log->update([ 'status' => 'success', 'message' => $this->backup_output, diff --git a/app/Jobs/DockerCleanupJob.php b/app/Jobs/DockerCleanupJob.php index 80542e03b..103c137b9 100644 --- a/app/Jobs/DockerCleanupJob.php +++ b/app/Jobs/DockerCleanupJob.php @@ -4,7 +4,8 @@ namespace App\Jobs; use App\Actions\Server\CleanupDocker; use App\Models\Server; -use App\Notifications\Server\DockerCleanup; +use App\Notifications\Server\DockerCleanupFailed; +use App\Notifications\Server\DockerCleanupSuccess; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeEncrypted; use Illuminate\Contracts\Queue\ShouldQueue; @@ -12,7 +13,6 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Log; class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue { @@ -38,35 +38,36 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue return; } - if ($this->manualCleanup || $this->server->settings->force_docker_cleanup) { - Log::info('DockerCleanupJob '.($this->manualCleanup ? 'manual' : 'force').' cleanup on '.$this->server->name); - CleanupDocker::run(server: $this->server); - - return; - } - $this->usageBefore = $this->server->getDiskUsage(); - if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) { - Log::info('DockerCleanupJob force cleanup on '.$this->server->name); + + if ($this->manualCleanup || $this->server->settings->force_docker_cleanup) { CleanupDocker::run(server: $this->server); + $usageAfter = $this->server->getDiskUsage(); + $this->server->team?->notify(new DockerCleanupSuccess($this->server, ($this->manualCleanup ? 'Manual' : 'Forced').' Docker cleanup job executed successfully. Disk usage before: '.$this->usageBefore.'%, Disk usage after: '.$usageAfter.'%.')); return; } + + if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) { + CleanupDocker::run(server: $this->server); + $this->server->team?->notify(new DockerCleanupSuccess($this->server, 'Docker cleanup job executed successfully, but no disk usage could be determined.')); + } + if ($this->usageBefore >= $this->server->settings->docker_cleanup_threshold) { CleanupDocker::run(server: $this->server); $usageAfter = $this->server->getDiskUsage(); - if ($usageAfter < $this->usageBefore) { - $this->server->team?->notify(new DockerCleanup($this->server, 'Saved '.($this->usageBefore - $usageAfter).'% disk space.')); - Log::info('DockerCleanupJob done: Saved '.($this->usageBefore - $usageAfter).'% disk space on '.$this->server->name); + $diskSaved = $this->usageBefore - $usageAfter; + + if ($diskSaved > 0) { + $this->server->team?->notify(new DockerCleanupSuccess($this->server, 'Saved '.$diskSaved.'% disk space. Disk usage before: '.$this->usageBefore.'%, Disk usage after: '.$usageAfter.'%.')); } else { - Log::info('DockerCleanupJob failed to save disk space on '.$this->server->name); + $this->server->team?->notify(new DockerCleanupSuccess($this->server, 'Docker cleanup job executed successfully, but no disk space was saved. Disk usage before: '.$this->usageBefore.'%, Disk usage after: '.$usageAfter.'%.')); } } else { - Log::info('No need to clean up '.$this->server->name); + $this->server->team?->notify(new DockerCleanupSuccess($this->server, 'No cleanup needed for '.$this->server->name)); } } catch (\Throwable $e) { - CleanupDocker::run(server: $this->server); - Log::error('DockerCleanupJob failed: '.$e->getMessage()); + $this->server->team?->notify(new DockerCleanupFailed($this->server, 'Docker cleanup job failed with the following error: '.$e->getMessage())); throw $e; } } diff --git a/app/Jobs/ScheduledTaskJob.php b/app/Jobs/ScheduledTaskJob.php index 00575e187..90a10f3e9 100644 --- a/app/Jobs/ScheduledTaskJob.php +++ b/app/Jobs/ScheduledTaskJob.php @@ -10,6 +10,7 @@ use App\Models\Server; use App\Models\Service; use App\Models\Team; use App\Notifications\ScheduledTask\TaskFailed; +use App\Notifications\ScheduledTask\TaskSuccess; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; @@ -111,6 +112,8 @@ class ScheduledTaskJob implements ShouldQueue 'message' => $this->task_output, ]); + $this->team?->notify(new TaskSuccess($this->task, $this->task_output)); + return; } } @@ -125,7 +128,6 @@ class ScheduledTaskJob implements ShouldQueue ]); } $this->team?->notify(new TaskFailed($this->task, $e->getMessage())); - // send_internal_notification('ScheduledTaskJob failed with: ' . $e->getMessage()); throw $e; } finally { ScheduledTaskDone::dispatch($this->team->id); diff --git a/app/Jobs/SendConfirmationForWaitlistJob.php b/app/Jobs/SendConfirmationForWaitlistJob.php deleted file mode 100755 index 7af8205fc..000000000 --- a/app/Jobs/SendConfirmationForWaitlistJob.php +++ /dev/null @@ -1,37 +0,0 @@ -email.'&confirmation_code='.$this->uuid; - $cancel_url = base_url().'/webhooks/waitlist/cancel?email='.$this->email.'&confirmation_code='.$this->uuid; - $mail->view('emails.waitlist-confirmation', - [ - 'confirmation_url' => $confirmation_url, - 'cancel_url' => $cancel_url, - ]); - $mail->subject('You are on the waitlist!'); - send_user_an_email($mail, $this->email); - } catch (\Throwable $e) { - send_internal_notification("SendConfirmationForWaitlistJob failed for {$this->email} with error: ".$e->getMessage()); - throw $e; - } - } -} diff --git a/app/Livewire/Notifications/Discord.php b/app/Livewire/Notifications/Discord.php index 7a177a227..57007813e 100644 --- a/app/Livewire/Notifications/Discord.php +++ b/app/Livewire/Notifications/Discord.php @@ -2,6 +2,7 @@ namespace App\Livewire\Notifications; +use App\Models\DiscordNotificationSettings; use App\Models\Team; use App\Notifications\Test; use Livewire\Attributes\Validate; @@ -11,6 +12,8 @@ class Discord extends Component { public Team $team; + public DiscordNotificationSettings $settings; + #[Validate(['boolean'])] public bool $discordEnabled = false; @@ -18,27 +21,46 @@ class Discord extends Component public ?string $discordWebhookUrl = null; #[Validate(['boolean'])] - public bool $discordNotificationsTest = false; + public bool $deploymentSuccessDiscordNotifications = false; #[Validate(['boolean'])] - public bool $discordNotificationsDeployments = false; + public bool $deploymentFailureDiscordNotifications = true; #[Validate(['boolean'])] - public bool $discordNotificationsStatusChanges = false; + public bool $statusChangeDiscordNotifications = false; #[Validate(['boolean'])] - public bool $discordNotificationsDatabaseBackups = false; + public bool $backupSuccessDiscordNotifications = false; #[Validate(['boolean'])] - public bool $discordNotificationsScheduledTasks = false; + public bool $backupFailureDiscordNotifications = true; #[Validate(['boolean'])] - public bool $discordNotificationsServerDiskUsage = false; + public bool $scheduledTaskSuccessDiscordNotifications = false; + + #[Validate(['boolean'])] + public bool $scheduledTaskFailureDiscordNotifications = true; + + #[Validate(['boolean'])] + public bool $dockerCleanupSuccessDiscordNotifications = false; + + #[Validate(['boolean'])] + public bool $dockerCleanupFailureDiscordNotifications = true; + + #[Validate(['boolean'])] + public bool $serverDiskUsageDiscordNotifications = true; + + #[Validate(['boolean'])] + public bool $serverReachableDiscordNotifications = false; + + #[Validate(['boolean'])] + public bool $serverUnreachableDiscordNotifications = true; public function mount() { try { $this->team = auth()->user()->currentTeam(); + $this->settings = $this->team->discordNotificationSettings; $this->syncData(); } catch (\Throwable $e) { return handleError($e, $this); @@ -49,25 +71,40 @@ class Discord extends Component { if ($toModel) { $this->validate(); - $this->team->discord_enabled = $this->discordEnabled; - $this->team->discord_webhook_url = $this->discordWebhookUrl; - $this->team->discord_notifications_test = $this->discordNotificationsTest; - $this->team->discord_notifications_deployments = $this->discordNotificationsDeployments; - $this->team->discord_notifications_status_changes = $this->discordNotificationsStatusChanges; - $this->team->discord_notifications_database_backups = $this->discordNotificationsDatabaseBackups; - $this->team->discord_notifications_scheduled_tasks = $this->discordNotificationsScheduledTasks; - $this->team->discord_notifications_server_disk_usage = $this->discordNotificationsServerDiskUsage; - $this->team->save(); + $this->settings->discord_enabled = $this->discordEnabled; + $this->settings->discord_webhook_url = $this->discordWebhookUrl; + + $this->settings->deployment_success_discord_notifications = $this->deploymentSuccessDiscordNotifications; + $this->settings->deployment_failure_discord_notifications = $this->deploymentFailureDiscordNotifications; + $this->settings->status_change_discord_notifications = $this->statusChangeDiscordNotifications; + $this->settings->backup_success_discord_notifications = $this->backupSuccessDiscordNotifications; + $this->settings->backup_failure_discord_notifications = $this->backupFailureDiscordNotifications; + $this->settings->scheduled_task_success_discord_notifications = $this->scheduledTaskSuccessDiscordNotifications; + $this->settings->scheduled_task_failure_discord_notifications = $this->scheduledTaskFailureDiscordNotifications; + $this->settings->docker_cleanup_success_discord_notifications = $this->dockerCleanupSuccessDiscordNotifications; + $this->settings->docker_cleanup_failure_discord_notifications = $this->dockerCleanupFailureDiscordNotifications; + $this->settings->server_disk_usage_discord_notifications = $this->serverDiskUsageDiscordNotifications; + $this->settings->server_reachable_discord_notifications = $this->serverReachableDiscordNotifications; + $this->settings->server_unreachable_discord_notifications = $this->serverUnreachableDiscordNotifications; + + $this->settings->save(); refreshSession(); } else { - $this->discordEnabled = $this->team->discord_enabled; - $this->discordWebhookUrl = $this->team->discord_webhook_url; - $this->discordNotificationsTest = $this->team->discord_notifications_test; - $this->discordNotificationsDeployments = $this->team->discord_notifications_deployments; - $this->discordNotificationsStatusChanges = $this->team->discord_notifications_status_changes; - $this->discordNotificationsDatabaseBackups = $this->team->discord_notifications_database_backups; - $this->discordNotificationsScheduledTasks = $this->team->discord_notifications_scheduled_tasks; - $this->discordNotificationsServerDiskUsage = $this->team->discord_notifications_server_disk_usage; + $this->discordEnabled = $this->settings->discord_enabled; + $this->discordWebhookUrl = $this->settings->discord_webhook_url; + + $this->deploymentSuccessDiscordNotifications = $this->settings->deployment_success_discord_notifications; + $this->deploymentFailureDiscordNotifications = $this->settings->deployment_failure_discord_notifications; + $this->statusChangeDiscordNotifications = $this->settings->status_change_discord_notifications; + $this->backupSuccessDiscordNotifications = $this->settings->backup_success_discord_notifications; + $this->backupFailureDiscordNotifications = $this->settings->backup_failure_discord_notifications; + $this->scheduledTaskSuccessDiscordNotifications = $this->settings->scheduled_task_success_discord_notifications; + $this->scheduledTaskFailureDiscordNotifications = $this->settings->scheduled_task_failure_discord_notifications; + $this->dockerCleanupSuccessDiscordNotifications = $this->settings->docker_cleanup_success_discord_notifications; + $this->dockerCleanupFailureDiscordNotifications = $this->settings->docker_cleanup_failure_discord_notifications; + $this->serverDiskUsageDiscordNotifications = $this->settings->server_disk_usage_discord_notifications; + $this->serverReachableDiscordNotifications = $this->settings->server_reachable_discord_notifications; + $this->serverUnreachableDiscordNotifications = $this->settings->server_unreachable_discord_notifications; } } @@ -117,7 +154,7 @@ class Discord extends Component public function sendTestNotification() { try { - $this->team->notify(new Test); + $this->team->notify(new Test(channel: 'discord')); $this->dispatch('success', 'Test notification sent.'); } catch (\Throwable $e) { return handleError($e, $this); diff --git a/app/Livewire/Notifications/Email.php b/app/Livewire/Notifications/Email.php index ab3768643..dc2a95e84 100644 --- a/app/Livewire/Notifications/Email.php +++ b/app/Livewire/Notifications/Email.php @@ -2,6 +2,7 @@ namespace App\Livewire\Notifications; +use App\Models\EmailNotificationSettings; use App\Models\Team; use App\Notifications\Test; use Illuminate\Support\Facades\RateLimiter; @@ -11,17 +12,18 @@ use Livewire\Component; class Email extends Component { + protected $listeners = ['refresh' => '$refresh']; + public Team $team; + public EmailNotificationSettings $settings; + #[Locked] public string $emails; #[Validate(['boolean'])] public bool $smtpEnabled = false; - #[Validate(['boolean'])] - public bool $useInstanceEmailSettings = false; - #[Validate(['nullable', 'email'])] public ?string $smtpFromAddress = null; @@ -34,11 +36,11 @@ class Email extends Component #[Validate(['nullable', 'string'])] public ?string $smtpHost = null; - #[Validate(['nullable', 'numeric'])] + #[Validate(['nullable', 'numeric', 'min:1', 'max:65535'])] public ?int $smtpPort = null; #[Validate(['nullable', 'string', 'in:tls,ssl,none'])] - public ?string $smtpEncryption = null; + public ?string $smtpEncryption = 'tls'; #[Validate(['nullable', 'string'])] public ?string $smtpUsername = null; @@ -50,29 +52,50 @@ class Email extends Component public ?int $smtpTimeout = null; #[Validate(['boolean'])] - public bool $smtpNotificationsTest = false; - - #[Validate(['boolean'])] - public bool $smtpNotificationsDeployments = false; - - #[Validate(['boolean'])] - public bool $smtpNotificationsStatusChanges = false; - - #[Validate(['boolean'])] - public bool $smtpNotificationsDatabaseBackups = false; - - #[Validate(['boolean'])] - public bool $smtpNotificationsScheduledTasks = false; - - #[Validate(['boolean'])] - public bool $smtpNotificationsServerDiskUsage = false; - - #[Validate(['boolean'])] - public bool $resendEnabled; + public bool $resendEnabled = false; #[Validate(['nullable', 'string'])] public ?string $resendApiKey = null; + #[Validate(['boolean'])] + public bool $useInstanceEmailSettings = false; + + #[Validate(['boolean'])] + public bool $deploymentSuccessEmailNotifications = false; + + #[Validate(['boolean'])] + public bool $deploymentFailureEmailNotifications = true; + + #[Validate(['boolean'])] + public bool $statusChangeEmailNotifications = false; + + #[Validate(['boolean'])] + public bool $backupSuccessEmailNotifications = false; + + #[Validate(['boolean'])] + public bool $backupFailureEmailNotifications = true; + + #[Validate(['boolean'])] + public bool $scheduledTaskSuccessEmailNotifications = false; + + #[Validate(['boolean'])] + public bool $scheduledTaskFailureEmailNotifications = true; + + #[Validate(['boolean'])] + public bool $dockerCleanupSuccessEmailNotifications = false; + + #[Validate(['boolean'])] + public bool $dockerCleanupFailureEmailNotifications = true; + + #[Validate(['boolean'])] + public bool $serverDiskUsageEmailNotifications = true; + + #[Validate(['boolean'])] + public bool $serverReachableEmailNotifications = false; + + #[Validate(['boolean'])] + public bool $serverUnreachableEmailNotifications = true; + #[Validate(['nullable', 'email'])] public ?string $testEmailAddress = null; @@ -81,7 +104,9 @@ class Email extends Component try { $this->team = auth()->user()->currentTeam(); $this->emails = auth()->user()->email; + $this->settings = $this->team->emailNotificationSettings; $this->syncData(); + $this->testEmailAddress = auth()->user()->email; } catch (\Throwable $e) { return handleError($e, $this); } @@ -91,47 +116,191 @@ class Email extends Component { if ($toModel) { $this->validate(); - $this->team->smtp_enabled = $this->smtpEnabled; - $this->team->smtp_from_address = $this->smtpFromAddress; - $this->team->smtp_from_name = $this->smtpFromName; - $this->team->smtp_host = $this->smtpHost; - $this->team->smtp_port = $this->smtpPort; - $this->team->smtp_encryption = $this->smtpEncryption; - $this->team->smtp_username = $this->smtpUsername; - $this->team->smtp_password = $this->smtpPassword; - $this->team->smtp_timeout = $this->smtpTimeout; - $this->team->smtp_recipients = $this->smtpRecipients; - $this->team->smtp_notifications_test = $this->smtpNotificationsTest; - $this->team->smtp_notifications_deployments = $this->smtpNotificationsDeployments; - $this->team->smtp_notifications_status_changes = $this->smtpNotificationsStatusChanges; - $this->team->smtp_notifications_database_backups = $this->smtpNotificationsDatabaseBackups; - $this->team->smtp_notifications_scheduled_tasks = $this->smtpNotificationsScheduledTasks; - $this->team->smtp_notifications_server_disk_usage = $this->smtpNotificationsServerDiskUsage; - $this->team->use_instance_email_settings = $this->useInstanceEmailSettings; - $this->team->resend_enabled = $this->resendEnabled; - $this->team->resend_api_key = $this->resendApiKey; - $this->team->save(); - refreshSession(); + $this->settings->smtp_enabled = $this->smtpEnabled; + $this->settings->smtp_from_address = $this->smtpFromAddress; + $this->settings->smtp_from_name = $this->smtpFromName; + $this->settings->smtp_recipients = $this->smtpRecipients; + $this->settings->smtp_host = $this->smtpHost; + $this->settings->smtp_port = $this->smtpPort; + $this->settings->smtp_encryption = $this->smtpEncryption; + $this->settings->smtp_username = $this->smtpUsername; + $this->settings->smtp_password = $this->smtpPassword; + $this->settings->smtp_timeout = $this->smtpTimeout; + + $this->settings->resend_enabled = $this->resendEnabled; + $this->settings->resend_api_key = $this->resendApiKey; + + $this->settings->use_instance_email_settings = $this->useInstanceEmailSettings; + + $this->settings->deployment_success_email_notifications = $this->deploymentSuccessEmailNotifications; + $this->settings->deployment_failure_email_notifications = $this->deploymentFailureEmailNotifications; + $this->settings->status_change_email_notifications = $this->statusChangeEmailNotifications; + $this->settings->backup_success_email_notifications = $this->backupSuccessEmailNotifications; + $this->settings->backup_failure_email_notifications = $this->backupFailureEmailNotifications; + $this->settings->scheduled_task_success_email_notifications = $this->scheduledTaskSuccessEmailNotifications; + $this->settings->scheduled_task_failure_email_notifications = $this->scheduledTaskFailureEmailNotifications; + $this->settings->docker_cleanup_success_email_notifications = $this->dockerCleanupSuccessEmailNotifications; + $this->settings->docker_cleanup_failure_email_notifications = $this->dockerCleanupFailureEmailNotifications; + $this->settings->server_disk_usage_email_notifications = $this->serverDiskUsageEmailNotifications; + $this->settings->server_reachable_email_notifications = $this->serverReachableEmailNotifications; + $this->settings->server_unreachable_email_notifications = $this->serverUnreachableEmailNotifications; + $this->settings->save(); + } else { - $this->smtpEnabled = $this->team->smtp_enabled; - $this->smtpFromAddress = $this->team->smtp_from_address; - $this->smtpFromName = $this->team->smtp_from_name; - $this->smtpHost = $this->team->smtp_host; - $this->smtpPort = $this->team->smtp_port; - $this->smtpEncryption = $this->team->smtp_encryption; - $this->smtpUsername = $this->team->smtp_username; - $this->smtpPassword = $this->team->smtp_password; - $this->smtpTimeout = $this->team->smtp_timeout; - $this->smtpRecipients = $this->team->smtp_recipients; - $this->smtpNotificationsTest = $this->team->smtp_notifications_test; - $this->smtpNotificationsDeployments = $this->team->smtp_notifications_deployments; - $this->smtpNotificationsStatusChanges = $this->team->smtp_notifications_status_changes; - $this->smtpNotificationsDatabaseBackups = $this->team->smtp_notifications_database_backups; - $this->smtpNotificationsScheduledTasks = $this->team->smtp_notifications_scheduled_tasks; - $this->smtpNotificationsServerDiskUsage = $this->team->smtp_notifications_server_disk_usage; - $this->useInstanceEmailSettings = $this->team->use_instance_email_settings; - $this->resendEnabled = $this->team->resend_enabled; - $this->resendApiKey = $this->team->resend_api_key; + $this->smtpEnabled = $this->settings->smtp_enabled; + $this->smtpFromAddress = $this->settings->smtp_from_address; + $this->smtpFromName = $this->settings->smtp_from_name; + $this->smtpRecipients = $this->settings->smtp_recipients; + $this->smtpHost = $this->settings->smtp_host; + $this->smtpPort = $this->settings->smtp_port; + $this->smtpEncryption = $this->settings->smtp_encryption; + $this->smtpUsername = $this->settings->smtp_username; + $this->smtpPassword = $this->settings->smtp_password; + $this->smtpTimeout = $this->settings->smtp_timeout; + + $this->resendEnabled = $this->settings->resend_enabled; + $this->resendApiKey = $this->settings->resend_api_key; + + $this->useInstanceEmailSettings = $this->settings->use_instance_email_settings; + + $this->deploymentSuccessEmailNotifications = $this->settings->deployment_success_email_notifications; + $this->deploymentFailureEmailNotifications = $this->settings->deployment_failure_email_notifications; + $this->statusChangeEmailNotifications = $this->settings->status_change_email_notifications; + $this->backupSuccessEmailNotifications = $this->settings->backup_success_email_notifications; + $this->backupFailureEmailNotifications = $this->settings->backup_failure_email_notifications; + $this->scheduledTaskSuccessEmailNotifications = $this->settings->scheduled_task_success_email_notifications; + $this->scheduledTaskFailureEmailNotifications = $this->settings->scheduled_task_failure_email_notifications; + $this->dockerCleanupSuccessEmailNotifications = $this->settings->docker_cleanup_success_email_notifications; + $this->dockerCleanupFailureEmailNotifications = $this->settings->docker_cleanup_failure_email_notifications; + $this->serverDiskUsageEmailNotifications = $this->settings->server_disk_usage_email_notifications; + $this->serverReachableEmailNotifications = $this->settings->server_reachable_email_notifications; + $this->serverUnreachableEmailNotifications = $this->settings->server_unreachable_email_notifications; + } + } + + public function submit() + { + try { + $this->resetErrorBag(); + $this->saveModel(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function saveModel() + { + $this->syncData(true); + $this->dispatch('success', 'Email notifications settings updated.'); + } + + public function instantSave(?string $type = null) + { + try { + $this->resetErrorBag(); + + if ($type === 'SMTP') { + $this->submitSmtp(); + } elseif ($type === 'Resend') { + $this->submitResend(); + } else { + $this->smtpEnabled = false; + $this->resendEnabled = false; + $this->saveModel(); + + return; + } + } catch (\Throwable $e) { + if ($type === 'SMTP') { + $this->smtpEnabled = false; + } elseif ($type === 'Resend') { + $this->resendEnabled = false; + } + + return handleError($e, $this); + } finally { + $this->dispatch('refresh'); + } + } + + public function submitSmtp() + { + try { + $this->resetErrorBag(); + $this->validate([ + 'smtpEnabled' => 'boolean', + 'smtpFromAddress' => 'required|email', + 'smtpFromName' => 'required|string', + 'smtpHost' => 'required|string', + 'smtpPort' => 'required|numeric', + 'smtpEncryption' => 'required|string|in:tls,ssl,none', + 'smtpUsername' => 'nullable|string', + 'smtpPassword' => 'nullable|string', + 'smtpTimeout' => 'nullable|numeric', + ], [ + 'smtpFromAddress.required' => 'From Address is required.', + 'smtpFromAddress.email' => 'Please enter a valid email address.', + 'smtpFromName.required' => 'From Name is required.', + 'smtpHost.required' => 'SMTP Host is required.', + 'smtpPort.required' => 'SMTP Port is required.', + 'smtpPort.numeric' => 'SMTP Port must be a number.', + 'smtpEncryption.required' => 'Encryption type is required.', + ]); + + $this->settings->resend_enabled = false; + $this->settings->use_instance_email_settings = false; + $this->resendEnabled = false; + $this->useInstanceEmailSettings = false; + + $this->settings->smtp_enabled = $this->smtpEnabled; + $this->settings->smtp_from_address = $this->smtpFromAddress; + $this->settings->smtp_from_name = $this->smtpFromName; + $this->settings->smtp_host = $this->smtpHost; + $this->settings->smtp_port = $this->smtpPort; + $this->settings->smtp_encryption = $this->smtpEncryption; + $this->settings->smtp_username = $this->smtpUsername; + $this->settings->smtp_password = $this->smtpPassword; + $this->settings->smtp_timeout = $this->smtpTimeout; + + $this->settings->save(); + $this->dispatch('success', 'SMTP settings updated.'); + } catch (\Throwable $e) { + $this->smtpEnabled = false; + + return handleError($e); + } + } + + public function submitResend() + { + try { + $this->resetErrorBag(); + $this->validate([ + 'resendEnabled' => 'boolean', + 'resendApiKey' => 'required|string', + 'smtpFromAddress' => 'required|email', + 'smtpFromName' => 'required|string', + ], [ + 'resendApiKey.required' => 'Resend API Key is required.', + 'smtpFromAddress.required' => 'From Address is required.', + 'smtpFromAddress.email' => 'Please enter a valid email address.', + 'smtpFromName.required' => 'From Name is required.', + ]); + + $this->settings->smtp_enabled = false; + $this->settings->use_instance_email_settings = false; + $this->smtpEnabled = false; + $this->useInstanceEmailSettings = false; + + $this->settings->resend_enabled = $this->resendEnabled; + $this->settings->resend_api_key = $this->resendApiKey; + $this->settings->smtp_from_address = $this->smtpFromAddress; + $this->settings->smtp_from_name = $this->smtpFromName; + + $this->settings->save(); + $this->dispatch('success', 'Resend settings updated.'); + } catch (\Throwable $e) { + return handleError($e, $this); } } @@ -149,7 +318,7 @@ class Email extends Component 'test-email:'.$this->team->id, $perMinute = 0, function () { - $this->team?->notify(new Test($this->testEmailAddress)); + $this->team?->notify(new Test($this->testEmailAddress, 'email')); $this->dispatch('success', 'Test Email sent.'); }, $decaySeconds = 10, @@ -163,70 +332,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() { $settings = instanceSettings(); diff --git a/app/Livewire/Notifications/Slack.php b/app/Livewire/Notifications/Slack.php index 06b7643ea..97464fa1c 100644 --- a/app/Livewire/Notifications/Slack.php +++ b/app/Livewire/Notifications/Slack.php @@ -2,6 +2,7 @@ namespace App\Livewire\Notifications; +use App\Models\SlackNotificationSettings; use App\Models\Team; use App\Notifications\Test; use Livewire\Attributes\Validate; @@ -11,6 +12,8 @@ class Slack extends Component { public Team $team; + public SlackNotificationSettings $settings; + #[Validate(['boolean'])] public bool $slackEnabled = false; @@ -18,27 +21,46 @@ class Slack extends Component public ?string $slackWebhookUrl = null; #[Validate(['boolean'])] - public bool $slackNotificationsTest = false; + public bool $deploymentSuccessSlackNotifications = false; #[Validate(['boolean'])] - public bool $slackNotificationsDeployments = false; + public bool $deploymentFailureSlackNotifications = true; #[Validate(['boolean'])] - public bool $slackNotificationsStatusChanges = false; + public bool $statusChangeSlackNotifications = false; #[Validate(['boolean'])] - public bool $slackNotificationsDatabaseBackups = false; + public bool $backupSuccessSlackNotifications = false; #[Validate(['boolean'])] - public bool $slackNotificationsScheduledTasks = false; + public bool $backupFailureSlackNotifications = true; #[Validate(['boolean'])] - public bool $slackNotificationsServerDiskUsage = false; + public bool $scheduledTaskSuccessSlackNotifications = false; + + #[Validate(['boolean'])] + public bool $scheduledTaskFailureSlackNotifications = true; + + #[Validate(['boolean'])] + public bool $dockerCleanupSuccessSlackNotifications = false; + + #[Validate(['boolean'])] + public bool $dockerCleanupFailureSlackNotifications = true; + + #[Validate(['boolean'])] + public bool $serverDiskUsageSlackNotifications = true; + + #[Validate(['boolean'])] + public bool $serverReachableSlackNotifications = false; + + #[Validate(['boolean'])] + public bool $serverUnreachableSlackNotifications = true; public function mount() { try { $this->team = auth()->user()->currentTeam(); + $this->settings = $this->team->slackNotificationSettings; $this->syncData(); } catch (\Throwable $e) { return handleError($e, $this); @@ -49,25 +71,40 @@ class Slack extends Component { if ($toModel) { $this->validate(); - $this->team->slack_enabled = $this->slackEnabled; - $this->team->slack_webhook_url = $this->slackWebhookUrl; - $this->team->slack_notifications_test = $this->slackNotificationsTest; - $this->team->slack_notifications_deployments = $this->slackNotificationsDeployments; - $this->team->slack_notifications_status_changes = $this->slackNotificationsStatusChanges; - $this->team->slack_notifications_database_backups = $this->slackNotificationsDatabaseBackups; - $this->team->slack_notifications_scheduled_tasks = $this->slackNotificationsScheduledTasks; - $this->team->slack_notifications_server_disk_usage = $this->slackNotificationsServerDiskUsage; - $this->team->save(); + $this->settings->slack_enabled = $this->slackEnabled; + $this->settings->slack_webhook_url = $this->slackWebhookUrl; + + $this->settings->deployment_success_slack_notifications = $this->deploymentSuccessSlackNotifications; + $this->settings->deployment_failure_slack_notifications = $this->deploymentFailureSlackNotifications; + $this->settings->status_change_slack_notifications = $this->statusChangeSlackNotifications; + $this->settings->backup_success_slack_notifications = $this->backupSuccessSlackNotifications; + $this->settings->backup_failure_slack_notifications = $this->backupFailureSlackNotifications; + $this->settings->scheduled_task_success_slack_notifications = $this->scheduledTaskSuccessSlackNotifications; + $this->settings->scheduled_task_failure_slack_notifications = $this->scheduledTaskFailureSlackNotifications; + $this->settings->docker_cleanup_success_slack_notifications = $this->dockerCleanupSuccessSlackNotifications; + $this->settings->docker_cleanup_failure_slack_notifications = $this->dockerCleanupFailureSlackNotifications; + $this->settings->server_disk_usage_slack_notifications = $this->serverDiskUsageSlackNotifications; + $this->settings->server_reachable_slack_notifications = $this->serverReachableSlackNotifications; + $this->settings->server_unreachable_slack_notifications = $this->serverUnreachableSlackNotifications; + + $this->settings->save(); refreshSession(); } else { - $this->slackEnabled = $this->team->slack_enabled; - $this->slackWebhookUrl = $this->team->slack_webhook_url; - $this->slackNotificationsTest = $this->team->slack_notifications_test; - $this->slackNotificationsDeployments = $this->team->slack_notifications_deployments; - $this->slackNotificationsStatusChanges = $this->team->slack_notifications_status_changes; - $this->slackNotificationsDatabaseBackups = $this->team->slack_notifications_database_backups; - $this->slackNotificationsScheduledTasks = $this->team->slack_notifications_scheduled_tasks; - $this->slackNotificationsServerDiskUsage = $this->team->slack_notifications_server_disk_usage; + $this->slackEnabled = $this->settings->slack_enabled; + $this->slackWebhookUrl = $this->settings->slack_webhook_url; + + $this->deploymentSuccessSlackNotifications = $this->settings->deployment_success_slack_notifications; + $this->deploymentFailureSlackNotifications = $this->settings->deployment_failure_slack_notifications; + $this->statusChangeSlackNotifications = $this->settings->status_change_slack_notifications; + $this->backupSuccessSlackNotifications = $this->settings->backup_success_slack_notifications; + $this->backupFailureSlackNotifications = $this->settings->backup_failure_slack_notifications; + $this->scheduledTaskSuccessSlackNotifications = $this->settings->scheduled_task_success_slack_notifications; + $this->scheduledTaskFailureSlackNotifications = $this->settings->scheduled_task_failure_slack_notifications; + $this->dockerCleanupSuccessSlackNotifications = $this->settings->docker_cleanup_success_slack_notifications; + $this->dockerCleanupFailureSlackNotifications = $this->settings->docker_cleanup_failure_slack_notifications; + $this->serverDiskUsageSlackNotifications = $this->settings->server_disk_usage_slack_notifications; + $this->serverReachableSlackNotifications = $this->settings->server_reachable_slack_notifications; + $this->serverUnreachableSlackNotifications = $this->settings->server_unreachable_slack_notifications; } } @@ -117,7 +154,7 @@ class Slack extends Component public function sendTestNotification() { try { - $this->team->notify(new Test); + $this->team->notify(new Test(channel: 'slack')); $this->dispatch('success', 'Test notification sent.'); } catch (\Throwable $e) { return handleError($e, $this); diff --git a/app/Livewire/Notifications/Telegram.php b/app/Livewire/Notifications/Telegram.php index 15ec20577..de2fa9cdc 100644 --- a/app/Livewire/Notifications/Telegram.php +++ b/app/Livewire/Notifications/Telegram.php @@ -3,6 +3,7 @@ namespace App\Livewire\Notifications; use App\Models\Team; +use App\Models\TelegramNotificationSettings; use App\Notifications\Test; use Livewire\Attributes\Validate; use Livewire\Component; @@ -11,6 +12,8 @@ class Telegram extends Component { public Team $team; + public TelegramNotificationSettings $settings; + #[Validate(['boolean'])] public bool $telegramEnabled = false; @@ -21,42 +24,82 @@ class Telegram extends Component public ?string $telegramChatId = null; #[Validate(['boolean'])] - public bool $telegramNotificationsTest = false; + public bool $deploymentSuccessTelegramNotifications = false; #[Validate(['boolean'])] - public bool $telegramNotificationsDeployments = false; + public bool $deploymentFailureTelegramNotifications = true; #[Validate(['boolean'])] - public bool $telegramNotificationsStatusChanges = false; + public bool $statusChangeTelegramNotifications = false; #[Validate(['boolean'])] - public bool $telegramNotificationsDatabaseBackups = false; + public bool $backupSuccessTelegramNotifications = false; #[Validate(['boolean'])] - public bool $telegramNotificationsScheduledTasks = false; - - #[Validate(['nullable', 'string'])] - public ?string $telegramNotificationsTestMessageThreadId = null; - - #[Validate(['nullable', 'string'])] - public ?string $telegramNotificationsDeploymentsMessageThreadId = null; - - #[Validate(['nullable', 'string'])] - public ?string $telegramNotificationsStatusChangesMessageThreadId = null; - - #[Validate(['nullable', 'string'])] - public ?string $telegramNotificationsDatabaseBackupsMessageThreadId = null; - - #[Validate(['nullable', 'string'])] - public ?string $telegramNotificationsScheduledTasksThreadId = null; + public bool $backupFailureTelegramNotifications = true; #[Validate(['boolean'])] - public bool $telegramNotificationsServerDiskUsage = false; + public bool $scheduledTaskSuccessTelegramNotifications = false; + + #[Validate(['boolean'])] + public bool $scheduledTaskFailureTelegramNotifications = true; + + #[Validate(['boolean'])] + public bool $dockerCleanupSuccessTelegramNotifications = false; + + #[Validate(['boolean'])] + public bool $dockerCleanupFailureTelegramNotifications = true; + + #[Validate(['boolean'])] + public bool $serverDiskUsageTelegramNotifications = true; + + #[Validate(['boolean'])] + public bool $serverReachableTelegramNotifications = false; + + #[Validate(['boolean'])] + public bool $serverUnreachableTelegramNotifications = true; + + #[Validate(['nullable', 'string'])] + public ?string $telegramNotificationsDeploymentSuccessTopicId = null; + + #[Validate(['nullable', 'string'])] + public ?string $telegramNotificationsDeploymentFailureTopicId = null; + + #[Validate(['nullable', 'string'])] + public ?string $telegramNotificationsStatusChangeTopicId = null; + + #[Validate(['nullable', 'string'])] + public ?string $telegramNotificationsBackupSuccessTopicId = null; + + #[Validate(['nullable', 'string'])] + public ?string $telegramNotificationsBackupFailureTopicId = null; + + #[Validate(['nullable', 'string'])] + public ?string $telegramNotificationsScheduledTaskSuccessTopicId = null; + + #[Validate(['nullable', 'string'])] + public ?string $telegramNotificationsScheduledTaskFailureTopicId = null; + + #[Validate(['nullable', 'string'])] + public ?string $telegramNotificationsDockerCleanupSuccessTopicId = null; + + #[Validate(['nullable', 'string'])] + public ?string $telegramNotificationsDockerCleanupFailureTopicId = null; + + #[Validate(['nullable', 'string'])] + public ?string $telegramNotificationsServerDiskUsageTopicId = null; + + #[Validate(['nullable', 'string'])] + public ?string $telegramNotificationsServerReachableTopicId = null; + + #[Validate(['nullable', 'string'])] + public ?string $telegramNotificationsServerUnreachableTopicId = null; public function mount() { try { $this->team = auth()->user()->currentTeam(); + $this->settings = $this->team->telegramNotificationSettings; $this->syncData(); } catch (\Throwable $e) { return handleError($e, $this); @@ -67,39 +110,69 @@ class Telegram extends Component { if ($toModel) { $this->validate(); - $this->team->telegram_enabled = $this->telegramEnabled; - $this->team->telegram_token = $this->telegramToken; - $this->team->telegram_chat_id = $this->telegramChatId; - $this->team->telegram_notifications_test = $this->telegramNotificationsTest; - $this->team->telegram_notifications_deployments = $this->telegramNotificationsDeployments; - $this->team->telegram_notifications_status_changes = $this->telegramNotificationsStatusChanges; - $this->team->telegram_notifications_database_backups = $this->telegramNotificationsDatabaseBackups; - $this->team->telegram_notifications_scheduled_tasks = $this->telegramNotificationsScheduledTasks; - $this->team->telegram_notifications_test_message_thread_id = $this->telegramNotificationsTestMessageThreadId; - $this->team->telegram_notifications_deployments_message_thread_id = $this->telegramNotificationsDeploymentsMessageThreadId; - $this->team->telegram_notifications_status_changes_message_thread_id = $this->telegramNotificationsStatusChangesMessageThreadId; - $this->team->telegram_notifications_database_backups_message_thread_id = $this->telegramNotificationsDatabaseBackupsMessageThreadId; - $this->team->telegram_notifications_scheduled_tasks_thread_id = $this->telegramNotificationsScheduledTasksThreadId; - $this->team->telegram_notifications_server_disk_usage = $this->telegramNotificationsServerDiskUsage; - $this->team->save(); + $this->settings->telegram_enabled = $this->telegramEnabled; + $this->settings->telegram_token = $this->telegramToken; + $this->settings->telegram_chat_id = $this->telegramChatId; + + $this->settings->deployment_success_telegram_notifications = $this->deploymentSuccessTelegramNotifications; + $this->settings->deployment_failure_telegram_notifications = $this->deploymentFailureTelegramNotifications; + $this->settings->status_change_telegram_notifications = $this->statusChangeTelegramNotifications; + $this->settings->backup_success_telegram_notifications = $this->backupSuccessTelegramNotifications; + $this->settings->backup_failure_telegram_notifications = $this->backupFailureTelegramNotifications; + $this->settings->scheduled_task_success_telegram_notifications = $this->scheduledTaskSuccessTelegramNotifications; + $this->settings->scheduled_task_failure_telegram_notifications = $this->scheduledTaskFailureTelegramNotifications; + $this->settings->docker_cleanup_success_telegram_notifications = $this->dockerCleanupSuccessTelegramNotifications; + $this->settings->docker_cleanup_failure_telegram_notifications = $this->dockerCleanupFailureTelegramNotifications; + $this->settings->server_disk_usage_telegram_notifications = $this->serverDiskUsageTelegramNotifications; + $this->settings->server_reachable_telegram_notifications = $this->serverReachableTelegramNotifications; + $this->settings->server_unreachable_telegram_notifications = $this->serverUnreachableTelegramNotifications; + + $this->settings->telegram_notifications_deployment_success_topic_id = $this->telegramNotificationsDeploymentSuccessTopicId; + $this->settings->telegram_notifications_deployment_failure_topic_id = $this->telegramNotificationsDeploymentFailureTopicId; + $this->settings->telegram_notifications_status_change_topic_id = $this->telegramNotificationsStatusChangeTopicId; + $this->settings->telegram_notifications_backup_success_topic_id = $this->telegramNotificationsBackupSuccessTopicId; + $this->settings->telegram_notifications_backup_failure_topic_id = $this->telegramNotificationsBackupFailureTopicId; + $this->settings->telegram_notifications_scheduled_task_success_topic_id = $this->telegramNotificationsScheduledTaskSuccessTopicId; + $this->settings->telegram_notifications_scheduled_task_failure_topic_id = $this->telegramNotificationsScheduledTaskFailureTopicId; + $this->settings->telegram_notifications_docker_cleanup_success_topic_id = $this->telegramNotificationsDockerCleanupSuccessTopicId; + $this->settings->telegram_notifications_docker_cleanup_failure_topic_id = $this->telegramNotificationsDockerCleanupFailureTopicId; + $this->settings->telegram_notifications_server_disk_usage_topic_id = $this->telegramNotificationsServerDiskUsageTopicId; + $this->settings->telegram_notifications_server_reachable_topic_id = $this->telegramNotificationsServerReachableTopicId; + $this->settings->telegram_notifications_server_unreachable_topic_id = $this->telegramNotificationsServerUnreachableTopicId; + + $this->settings->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->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->telegramNotificationsDeploymentSuccessTopicId = $this->settings->telegram_notifications_deployment_success_topic_id; + $this->telegramNotificationsDeploymentFailureTopicId = $this->settings->telegram_notifications_deployment_failure_topic_id; + $this->telegramNotificationsStatusChangeTopicId = $this->settings->telegram_notifications_status_change_topic_id; + $this->telegramNotificationsBackupSuccessTopicId = $this->settings->telegram_notifications_backup_success_topic_id; + $this->telegramNotificationsBackupFailureTopicId = $this->settings->telegram_notifications_backup_failure_topic_id; + $this->telegramNotificationsScheduledTaskSuccessTopicId = $this->settings->telegram_notifications_scheduled_task_success_topic_id; + $this->telegramNotificationsScheduledTaskFailureTopicId = $this->settings->telegram_notifications_scheduled_task_failure_topic_id; + $this->telegramNotificationsDockerCleanupSuccessTopicId = $this->settings->telegram_notifications_docker_cleanup_success_topic_id; + $this->telegramNotificationsDockerCleanupFailureTopicId = $this->settings->telegram_notifications_docker_cleanup_failure_topic_id; + $this->telegramNotificationsServerDiskUsageTopicId = $this->settings->telegram_notifications_server_disk_usage_topic_id; + $this->telegramNotificationsServerReachableTopicId = $this->settings->telegram_notifications_server_reachable_topic_id; + $this->telegramNotificationsServerUnreachableTopicId = $this->settings->telegram_notifications_server_unreachable_topic_id; + } } public function instantSave() @@ -150,7 +223,7 @@ class Telegram extends Component public function sendTestNotification() { try { - $this->team->notify(new Test); + $this->team->notify(new Test(channel: 'telegram')); $this->dispatch('success', 'Test notification sent.'); } catch (\Throwable $e) { return handleError($e, $this); diff --git a/app/Livewire/Settings/Index.php b/app/Livewire/Settings/Index.php index c1be35ced..7adb0f8a7 100644 --- a/app/Livewire/Settings/Index.php +++ b/app/Livewire/Settings/Index.php @@ -23,9 +23,6 @@ class Index extends Component #[Validate('nullable|string|max:255')] public ?string $fqdn = null; - #[Validate('nullable|string|max:255')] - public ?string $resale_license = null; - #[Validate('required|integer|min:1025|max:65535')] public int $public_port_min; @@ -83,7 +80,6 @@ class Index extends Component } else { $this->settings = instanceSettings(); $this->fqdn = $this->settings->fqdn; - $this->resale_license = $this->settings->resale_license; $this->public_port_min = $this->settings->public_port_min; $this->public_port_max = $this->settings->public_port_max; $this->custom_dns_servers = $this->settings->custom_dns_servers; @@ -122,7 +118,6 @@ class Index extends Component } $this->settings->fqdn = $this->fqdn; - $this->settings->resale_license = $this->resale_license; $this->settings->public_port_min = $this->public_port_min; $this->settings->public_port_max = $this->public_port_max; $this->settings->custom_dns_servers = $this->custom_dns_servers; diff --git a/app/Livewire/SettingsEmail.php b/app/Livewire/SettingsEmail.php index abf3a12f9..1c5edb108 100644 --- a/app/Livewire/SettingsEmail.php +++ b/app/Livewire/SettingsEmail.php @@ -3,6 +3,10 @@ namespace App\Livewire; use App\Models\InstanceSettings; +use App\Models\Team; +use App\Notifications\Test; +use Illuminate\Support\Facades\RateLimiter; +use Livewire\Attributes\Locked; use Livewire\Attributes\Validate; use Livewire\Component; @@ -10,9 +14,21 @@ class SettingsEmail extends Component { public InstanceSettings $settings; + #[Locked] + public Team $team; + #[Validate(['boolean'])] public bool $smtpEnabled = false; + #[Validate(['nullable', 'email'])] + public ?string $smtpFromAddress = null; + + #[Validate(['nullable', 'string'])] + public ?string $smtpFromName = null; + + #[Validate(['nullable', 'string'])] + public ?string $smtpRecipients = null; + #[Validate(['nullable', 'string'])] public ?string $smtpHost = null; @@ -20,29 +36,26 @@ class SettingsEmail extends Component public ?int $smtpPort = null; #[Validate(['nullable', 'string', 'in:tls,ssl,none'])] - public ?string $smtpEncryption = null; + public ?string $smtpEncryption = 'tls'; #[Validate(['nullable', 'string'])] public ?string $smtpUsername = null; - #[Validate(['nullable'])] + #[Validate(['nullable', 'string'])] public ?string $smtpPassword = null; #[Validate(['nullable', 'numeric'])] public ?int $smtpTimeout = null; - #[Validate(['nullable', 'email'])] - public ?string $smtpFromAddress = null; - - #[Validate(['nullable', 'string'])] - public ?string $smtpFromName = null; - #[Validate(['boolean'])] public bool $resendEnabled = false; #[Validate(['nullable', 'string'])] public ?string $resendApiKey = null; + #[Validate(['nullable', 'email'])] + public ?string $testEmailAddress = null; + public function mount() { if (isInstanceAdmin() === false) { @@ -50,6 +63,8 @@ class SettingsEmail extends Component } $this->settings = instanceSettings(); $this->syncData(); + $this->team = auth()->user()->currentTeam(); + $this->testEmailAddress = auth()->user()->email; } public function syncData(bool $toModel = false) @@ -90,7 +105,7 @@ class SettingsEmail extends Component try { $this->resetErrorBag(); $this->syncData(true); - $this->dispatch('success', 'Settings saved.'); + $this->dispatch('success', 'Transactional email settings updated.'); } catch (\Throwable $e) { return handleError($e, $this); } @@ -99,19 +114,129 @@ class SettingsEmail extends Component public function instantSave(string $type) { try { + $this->resetErrorBag(); + if ($type === 'SMTP') { - $this->resendEnabled = false; - } else { - $this->smtpEnabled = false; - } - $this->syncData(true); - if ($this->smtpEnabled || $this->resendEnabled) { - $this->dispatch('success', "{$type} enabled."); - } else { - $this->dispatch('success', "{$type} disabled."); + $this->submitSmtp(); + } elseif ($type === 'Resend') { + $this->submitResend(); } + } catch (\Throwable $e) { + if ($type === 'SMTP') { + $this->smtpEnabled = false; + } elseif ($type === 'Resend') { + $this->resendEnabled = false; + } + return handleError($e, $this); } } + + public function submitSmtp() + { + try { + $this->validate([ + 'smtpEnabled' => 'boolean', + 'smtpFromAddress' => 'required|email', + 'smtpFromName' => 'required|string', + 'smtpHost' => 'required|string', + 'smtpPort' => 'required|numeric', + 'smtpEncryption' => 'required|string|in:tls,ssl,none', + 'smtpUsername' => 'nullable|string', + 'smtpPassword' => 'nullable|string', + 'smtpTimeout' => 'nullable|numeric', + ], [ + 'smtpFromAddress.required' => 'From Address is required.', + 'smtpFromAddress.email' => 'Please enter a valid email address.', + 'smtpFromName.required' => 'From Name is required.', + 'smtpHost.required' => 'SMTP Host is required.', + 'smtpPort.required' => 'SMTP Port is required.', + 'smtpPort.numeric' => 'SMTP Port must be a number.', + 'smtpEncryption.required' => 'Encryption type is required.', + ]); + + $this->resendEnabled = false; + $this->settings->resend_enabled = false; + + $this->settings->smtp_enabled = $this->smtpEnabled; + $this->settings->smtp_host = $this->smtpHost; + $this->settings->smtp_port = $this->smtpPort; + $this->settings->smtp_encryption = $this->smtpEncryption; + $this->settings->smtp_username = $this->smtpUsername; + $this->settings->smtp_password = $this->smtpPassword; + $this->settings->smtp_timeout = $this->smtpTimeout; + $this->settings->smtp_from_address = $this->smtpFromAddress; + $this->settings->smtp_from_name = $this->smtpFromName; + + $this->settings->save(); + + $this->dispatch('success', 'SMTP settings updated.'); + } catch (\Throwable $e) { + $this->smtpEnabled = false; + + return handleError($e); + } + } + + public function submitResend() + { + try { + $this->validate([ + 'resendEnabled' => 'boolean', + 'resendApiKey' => 'required|string', + 'smtpFromAddress' => 'required|email', + 'smtpFromName' => 'required|string', + ], [ + 'resendApiKey.required' => 'Resend API Key is required.', + 'smtpFromAddress.required' => 'From Address is required.', + 'smtpFromAddress.email' => 'Please enter a valid email address.', + 'smtpFromName.required' => 'From Name is required.', + ]); + + $this->smtpEnabled = false; + $this->settings->smtp_enabled = false; + + $this->settings->resend_enabled = $this->resendEnabled; + $this->settings->resend_api_key = $this->resendApiKey; + $this->settings->smtp_from_address = $this->smtpFromAddress; + $this->settings->smtp_from_name = $this->smtpFromName; + + $this->settings->save(); + + $this->dispatch('success', 'Resend settings updated.'); + } catch (\Throwable $e) { + $this->resendEnabled = false; + + return handleError($e); + } + } + + public function sendTestEmail() + { + try { + $this->validate([ + 'testEmailAddress' => 'required|email', + ], [ + 'testEmailAddress.required' => 'Test email address is required.', + 'testEmailAddress.email' => 'Please enter a valid email address.', + ]); + + $executed = RateLimiter::attempt( + 'test-email:'.$this->team->id, + $perMinute = 0, + function () { + $this->team?->notify(new Test($this->testEmailAddress, 'email')); + $this->dispatch('success', 'Test Email sent.'); + }, + $decaySeconds = 10, + ); + + if (! $executed) { + throw new \Exception('Too many messages sent!'); + } + } catch (\Throwable $e) { + return handleError($e); + } + } } diff --git a/app/Livewire/Waitlist/Index.php b/app/Livewire/Waitlist/Index.php deleted file mode 100644 index 0524b495c..000000000 --- a/app/Livewire/Waitlist/Index.php +++ /dev/null @@ -1,70 +0,0 @@ - 'required|email', - ]; - - public function render() - { - return view('livewire.waitlist.index')->layout('layouts.simple'); - } - - public function mount() - { - if (config('constants.waitlist.enabled') == false) { - return redirect()->route('register'); - } - $this->waitingInLine = Waitlist::whereVerified(true)->count(); - $this->users = User::count(); - if (isDev()) { - $this->email = 'waitlist@example.com'; - } - } - - public function submit() - { - $this->validate(); - try { - $already_registered = User::whereEmail($this->email)->first(); - if ($already_registered) { - throw new \Exception('You are already on the waitlist or registered.
Please check your email to verify your email address or contact support.'); - } - $found = Waitlist::where('email', $this->email)->first(); - if ($found) { - if (! $found->verified) { - $this->dispatch('error', 'You are already on the waitlist.
Please check your email to verify your email address.'); - - return; - } - $this->dispatch('error', 'You are already on the waitlist.
You will be notified when your turn comes.
Thank you.'); - - return; - } - $waitlist = Waitlist::create([ - 'email' => Str::lower($this->email), - 'type' => 'registration', - ]); - - $this->dispatch('success', 'Check your email to verify your email address.'); - dispatch(new SendConfirmationForWaitlistJob($this->email, $waitlist->uuid)); - } catch (\Throwable $e) { - return handleError($e, $this); - } - } -} diff --git a/app/Models/Application.php b/app/Models/Application.php index d1efd3f33..82ad0c2d1 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -327,7 +327,7 @@ class Application extends BaseModel return null; } - public function failedTaskLink($task_uuid) + public function taskLink($task_uuid) { if (data_get($this, 'environment.project.uuid')) { $route = route('project.application.scheduled-tasks', [ diff --git a/app/Models/DiscordNotificationSettings.php b/app/Models/DiscordNotificationSettings.php new file mode 100644 index 000000000..619393ddc --- /dev/null +++ b/app/Models/DiscordNotificationSettings.php @@ -0,0 +1,59 @@ + 'boolean', + 'discord_webhook_url' => 'encrypted', + + 'deployment_success_discord_notifications' => 'boolean', + 'deployment_failure_discord_notifications' => 'boolean', + 'status_change_discord_notifications' => 'boolean', + 'backup_success_discord_notifications' => 'boolean', + 'backup_failure_discord_notifications' => 'boolean', + 'scheduled_task_success_discord_notifications' => 'boolean', + 'scheduled_task_failure_discord_notifications' => 'boolean', + 'docker_cleanup_discord_notifications' => 'boolean', + 'server_disk_usage_discord_notifications' => 'boolean', + 'server_reachable_discord_notifications' => 'boolean', + 'server_unreachable_discord_notifications' => 'boolean', + ]; + + public function team() + { + return $this->belongsTo(Team::class); + } + + public function isEnabled() + { + return $this->discord_enabled; + } +} diff --git a/app/Models/EmailNotificationSettings.php b/app/Models/EmailNotificationSettings.php new file mode 100644 index 000000000..ae118986f --- /dev/null +++ b/app/Models/EmailNotificationSettings.php @@ -0,0 +1,79 @@ + 'boolean', + 'smtp_from_address' => 'encrypted', + 'smtp_from_name' => 'encrypted', + 'smtp_recipients' => 'encrypted', + 'smtp_host' => 'encrypted', + 'smtp_port' => 'integer', + 'smtp_username' => 'encrypted', + 'smtp_password' => 'encrypted', + 'smtp_timeout' => 'integer', + + 'resend_enabled' => 'boolean', + 'resend_api_key' => 'encrypted', + + 'use_instance_email_settings' => 'boolean', + + 'deployment_success_email_notifications' => 'boolean', + 'deployment_failure_email_notifications' => 'boolean', + 'status_change_email_notifications' => 'boolean', + 'backup_success_email_notifications' => 'boolean', + 'backup_failure_email_notifications' => 'boolean', + 'scheduled_task_success_email_notifications' => 'boolean', + 'scheduled_task_failure_email_notifications' => 'boolean', + 'server_disk_usage_email_notifications' => 'boolean', + ]; + + public function team() + { + return $this->belongsTo(Team::class); + } + + public function isEnabled() + { + if (isCloud()) { + return true; + } + + return $this->smtp_enabled || $this->resend_enabled || $this->use_instance_email_settings; + } +} diff --git a/app/Models/InstanceSettings.php b/app/Models/InstanceSettings.php index eeb803925..5b89bb401 100644 --- a/app/Models/InstanceSettings.php +++ b/app/Models/InstanceSettings.php @@ -16,8 +16,19 @@ class InstanceSettings extends Model implements SendsEmail protected $guarded = []; protected $casts = [ - 'resale_license' => 'encrypted', + 'smtp_enabled' => 'boolean', + 'smtp_from_address' => 'encrypted', + 'smtp_from_name' => 'encrypted', + 'smtp_recipients' => 'encrypted', + 'smtp_host' => 'encrypted', + 'smtp_port' => 'integer', + 'smtp_username' => 'encrypted', 'smtp_password' => 'encrypted', + 'smtp_timeout' => 'integer', + + 'resend_enabled' => 'boolean', + 'resend_api_key' => 'encrypted', + 'allowed_ip_ranges' => 'array', 'is_auto_update_enabled' => 'boolean', 'auto_update_frequency' => 'string', @@ -81,7 +92,7 @@ class InstanceSettings extends Model implements SendsEmail return InstanceSettings::findOrFail(0); } - public function getRecepients($notification) + public function getRecipients($notification) { $recipients = data_get($notification, 'emails', null); if (is_null($recipients) || $recipients === '') { diff --git a/app/Models/S3Storage.php b/app/Models/S3Storage.php index a432a6e9c..f1247e6f7 100644 --- a/app/Models/S3Storage.php +++ b/app/Models/S3Storage.php @@ -59,7 +59,7 @@ class S3Storage extends BaseModel $this->is_usable = true; } catch (\Throwable $e) { $this->is_usable = false; - if ($this->unusable_email_sent === false && is_transactional_emails_active()) { + if ($this->unusable_email_sent === false && is_transactional_emails_enabled()) { $mail = new MailMessage; $mail->subject('Coolify: S3 Storage Connection Error'); $mail->view('emails.s3-connection-error', ['name' => $this->name, 'reason' => $e->getMessage(), 'url' => route('storage.show', ['storage_uuid' => $this->uuid])]); diff --git a/app/Models/Server.php b/app/Models/Server.php index 6dfb0a4a1..cc8211789 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -1042,7 +1042,7 @@ $schema://$host { $this->unreachable_notification_sent = false; $this->save(); $this->refresh(); - // $this->team->notify(new Reachable($this)); + $this->team->notify(new Reachable($this)); } public function sendUnreachableNotification() @@ -1050,7 +1050,7 @@ $schema://$host { $this->unreachable_notification_sent = true; $this->save(); $this->refresh(); - // $this->team->notify(new Unreachable($this)); + $this->team->notify(new Unreachable($this)); } public function validateConnection(bool $justCheckingNewKey = false) diff --git a/app/Models/Service.php b/app/Models/Service.php index 6d3d2024b..117677d53 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -1140,7 +1140,7 @@ class Service extends BaseModel return null; } - public function failedTaskLink($task_uuid) + public function taskLink($task_uuid) { if (data_get($this, 'environment.project.uuid')) { $route = route('project.service.scheduled-tasks', [ diff --git a/app/Models/SlackNotificationSettings.php b/app/Models/SlackNotificationSettings.php new file mode 100644 index 000000000..48153f2ea --- /dev/null +++ b/app/Models/SlackNotificationSettings.php @@ -0,0 +1,59 @@ + 'boolean', + 'slack_webhook_url' => 'encrypted', + + 'deployment_success_slack_notifications' => 'boolean', + 'deployment_failure_slack_notifications' => 'boolean', + 'status_change_slack_notifications' => 'boolean', + 'backup_success_slack_notifications' => 'boolean', + 'backup_failure_slack_notifications' => 'boolean', + 'scheduled_task_success_slack_notifications' => 'boolean', + 'scheduled_task_failure_slack_notifications' => 'boolean', + 'docker_cleanup_slack_notifications' => 'boolean', + 'server_disk_usage_slack_notifications' => 'boolean', + 'server_reachable_slack_notifications' => 'boolean', + 'server_unreachable_slack_notifications' => 'boolean', + ]; + + public function team() + { + return $this->belongsTo(Team::class); + } + + public function isEnabled() + { + return $this->slack_enabled; + } +} diff --git a/app/Models/Team.php b/app/Models/Team.php index ecf662787..07424a55f 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -5,6 +5,7 @@ namespace App\Models; use App\Notifications\Channels\SendsDiscord; use App\Notifications\Channels\SendsEmail; use App\Notifications\Channels\SendsSlack; +use App\Traits\HasNotificationSettings; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; @@ -20,49 +21,8 @@ use OpenApi\Attributes as OA; 'personal_team' => ['type' => 'boolean', 'description' => 'Whether the team is personal or not.'], 'created_at' => ['type' => 'string', 'description' => 'The date and time the team was created.'], 'updated_at' => ['type' => 'string', 'description' => 'The date and time the team was last updated.'], - 'smtp_enabled' => ['type' => 'boolean', 'description' => 'Whether SMTP is enabled or not.'], - 'smtp_from_address' => ['type' => 'string', 'description' => 'The email address to send emails from.'], - 'smtp_from_name' => ['type' => 'string', 'description' => 'The name to send emails from.'], - 'smtp_recipients' => ['type' => 'string', 'description' => 'The email addresses to send emails to.'], - 'smtp_host' => ['type' => 'string', 'description' => 'The SMTP host.'], - 'smtp_port' => ['type' => 'string', 'description' => 'The SMTP port.'], - 'smtp_encryption' => ['type' => 'string', 'description' => 'The SMTP encryption.'], - 'smtp_username' => ['type' => 'string', 'description' => 'The SMTP username.'], - 'smtp_password' => ['type' => 'string', 'description' => 'The SMTP password.'], - 'smtp_timeout' => ['type' => 'string', 'description' => 'The SMTP timeout.'], - 'smtp_notifications_test' => ['type' => 'boolean', 'description' => 'Whether to send test notifications via SMTP.'], - 'smtp_notifications_deployments' => ['type' => 'boolean', 'description' => 'Whether to send deployment notifications via SMTP.'], - 'smtp_notifications_status_changes' => ['type' => 'boolean', 'description' => 'Whether to send status change notifications via SMTP.'], - 'smtp_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via SMTP.'], - 'smtp_notifications_database_backups' => ['type' => 'boolean', 'description' => 'Whether to send database backup notifications via SMTP.'], - 'smtp_notifications_server_disk_usage' => ['type' => 'boolean', 'description' => 'Whether to send server disk usage notifications via SMTP.'], - 'discord_enabled' => ['type' => 'boolean', 'description' => 'Whether Discord is enabled or not.'], - 'discord_webhook_url' => ['type' => 'string', 'description' => 'The Discord webhook URL.'], - 'discord_notifications_test' => ['type' => 'boolean', 'description' => 'Whether to send test notifications via Discord.'], - 'discord_notifications_deployments' => ['type' => 'boolean', 'description' => 'Whether to send deployment notifications via Discord.'], - 'discord_notifications_status_changes' => ['type' => 'boolean', 'description' => 'Whether to send status change notifications via Discord.'], - 'discord_notifications_database_backups' => ['type' => 'boolean', 'description' => 'Whether to send database backup notifications via Discord.'], - 'discord_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via Discord.'], - 'discord_notifications_server_disk_usage' => ['type' => 'boolean', 'description' => 'Whether to send server disk usage notifications via Discord.'], 'show_boarding' => ['type' => 'boolean', 'description' => 'Whether to show the boarding screen or not.'], - 'resend_enabled' => ['type' => 'boolean', 'description' => 'Whether to enable resending or not.'], - 'resend_api_key' => ['type' => 'string', 'description' => 'The resending API key.'], - 'use_instance_email_settings' => ['type' => 'boolean', 'description' => 'Whether to use instance email settings or not.'], - 'telegram_enabled' => ['type' => 'boolean', 'description' => 'Whether Telegram is enabled or not.'], - 'telegram_token' => ['type' => 'string', 'description' => 'The Telegram token.'], - 'telegram_chat_id' => ['type' => 'string', 'description' => 'The Telegram chat ID.'], - 'telegram_notifications_test' => ['type' => 'boolean', 'description' => 'Whether to send test notifications via Telegram.'], - 'telegram_notifications_deployments' => ['type' => 'boolean', 'description' => 'Whether to send deployment notifications via Telegram.'], - 'telegram_notifications_status_changes' => ['type' => 'boolean', 'description' => 'Whether to send status change notifications via Telegram.'], - 'telegram_notifications_database_backups' => ['type' => 'boolean', 'description' => 'Whether to send database backup notifications via Telegram.'], - 'telegram_notifications_test_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram test message thread ID.'], - 'telegram_notifications_deployments_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram deployment message thread ID.'], - 'telegram_notifications_status_changes_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram status change message thread ID.'], - 'telegram_notifications_database_backups_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram database backup message thread ID.'], - 'custom_server_limit' => ['type' => 'string', 'description' => 'The custom server limit.'], - 'telegram_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via Telegram.'], - 'telegram_notifications_scheduled_tasks_thread_id' => ['type' => 'string', 'description' => 'The Telegram scheduled task message thread ID.'], 'members' => new OA\Property( property: 'members', type: 'array', @@ -71,20 +31,26 @@ use OpenApi\Attributes as OA; ), ] )] + class Team extends Model implements SendsDiscord, SendsEmail, SendsSlack { - use Notifiable; + use HasNotificationSettings, Notifiable; protected $guarded = []; protected $casts = [ 'personal_team' => 'boolean', - 'smtp_password' => 'encrypted', - 'resend_api_key' => 'encrypted', ]; protected static function booted() { + static::created(function ($team) { + $team->emailNotificationSettings()->create(); + $team->discordNotificationSettings()->create(); + $team->slackNotificationSettings()->create(); + $team->telegramNotificationSettings()->create(); + }); + static::saving(function ($team) { if (auth()->user()?->isMember()) { throw new \Exception('You are not allowed to update this team.'); @@ -115,34 +81,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() { $serverLimit = Team::serverLimit(); @@ -196,10 +134,66 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsSlack 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 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(); + } + + public function subscriptionEnded() + { + $this->subscription->update([ + 'stripe_subscription_id' => null, + 'stripe_plan_id' => null, + 'stripe_cancel_at_period_end' => false, + 'stripe_invoice_paid' => false, + 'stripe_trial_already_ended' => false, + ]); + foreach ($this->servers as $server) { + $server->settings()->update([ + 'is_usable' => false, + 'is_reachable' => false, + ]); + } + } + public function environment_variables() { return $this->hasMany(SharedEnvironmentVariable::class)->whereNull('project_id')->whereNull('environment_id'); @@ -263,32 +257,23 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsSlack return $this->hasMany(S3Storage::class)->where('is_usable', true); } - public function subscriptionEnded() + public function emailNotificationSettings() { - $this->subscription->update([ - 'stripe_subscription_id' => null, - 'stripe_plan_id' => null, - 'stripe_cancel_at_period_end' => false, - 'stripe_invoice_paid' => false, - 'stripe_trial_already_ended' => false, - ]); - foreach ($this->servers as $server) { - $server->settings()->update([ - 'is_usable' => false, - 'is_reachable' => false, - ]); - } + return $this->hasOne(EmailNotificationSettings::class); } - public function isAnyNotificationEnabled() + public function discordNotificationSettings() { - if (isCloud()) { - return true; - } - if ($this->smtp_enabled || $this->resend_enabled || $this->discord_enabled || $this->telegram_enabled || $this->use_instance_email_settings) { - return true; - } + return $this->hasOne(DiscordNotificationSettings::class); + } - return false; + public function telegramNotificationSettings() + { + return $this->hasOne(TelegramNotificationSettings::class); + } + + public function slackNotificationSettings() + { + return $this->hasOne(SlackNotificationSettings::class); } } diff --git a/app/Models/TelegramNotificationSettings.php b/app/Models/TelegramNotificationSettings.php new file mode 100644 index 000000000..2edca14ff --- /dev/null +++ b/app/Models/TelegramNotificationSettings.php @@ -0,0 +1,85 @@ + 'boolean', + 'telegram_token' => 'encrypted', + 'telegram_chat_id' => 'encrypted', + + 'deployment_success_telegram_notifications' => 'boolean', + 'deployment_failure_telegram_notifications' => 'boolean', + 'status_change_telegram_notifications' => 'boolean', + 'backup_success_telegram_notifications' => 'boolean', + 'backup_failure_telegram_notifications' => 'boolean', + 'scheduled_task_success_telegram_notifications' => 'boolean', + 'scheduled_task_failure_telegram_notifications' => 'boolean', + 'docker_cleanup_telegram_notifications' => 'boolean', + 'server_disk_usage_telegram_notifications' => 'boolean', + 'server_reachable_telegram_notifications' => 'boolean', + 'server_unreachable_telegram_notifications' => 'boolean', + + 'telegram_notifications_deployment_success_topic_id' => 'encrypted', + 'telegram_notifications_deployment_failure_topic_id' => 'encrypted', + 'telegram_notifications_status_change_topic_id' => 'encrypted', + 'telegram_notifications_backup_success_topic_id' => 'encrypted', + 'telegram_notifications_backup_failure_topic_id' => 'encrypted', + 'telegram_notifications_scheduled_task_success_topic_id' => 'encrypted', + 'telegram_notifications_scheduled_task_failure_topic_id' => 'encrypted', + 'telegram_notifications_docker_cleanup_topic_id' => 'encrypted', + 'telegram_notifications_server_disk_usage_topic_id' => 'encrypted', + 'telegram_notifications_server_reachable_topic_id' => 'encrypted', + 'telegram_notifications_server_unreachable_topic_id' => 'encrypted', + ]; + + public function team() + { + return $this->belongsTo(Team::class); + } + + public function isEnabled() + { + return $this->telegram_enabled; + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 25fb33d66..7c23631c3 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -114,7 +114,7 @@ class User extends Authenticatable implements SendsEmail return $this->belongsToMany(Team::class)->withPivot('role'); } - public function getRecepients($notification) + public function getRecipients($notification) { return $this->email; } diff --git a/app/Models/Waitlist.php b/app/Models/Waitlist.php deleted file mode 100644 index 28e5f01fd..000000000 --- a/app/Models/Waitlist.php +++ /dev/null @@ -1,12 +0,0 @@ -getEnabledChannels('deployment_failure'); } public function toMail(): MailMessage diff --git a/app/Notifications/Application/DeploymentSuccess.php b/app/Notifications/Application/DeploymentSuccess.php index 79ae19f66..548f5c6e5 100644 --- a/app/Notifications/Application/DeploymentSuccess.php +++ b/app/Notifications/Application/DeploymentSuccess.php @@ -45,13 +45,7 @@ class DeploymentSuccess extends CustomEmailNotification public function via(object $notifiable): array { - $channels = setNotificationChannels($notifiable, 'deployments'); - if (isCloud()) { - // TODO: Make batch notifications work with email - $channels = array_diff($channels, [\App\Notifications\Channels\EmailChannel::class]); - } - - return $channels; + return $notifiable->getEnabledChannels('deployment_success'); } public function toMail(): MailMessage diff --git a/app/Notifications/Application/StatusChanged.php b/app/Notifications/Application/StatusChanged.php index 2167e9f13..32a6a659a 100644 --- a/app/Notifications/Application/StatusChanged.php +++ b/app/Notifications/Application/StatusChanged.php @@ -35,7 +35,7 @@ class StatusChanged extends CustomEmailNotification public function via(object $notifiable): array { - return setNotificationChannels($notifiable, 'status_changes'); + return $notifiable->getEnabledChannels('status_change'); } public function toMail(): MailMessage diff --git a/app/Notifications/Channels/DiscordChannel.php b/app/Notifications/Channels/DiscordChannel.php index df7040f8f..362006d8e 100644 --- a/app/Notifications/Channels/DiscordChannel.php +++ b/app/Notifications/Channels/DiscordChannel.php @@ -13,10 +13,13 @@ class DiscordChannel public function send(SendsDiscord $notifiable, Notification $notification): void { $message = $notification->toDiscord(); - $webhookUrl = $notifiable->routeNotificationForDiscord(); - if (! $webhookUrl) { + + $discordSettings = $notifiable->discordNotificationSettings; + + if (! $discordSettings || ! $discordSettings->isEnabled() || ! $discordSettings->discord_webhook_url) { return; } - SendMessageToDiscordJob::dispatch($message, $webhookUrl); + + SendMessageToDiscordJob::dispatch($message, $discordSettings->discord_webhook_url); } } diff --git a/app/Notifications/Channels/EmailChannel.php b/app/Notifications/Channels/EmailChannel.php index 5394f6106..0985f4393 100644 --- a/app/Notifications/Channels/EmailChannel.php +++ b/app/Notifications/Channels/EmailChannel.php @@ -13,7 +13,7 @@ class EmailChannel { try { $this->bootConfigs($notifiable); - $recipients = $notifiable->getRecepients($notification); + $recipients = $notifiable->getRecipients($notification); if (count($recipients) === 0) { throw new Exception('No email recipients found'); } @@ -46,7 +46,9 @@ class EmailChannel private function bootConfigs($notifiable): void { - if (data_get($notifiable, 'use_instance_email_settings')) { + $emailSettings = $notifiable->emailNotificationSettings; + + if ($emailSettings->use_instance_email_settings) { $type = set_transanctional_email_settings(); if (! $type) { throw new Exception('No email settings found.'); @@ -54,24 +56,27 @@ class EmailChannel return; } - config()->set('mail.from.address', data_get($notifiable, 'smtp_from_address', 'test@example.com')); - config()->set('mail.from.name', data_get($notifiable, 'smtp_from_name', 'Test')); - if (data_get($notifiable, 'resend_enabled')) { + + config()->set('mail.from.address', $emailSettings->smtp_from_address ?? 'test@example.com'); + config()->set('mail.from.name', $emailSettings->smtp_from_name ?? 'Test'); + + if ($emailSettings->resend_enabled) { config()->set('mail.default', 'resend'); - config()->set('resend.api_key', data_get($notifiable, 'resend_api_key')); + config()->set('resend.api_key', $emailSettings->resend_api_key); } - if (data_get($notifiable, 'smtp_enabled')) { + + if ($emailSettings->smtp_enabled) { config()->set('mail.default', 'smtp'); config()->set('mail.mailers.smtp', [ 'transport' => 'smtp', - 'host' => data_get($notifiable, 'smtp_host'), - 'port' => data_get($notifiable, 'smtp_port'), - 'encryption' => data_get($notifiable, 'smtp_encryption') === 'none' ? null : data_get($notifiable, 'smtp_encryption'), - 'username' => data_get($notifiable, 'smtp_username'), - 'password' => data_get($notifiable, 'smtp_password'), - 'timeout' => data_get($notifiable, 'smtp_timeout'), + 'host' => $emailSettings->smtp_host, + 'port' => $emailSettings->smtp_port, + 'encryption' => $emailSettings->smtp_encryption === 'none' ? null : $emailSettings->smtp_encryption, + 'username' => $emailSettings->smtp_username, + 'password' => $emailSettings->smtp_password, + 'timeout' => $emailSettings->smtp_timeout, 'local_domain' => null, - 'auto_tls' => data_get($notifiable, 'smtp_encryption') === 'none' ? '0' : '', + 'auto_tls' => $emailSettings->smtp_encryption === 'none' ? '0' : '', ]); } } diff --git a/app/Notifications/Channels/SendsEmail.php b/app/Notifications/Channels/SendsEmail.php index fc7528834..3adc6d0a2 100644 --- a/app/Notifications/Channels/SendsEmail.php +++ b/app/Notifications/Channels/SendsEmail.php @@ -4,5 +4,5 @@ namespace App\Notifications\Channels; interface SendsEmail { - public function getRecepients($notification); + public function getRecipients($notification); } diff --git a/app/Notifications/Channels/SlackChannel.php b/app/Notifications/Channels/SlackChannel.php index 32fdbe9cf..cddb7a561 100644 --- a/app/Notifications/Channels/SlackChannel.php +++ b/app/Notifications/Channels/SlackChannel.php @@ -13,10 +13,12 @@ class SlackChannel public function send(SendsSlack $notifiable, Notification $notification): void { $message = $notification->toSlack(); - $webhookUrl = $notifiable->routeNotificationForSlack(); - if (! $webhookUrl) { + $slackSettings = $notifiable->slackNotificationSettings; + + if (! $slackSettings || ! $slackSettings->isEnabled() || ! $slackSettings->slack_webhook_url) { return; } - SendMessageToSlackJob::dispatch($message, $webhookUrl); + + SendMessageToSlackJob::dispatch($message, $slackSettings->slack_webhook_url); } } diff --git a/app/Notifications/Channels/TelegramChannel.php b/app/Notifications/Channels/TelegramChannel.php index 958c46c21..17d76181a 100644 --- a/app/Notifications/Channels/TelegramChannel.php +++ b/app/Notifications/Channels/TelegramChannel.php @@ -9,38 +9,32 @@ class TelegramChannel public function send($notifiable, $notification): void { $data = $notification->toTelegram($notifiable); - $telegramData = $notifiable->routeNotificationForTelegram(); + $settings = $notifiable->telegramNotificationSettings; + $message = data_get($data, 'message'); $buttons = data_get($data, 'buttons', []); - $telegramToken = data_get($telegramData, 'token'); - $chatId = data_get($telegramData, 'chat_id'); - $topicId = null; - $topicsInstance = get_class($notification); + $telegramToken = $settings->telegram_token; + $chatId = $settings->telegram_chat_id; + + $topicId = match (get_class($notification)) { + \App\Notifications\Test::class => $settings->telegram_notifications_test_topic_id, + \App\Notifications\Application\StatusChanged::class, + \App\Notifications\Container\ContainerRestarted::class, + \App\Notifications\Container\ContainerStopped::class => $settings->telegram_notifications_status_change_topic_id, + \App\Notifications\Application\DeploymentSuccess::class => $settings->telegram_notifications_deployment_success_topic_id, + \App\Notifications\Application\DeploymentFailed::class => $settings->telegram_notifications_deployment_failure_topic_id, + \App\Notifications\Database\BackupSuccess::class => $settings->telegram_notifications_backup_success_topic_id, + \App\Notifications\Database\BackupFailed::class => $settings->telegram_notifications_backup_failure_topic_id, + \App\Notifications\ScheduledTask\TaskFailed::class => $settings->telegram_notifications_scheduled_task_failure_topic_id, + \App\Notifications\Server\Unreachable::class => $settings->telegram_notifications_server_unreachable_topic_id, + \App\Notifications\Server\Reachable::class => $settings->telegram_notifications_server_reachable_topic_id, + default => null, + }; - switch ($topicsInstance) { - case \App\Notifications\Test::class: - $topicId = data_get($notifiable, 'telegram_notifications_test_message_thread_id'); - break; - case \App\Notifications\Application\StatusChanged::class: - case \App\Notifications\Container\ContainerRestarted::class: - case \App\Notifications\Container\ContainerStopped::class: - $topicId = data_get($notifiable, 'telegram_notifications_status_changes_message_thread_id'); - break; - case \App\Notifications\Application\DeploymentSuccess::class: - case \App\Notifications\Application\DeploymentFailed::class: - $topicId = data_get($notifiable, 'telegram_notifications_deployments_message_thread_id'); - break; - case \App\Notifications\Database\BackupSuccess::class: - case \App\Notifications\Database\BackupFailed::class: - $topicId = data_get($notifiable, 'telegram_notifications_database_backups_message_thread_id'); - break; - case \App\Notifications\ScheduledTask\TaskFailed::class: - $topicId = data_get($notifiable, 'telegram_notifications_scheduled_tasks_thread_id'); - break; - } if (! $telegramToken || ! $chatId || ! $message) { return; } + SendMessageToTelegramJob::dispatch($message, $buttons, $telegramToken, $chatId, $topicId); } } diff --git a/app/Notifications/Channels/TransactionalEmailChannel.php b/app/Notifications/Channels/TransactionalEmailChannel.php index cc7d76ebf..761780231 100644 --- a/app/Notifications/Channels/TransactionalEmailChannel.php +++ b/app/Notifications/Channels/TransactionalEmailChannel.php @@ -7,7 +7,6 @@ use Exception; use Illuminate\Mail\Message; use Illuminate\Notifications\Notification; use Illuminate\Support\Facades\Mail; -use Log; class TransactionalEmailChannel { @@ -15,8 +14,6 @@ class TransactionalEmailChannel { $settings = instanceSettings(); if (! data_get($settings, 'smtp_enabled') && ! data_get($settings, 'resend_enabled')) { - Log::info('SMTP/Resend not enabled'); - return; } $email = $notifiable->email; diff --git a/app/Notifications/Container/ContainerRestarted.php b/app/Notifications/Container/ContainerRestarted.php index f9becd0e8..c25072ecf 100644 --- a/app/Notifications/Container/ContainerRestarted.php +++ b/app/Notifications/Container/ContainerRestarted.php @@ -17,7 +17,7 @@ class ContainerRestarted extends CustomEmailNotification public function via(object $notifiable): array { - return setNotificationChannels($notifiable, 'status_changes'); + return $notifiable->getEnabledChannels('status_change'); } public function toMail(): MailMessage diff --git a/app/Notifications/Container/ContainerStopped.php b/app/Notifications/Container/ContainerStopped.php index eae2cf552..bc6e52b6d 100644 --- a/app/Notifications/Container/ContainerStopped.php +++ b/app/Notifications/Container/ContainerStopped.php @@ -17,7 +17,7 @@ class ContainerStopped extends CustomEmailNotification public function via(object $notifiable): array { - return setNotificationChannels($notifiable, 'status_changes'); + return $notifiable->getEnabledChannels('status_change'); } public function toMail(): MailMessage diff --git a/app/Notifications/Database/BackupFailed.php b/app/Notifications/Database/BackupFailed.php index 2056255d6..2208f7b1d 100644 --- a/app/Notifications/Database/BackupFailed.php +++ b/app/Notifications/Database/BackupFailed.php @@ -23,13 +23,13 @@ class BackupFailed extends CustomEmailNotification public function via(object $notifiable): array { - return setNotificationChannels($notifiable, 'database_backups'); + return $notifiable->getEnabledChannels('backup_failure'); } public function toMail(): MailMessage { $mail = new MailMessage; - $mail->subject("Coolify: [ACTION REQUIRED] Backup FAILED for {$this->database->name}"); + $mail->subject("Coolify: [ACTION REQUIRED] Database Backup FAILED for {$this->database->name}"); $mail->view('emails.backup-failed', [ 'name' => $this->name, 'database_name' => $this->database_name, diff --git a/app/Notifications/Database/BackupSuccess.php b/app/Notifications/Database/BackupSuccess.php index 71866f9fc..10b4ff3df 100644 --- a/app/Notifications/Database/BackupSuccess.php +++ b/app/Notifications/Database/BackupSuccess.php @@ -24,7 +24,7 @@ class BackupSuccess extends CustomEmailNotification public function via(object $notifiable): array { - return setNotificationChannels($notifiable, 'database_backups'); + return $notifiable->getEnabledChannels('backup_success'); } public function toMail(): MailMessage diff --git a/app/Notifications/Internal/GeneralNotification.php b/app/Notifications/Internal/GeneralNotification.php index 347f4a0dd..7049e3055 100644 --- a/app/Notifications/Internal/GeneralNotification.php +++ b/app/Notifications/Internal/GeneralNotification.php @@ -2,9 +2,6 @@ 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\SlackMessage; use Illuminate\Bus\Queueable; @@ -24,22 +21,7 @@ class GeneralNotification extends Notification implements ShouldQueue public function via(object $notifiable): array { - $channels = []; - $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); - $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); - $isSlackEnabled = data_get($notifiable, 'slack_enabled'); - - if ($isDiscordEnabled) { - $channels[] = DiscordChannel::class; - } - if ($isTelegramEnabled) { - $channels[] = TelegramChannel::class; - } - if ($isSlackEnabled) { - $channels[] = SlackChannel::class; - } - - return $channels; + return $notifiable->getEnabledChannels('general'); } public function toDiscord(): DiscordMessage diff --git a/app/Notifications/ScheduledTask/TaskFailed.php b/app/Notifications/ScheduledTask/TaskFailed.php index 753da7ff0..56c410ecb 100644 --- a/app/Notifications/ScheduledTask/TaskFailed.php +++ b/app/Notifications/ScheduledTask/TaskFailed.php @@ -16,15 +16,15 @@ class TaskFailed extends CustomEmailNotification { $this->onQueue('high'); if ($task->application) { - $this->url = $task->application->failedTaskLink($task->uuid); + $this->url = $task->application->taskLink($task->uuid); } elseif ($task->service) { - $this->url = $task->service->failedTaskLink($task->uuid); + $this->url = $task->service->taskLink($task->uuid); } } public function via(object $notifiable): array { - return setNotificationChannels($notifiable, 'scheduled_tasks'); + return $notifiable->getEnabledChannels('scheduled_task_failure'); } public function toMail(): MailMessage diff --git a/app/Notifications/ScheduledTask/TaskSuccess.php b/app/Notifications/ScheduledTask/TaskSuccess.php new file mode 100644 index 000000000..fc79aea37 --- /dev/null +++ b/app/Notifications/ScheduledTask/TaskSuccess.php @@ -0,0 +1,88 @@ +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 toSlack(): SlackMessage + { + $title = 'Scheduled task succeeded'; + $description = "Scheduled task ({$this->task->name}) succeeded."; + + if ($this->url) { + $description .= "\n\n**Task URL:** {$this->url}"; + } + + return new SlackMessage( + title: $title, + description: $description, + color: SlackMessage::successColor() + ); + } +} diff --git a/app/Notifications/Server/DockerCleanup.php b/app/Notifications/Server/DockerCleanup.php deleted file mode 100644 index 46b730c7b..000000000 --- a/app/Notifications/Server/DockerCleanup.php +++ /dev/null @@ -1,78 +0,0 @@ -onQueue('high'); - } - - public function via(object $notifiable): array - { - $channels = []; - // $isEmailEnabled = isEmailEnabled($notifiable); - $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); - $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); - $isSlackEnabled = data_get($notifiable, 'slack_enabled'); - if ($isDiscordEnabled) { - $channels[] = DiscordChannel::class; - } - // if ($isEmailEnabled) { - // $channels[] = EmailChannel::class; - // } - if ($isTelegramEnabled) { - $channels[] = TelegramChannel::class; - } - if ($isSlackEnabled) { - $channels[] = SlackChannel::class; - } - - return $channels; - } - - // public function toMail(): MailMessage - // { - // $mail = new MailMessage(); - // $mail->subject("Coolify: Server ({$this->server->name}) high disk usage detected!"); - // $mail->view('emails.high-disk-usage', [ - // 'name' => $this->server->name, - // 'disk_usage' => $this->disk_usage, - // 'threshold' => $this->docker_cleanup_threshold, - // ]); - // return $mail; - // } - - public function toDiscord(): DiscordMessage - { - return new DiscordMessage( - title: ':white_check_mark: Server cleanup job done', - description: $this->message, - color: DiscordMessage::successColor(), - ); - } - - public function toTelegram(): array - { - return [ - 'message' => "Coolify: Server '{$this->server->name}' cleanup job done!\n\n{$this->message}", - ]; - } - - public function toSlack(): SlackMessage - { - return new SlackMessage( - title: 'Server cleanup job done', - description: "Server '{$this->server->name}' cleanup job done!\n\n{$this->message}", - color: SlackMessage::successColor() - ); - } -} diff --git a/app/Notifications/Server/DockerCleanupFailed.php b/app/Notifications/Server/DockerCleanupFailed.php new file mode 100644 index 000000000..53714925c --- /dev/null +++ b/app/Notifications/Server/DockerCleanupFailed.php @@ -0,0 +1,59 @@ +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 toSlack(): SlackMessage + { + return new SlackMessage( + title: 'Coolify: [ACTION REQUIRED] Docker cleanup job failed', + description: "Docker cleanup job failed on '{$this->server->name}'!\n\n{$this->message}", + color: SlackMessage::errorColor() + ); + } +} diff --git a/app/Notifications/Server/DockerCleanupSuccess.php b/app/Notifications/Server/DockerCleanupSuccess.php new file mode 100644 index 000000000..85a819da2 --- /dev/null +++ b/app/Notifications/Server/DockerCleanupSuccess.php @@ -0,0 +1,59 @@ +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 toSlack(): SlackMessage + { + return new SlackMessage( + title: 'Coolify: Docker cleanup job succeeded', + description: "Docker cleanup job succeeded on '{$this->server->name}'!\n\n{$this->message}", + color: SlackMessage::successColor() + ); + } +} diff --git a/app/Notifications/Server/ForceDisabled.php b/app/Notifications/Server/ForceDisabled.php index 143917dde..e122849da 100644 --- a/app/Notifications/Server/ForceDisabled.php +++ b/app/Notifications/Server/ForceDisabled.php @@ -3,10 +3,6 @@ namespace App\Notifications\Server; use App\Models\Server; -use App\Notifications\Channels\DiscordChannel; -use App\Notifications\Channels\EmailChannel; -use App\Notifications\Channels\SlackChannel; -use App\Notifications\Channels\TelegramChannel; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; use App\Notifications\Dto\SlackMessage; @@ -21,25 +17,7 @@ class ForceDisabled extends CustomEmailNotification public function via(object $notifiable): array { - $channels = []; - $isEmailEnabled = isEmailEnabled($notifiable); - $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); - $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); - $isSlackEnabled = data_get($notifiable, 'slack_enabled'); - if ($isDiscordEnabled) { - $channels[] = DiscordChannel::class; - } - if ($isEmailEnabled) { - $channels[] = EmailChannel::class; - } - if ($isTelegramEnabled) { - $channels[] = TelegramChannel::class; - } - if ($isSlackEnabled) { - $channels[] = SlackChannel::class; - } - - return $channels; + return $notifiable->getEnabledChannels('server_force_disabled'); } public function toMail(): MailMessage diff --git a/app/Notifications/Server/ForceEnabled.php b/app/Notifications/Server/ForceEnabled.php index 3b83882d8..a0e0a8b59 100644 --- a/app/Notifications/Server/ForceEnabled.php +++ b/app/Notifications/Server/ForceEnabled.php @@ -3,10 +3,6 @@ namespace App\Notifications\Server; use App\Models\Server; -use App\Notifications\Channels\DiscordChannel; -use App\Notifications\Channels\EmailChannel; -use App\Notifications\Channels\SlackChannel; -use App\Notifications\Channels\TelegramChannel; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; use App\Notifications\Dto\SlackMessage; @@ -21,25 +17,7 @@ class ForceEnabled extends CustomEmailNotification public function via(object $notifiable): array { - $channels = []; - $isEmailEnabled = isEmailEnabled($notifiable); - $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); - $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); - $isSlackEnabled = data_get($notifiable, 'slack_enabled'); - if ($isDiscordEnabled) { - $channels[] = DiscordChannel::class; - } - if ($isEmailEnabled) { - $channels[] = EmailChannel::class; - } - if ($isTelegramEnabled) { - $channels[] = TelegramChannel::class; - } - if ($isSlackEnabled) { - $channels[] = SlackChannel::class; - } - - return $channels; + return $notifiable->getEnabledChannels('server_force_enabled'); } public function toMail(): MailMessage diff --git a/app/Notifications/Server/HighDiskUsage.php b/app/Notifications/Server/HighDiskUsage.php index baff49508..9f4826689 100644 --- a/app/Notifications/Server/HighDiskUsage.php +++ b/app/Notifications/Server/HighDiskUsage.php @@ -17,7 +17,7 @@ class HighDiskUsage extends CustomEmailNotification public function via(object $notifiable): array { - return setNotificationChannels($notifiable, 'server_disk_usage'); + return $notifiable->getEnabledChannels('server_disk_usage'); } public function toMail(): MailMessage diff --git a/app/Notifications/Server/Reachable.php b/app/Notifications/Server/Reachable.php index 62ece34e8..4917e04f8 100644 --- a/app/Notifications/Server/Reachable.php +++ b/app/Notifications/Server/Reachable.php @@ -3,10 +3,6 @@ namespace App\Notifications\Server; use App\Models\Server; -use App\Notifications\Channels\DiscordChannel; -use App\Notifications\Channels\EmailChannel; -use App\Notifications\Channels\SlackChannel; -use App\Notifications\Channels\TelegramChannel; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; use App\Notifications\Dto\SlackMessage; @@ -30,25 +26,7 @@ class Reachable extends CustomEmailNotification return []; } - $channels = []; - $isEmailEnabled = isEmailEnabled($notifiable); - $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); - $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); - $isSlackEnabled = data_get($notifiable, 'slack_enabled'); - if ($isDiscordEnabled) { - $channels[] = DiscordChannel::class; - } - if ($isEmailEnabled) { - $channels[] = EmailChannel::class; - } - if ($isTelegramEnabled) { - $channels[] = TelegramChannel::class; - } - if ($isSlackEnabled) { - $channels[] = SlackChannel::class; - } - - return $channels; + return $notifiable->getEnabledChannels('server_reachable'); } public function toMail(): MailMessage diff --git a/app/Notifications/Server/Unreachable.php b/app/Notifications/Server/Unreachable.php index 2a90d7552..43f176d49 100644 --- a/app/Notifications/Server/Unreachable.php +++ b/app/Notifications/Server/Unreachable.php @@ -3,10 +3,6 @@ namespace App\Notifications\Server; use App\Models\Server; -use App\Notifications\Channels\DiscordChannel; -use App\Notifications\Channels\EmailChannel; -use App\Notifications\Channels\SlackChannel; -use App\Notifications\Channels\TelegramChannel; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; use App\Notifications\Dto\SlackMessage; @@ -30,26 +26,7 @@ class Unreachable extends CustomEmailNotification return []; } - $channels = []; - $isEmailEnabled = isEmailEnabled($notifiable); - $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); - $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); - $isSlackEnabled = data_get($notifiable, 'slack_enabled'); - - if ($isDiscordEnabled) { - $channels[] = DiscordChannel::class; - } - if ($isEmailEnabled) { - $channels[] = EmailChannel::class; - } - if ($isTelegramEnabled) { - $channels[] = TelegramChannel::class; - } - if ($isSlackEnabled) { - $channels[] = SlackChannel::class; - } - - return $channels; + return $notifiable->getEnabledChannels('server_unreachable'); } public function toMail(): ?MailMessage diff --git a/app/Notifications/Test.php b/app/Notifications/Test.php index 03f6c3296..da9098500 100644 --- a/app/Notifications/Test.php +++ b/app/Notifications/Test.php @@ -2,6 +2,10 @@ 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\Dto\DiscordMessage; use App\Notifications\Dto\SlackMessage; use Illuminate\Bus\Queueable; @@ -16,20 +20,32 @@ class Test extends Notification implements ShouldQueue public $tries = 5; - public function __construct(public ?string $emails = null) + public function __construct(public ?string $emails = null, public ?string $channel = null) { $this->onQueue('high'); } 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], + default => [], + }; + } else { + $channels = $notifiable->getEnabledChannels('test'); + } + + return $channels; } public function middleware(object $notifiable, string $channel) { return match ($channel) { - \App\Notifications\Channels\EmailChannel::class => [new RateLimited('email')], + EmailChannel::class => [new RateLimited('email')], default => [], }; } diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php index bbbf48345..ed27a158a 100644 --- a/app/Providers/FortifyServiceProvider.php +++ b/app/Providers/FortifyServiceProvider.php @@ -50,13 +50,10 @@ class FortifyServiceProvider extends ServiceProvider if (! $settings->is_registration_enabled) { return redirect()->route('login'); } - if (config('constants.waitlist.enabled')) { - return redirect()->route('waitlist.index'); - } else { - return view('auth.register', [ - 'isFirstUser' => $isFirstUser, - ]); - } + + return view('auth.register', [ + 'isFirstUser' => $isFirstUser, + ]); }); Fortify::loginView(function () { diff --git a/app/Traits/HasNotificationSettings.php b/app/Traits/HasNotificationSettings.php new file mode 100644 index 000000000..82cbda6ad --- /dev/null +++ b/app/Traits/HasNotificationSettings.php @@ -0,0 +1,90 @@ + $this->emailNotificationSettings, + 'discord' => $this->discordNotificationSettings, + 'telegram' => $this->telegramNotificationSettings, + 'slack' => $this->slackNotificationSettings, + 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, + ]; + + if ($event === 'general') { + unset($channelMap['email']); + } + + foreach ($channelMap as $channel => $channelClass) { + if ($this->isNotificationEnabled($channel) && $this->isNotificationTypeEnabled($channel, $event)) { + $channels[] = $channelClass; + } + } + + return $channels; + } +} diff --git a/bootstrap/helpers/notifications.php b/bootstrap/helpers/notifications.php new file mode 100644 index 000000000..3b1eb758b --- /dev/null +++ b/bootstrap/helpers/notifications.php @@ -0,0 +1,87 @@ +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; +} diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 835ead24c..ea051c84e 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -25,23 +25,15 @@ use App\Models\StandalonePostgresql; use App\Models\StandaloneRedis; use App\Models\Team; use App\Models\User; -use App\Notifications\Channels\DiscordChannel; -use App\Notifications\Channels\EmailChannel; -use App\Notifications\Channels\SlackChannel; -use App\Notifications\Channels\TelegramChannel; -use App\Notifications\Internal\GeneralNotification; use Carbon\CarbonImmutable; use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException; use Illuminate\Database\UniqueConstraintViolationException; -use Illuminate\Mail\Message; -use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Process\Pool; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Http; -use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Process; use Illuminate\Support\Facades\RateLimiter; 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"); } -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 { if (isDev()) { @@ -414,85 +369,7 @@ function validate_timezone(string $timezone): bool { 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) { $env_array = []; diff --git a/bootstrap/helpers/subscriptions.php b/bootstrap/helpers/subscriptions.php index 8ddb1331c..ab9ee9b9d 100644 --- a/bootstrap/helpers/subscriptions.php +++ b/bootstrap/helpers/subscriptions.php @@ -67,7 +67,6 @@ function allowedPathsForUnsubscribedAccounts() 'subscription/new', 'login', 'logout', - 'waitlist', 'force-password-reset', 'livewire/update', ]; diff --git a/config/constants.php b/config/constants.php index 42abd09cb..4ea4411b6 100644 --- a/config/constants.php +++ b/config/constants.php @@ -77,11 +77,6 @@ return [ ], ], - 'waitlist' => [ - 'enabled' => env('WAITLIST', false), - 'expiration' => 10, - ], - 'sentry' => [ 'sentry_dsn' => env('SENTRY_DSN'), ], diff --git a/database/migrations/2024_12_05_212355_create_email_notification_settings_table.php b/database/migrations/2024_12_05_212355_create_email_notification_settings_table.php new file mode 100644 index 000000000..7338a8d0d --- /dev/null +++ b/database/migrations/2024_12_05_212355_create_email_notification_settings_table.php @@ -0,0 +1,58 @@ +id(); + $table->foreignId('team_id')->constrained()->cascadeOnDelete(); + + $table->boolean('smtp_enabled')->default(false); + $table->text('smtp_from_address')->nullable(); + $table->text('smtp_from_name')->nullable(); + $table->text('smtp_recipients')->nullable(); + $table->text('smtp_host')->nullable(); + $table->integer('smtp_port')->nullable(); + $table->string('smtp_encryption')->nullable(); + $table->text('smtp_username')->nullable(); + $table->text('smtp_password')->nullable(); + $table->integer('smtp_timeout')->nullable(); + + $table->boolean('resend_enabled')->default(false); + $table->text('resend_api_key')->nullable(); + + $table->boolean('use_instance_email_settings')->default(false); + + $table->boolean('deployment_success_email_notifications')->default(false); + $table->boolean('deployment_failure_email_notifications')->default(true); + $table->boolean('status_change_email_notifications')->default(false); + $table->boolean('backup_success_email_notifications')->default(false); + $table->boolean('backup_failure_email_notifications')->default(true); + $table->boolean('scheduled_task_success_email_notifications')->default(false); + $table->boolean('scheduled_task_failure_email_notifications')->default(true); + $table->boolean('docker_cleanup_success_email_notifications')->default(false); + $table->boolean('docker_cleanup_failure_email_notifications')->default(true); + $table->boolean('server_disk_usage_email_notifications')->default(true); + $table->boolean('server_reachable_email_notifications')->default(false); + $table->boolean('server_unreachable_email_notifications')->default(true); + + $table->unique(['team_id']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('email_notification_settings'); + } +}; diff --git a/database/migrations/2024_12_05_212416_create_discord_notification_settings_table.php b/database/migrations/2024_12_05_212416_create_discord_notification_settings_table.php new file mode 100644 index 000000000..0f2ced67f --- /dev/null +++ b/database/migrations/2024_12_05_212416_create_discord_notification_settings_table.php @@ -0,0 +1,45 @@ +id(); + $table->foreignId('team_id')->constrained()->cascadeOnDelete(); + + $table->boolean('discord_enabled')->default(false); + $table->text('discord_webhook_url')->nullable(); + + $table->boolean('deployment_success_discord_notifications')->default(false); + $table->boolean('deployment_failure_discord_notifications')->default(true); + $table->boolean('status_change_discord_notifications')->default(false); + $table->boolean('backup_success_discord_notifications')->default(false); + $table->boolean('backup_failure_discord_notifications')->default(true); + $table->boolean('scheduled_task_success_discord_notifications')->default(false); + $table->boolean('scheduled_task_failure_discord_notifications')->default(true); + $table->boolean('docker_cleanup_success_discord_notifications')->default(false); + $table->boolean('docker_cleanup_failure_discord_notifications')->default(true); + $table->boolean('server_disk_usage_discord_notifications')->default(true); + $table->boolean('server_reachable_discord_notifications')->default(false); + $table->boolean('server_unreachable_discord_notifications')->default(true); + + $table->unique(['team_id']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('discord_notification_settings'); + } +}; diff --git a/database/migrations/2024_12_05_212440_create_telegram_notification_settings_table.php b/database/migrations/2024_12_05_212440_create_telegram_notification_settings_table.php new file mode 100644 index 000000000..190d148fc --- /dev/null +++ b/database/migrations/2024_12_05_212440_create_telegram_notification_settings_table.php @@ -0,0 +1,59 @@ +id(); + $table->foreignId('team_id')->constrained()->cascadeOnDelete(); + + $table->boolean('telegram_enabled')->default(false); + $table->text('telegram_token')->nullable(); + $table->text('telegram_chat_id')->nullable(); + + $table->boolean('deployment_success_telegram_notifications')->default(false); + $table->boolean('deployment_failure_telegram_notifications')->default(true); + $table->boolean('status_change_telegram_notifications')->default(false); + $table->boolean('backup_success_telegram_notifications')->default(false); + $table->boolean('backup_failure_telegram_notifications')->default(true); + $table->boolean('scheduled_task_success_telegram_notifications')->default(false); + $table->boolean('scheduled_task_failure_telegram_notifications')->default(true); + $table->boolean('docker_cleanup_success_telegram_notifications')->default(false); + $table->boolean('docker_cleanup_failure_telegram_notifications')->default(true); + $table->boolean('server_disk_usage_telegram_notifications')->default(true); + $table->boolean('server_reachable_telegram_notifications')->default(false); + $table->boolean('server_unreachable_telegram_notifications')->default(true); + + $table->text('telegram_notifications_deployment_success_topic_id')->nullable(); + $table->text('telegram_notifications_deployment_failure_topic_id')->nullable(); + $table->text('telegram_notifications_status_change_topic_id')->nullable(); + $table->text('telegram_notifications_backup_success_topic_id')->nullable(); + $table->text('telegram_notifications_backup_failure_topic_id')->nullable(); + $table->text('telegram_notifications_scheduled_task_success_topic_id')->nullable(); + $table->text('telegram_notifications_scheduled_task_failure_topic_id')->nullable(); + $table->text('telegram_notifications_docker_cleanup_success_topic_id')->nullable(); + $table->text('telegram_notifications_docker_cleanup_failure_topic_id')->nullable(); + $table->text('telegram_notifications_server_disk_usage_topic_id')->nullable(); + $table->text('telegram_notifications_server_reachable_topic_id')->nullable(); + $table->text('telegram_notifications_server_unreachable_topic_id')->nullable(); + + $table->unique(['team_id']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('telegram_notification_settings'); + } +}; diff --git a/database/migrations/2024_12_05_212546_migrate_email_notification_settings_from_teams_table.php b/database/migrations/2024_12_05_212546_migrate_email_notification_settings_from_teams_table.php new file mode 100644 index 000000000..384f62f06 --- /dev/null +++ b/database/migrations/2024_12_05_212546_migrate_email_notification_settings_from_teams_table.php @@ -0,0 +1,135 @@ +get(); + + foreach ($teams as $team) { + try { + DB::table('email_notification_settings')->updateOrInsert( + ['team_id' => $team->id], + [ + 'smtp_enabled' => $team->smtp_enabled ?? false, + 'smtp_from_address' => $team->smtp_from_address ? Crypt::encryptString($team->smtp_from_address) : null, + 'smtp_from_name' => $team->smtp_from_name ? Crypt::encryptString($team->smtp_from_name) : null, + 'smtp_recipients' => $team->smtp_recipients ? Crypt::encryptString($team->smtp_recipients) : null, + 'smtp_host' => $team->smtp_host ? Crypt::encryptString($team->smtp_host) : null, + 'smtp_port' => $team->smtp_port, + 'smtp_encryption' => $team->smtp_encryption, + 'smtp_username' => $team->smtp_username ? Crypt::encryptString($team->smtp_username) : null, + 'smtp_password' => $team->smtp_password, + 'smtp_timeout' => $team->smtp_timeout, + + 'use_instance_email_settings' => $team->use_instance_email_settings ?? false, + + 'resend_enabled' => $team->resend_enabled ?? false, + 'resend_api_key' => $team->resend_api_key, + + 'deployment_success_email_notifications' => $team->smtp_notifications_deployments ?? false, + 'deployment_failure_email_notifications' => $team->smtp_notifications_deployments ?? true, + 'backup_success_email_notifications' => $team->smtp_notifications_database_backups ?? false, + 'backup_failure_email_notifications' => $team->smtp_notifications_database_backups ?? true, + 'scheduled_task_success_email_notifications' => $team->smtp_notifications_scheduled_tasks ?? false, + 'scheduled_task_failure_email_notifications' => $team->smtp_notifications_scheduled_tasks ?? true, + 'status_change_email_notifications' => $team->smtp_notifications_status_changes ?? false, + 'server_disk_usage_email_notifications' => $team->smtp_notifications_server_disk_usage ?? true, + ] + ); + } catch (Exception $e) { + \Log::error('Error migrating email notification settings from teams table: '.$e->getMessage()); + } + } + + Schema::table('teams', function (Blueprint $table) { + $table->dropColumn([ + 'smtp_enabled', + 'smtp_from_address', + 'smtp_from_name', + 'smtp_recipients', + 'smtp_host', + 'smtp_port', + 'smtp_encryption', + 'smtp_username', + 'smtp_password', + 'smtp_timeout', + 'use_instance_email_settings', + 'resend_enabled', + 'resend_api_key', + 'smtp_notifications_test', + 'smtp_notifications_deployments', + 'smtp_notifications_database_backups', + 'smtp_notifications_scheduled_tasks', + 'smtp_notifications_status_changes', + 'smtp_notifications_server_disk_usage', + ]); + }); + } + + public function down(): void + { + Schema::table('teams', function (Blueprint $table) { + $table->boolean('smtp_enabled')->default(false); + $table->string('smtp_from_address')->nullable(); + $table->string('smtp_from_name')->nullable(); + $table->string('smtp_recipients')->nullable(); + $table->string('smtp_host')->nullable(); + $table->integer('smtp_port')->nullable(); + $table->string('smtp_encryption')->nullable(); + $table->text('smtp_username')->nullable(); + $table->text('smtp_password')->nullable(); + $table->integer('smtp_timeout')->nullable(); + + $table->boolean('use_instance_email_settings')->default(false); + $table->boolean('resend_enabled')->default(false); + + $table->text('resend_api_key')->nullable(); + + $table->boolean('smtp_notifications_test')->default(false); + $table->boolean('smtp_notifications_deployments')->default(false); + $table->boolean('smtp_notifications_database_backups')->default(true); + $table->boolean('smtp_notifications_scheduled_tasks')->default(false); + $table->boolean('smtp_notifications_status_changes')->default(false); + $table->boolean('smtp_notifications_server_disk_usage')->default(true); + }); + + $settings = DB::table('email_notification_settings')->get(); + foreach ($settings as $setting) { + try { + DB::table('teams') + ->where('id', $setting->team_id) + ->update([ + 'smtp_enabled' => $setting->smtp_enabled, + 'smtp_from_address' => $setting->smtp_from_address ? Crypt::decryptString($setting->smtp_from_address) : null, + 'smtp_from_name' => $setting->smtp_from_name ? Crypt::decryptString($setting->smtp_from_name) : null, + 'smtp_recipients' => $setting->smtp_recipients ? Crypt::decryptString($setting->smtp_recipients) : null, + 'smtp_host' => $setting->smtp_host ? Crypt::decryptString($setting->smtp_host) : null, + 'smtp_port' => $setting->smtp_port, + 'smtp_encryption' => $setting->smtp_encryption, + 'smtp_username' => $setting->smtp_username ? Crypt::decryptString($setting->smtp_username) : null, + 'smtp_password' => $setting->smtp_password, + 'smtp_timeout' => $setting->smtp_timeout, + + 'use_instance_email_settings' => $setting->use_instance_email_settings, + + 'resend_enabled' => $setting->resend_enabled, + 'resend_api_key' => $setting->resend_api_key, + + 'smtp_notifications_deployments' => $setting->deployment_success_email_notifications || $setting->deployment_failure_email_notifications, + 'smtp_notifications_database_backups' => $setting->backup_success_email_notifications || $setting->backup_failure_email_notifications, + 'smtp_notifications_scheduled_tasks' => $setting->scheduled_task_success_email_notifications || $setting->scheduled_task_failure_email_notifications, + 'smtp_notifications_status_changes' => $setting->status_change_email_notifications, + ]); + } catch (Exception $e) { + \Log::error('Error migrating email notification settings from teams table: '.$e->getMessage()); + } + } + } +}; diff --git a/database/migrations/2024_12_05_212631_migrate_discord_notification_settings_from_teams_table.php b/database/migrations/2024_12_05_212631_migrate_discord_notification_settings_from_teams_table.php new file mode 100644 index 000000000..ed9e9af82 --- /dev/null +++ b/database/migrations/2024_12_05_212631_migrate_discord_notification_settings_from_teams_table.php @@ -0,0 +1,92 @@ +get(); + + foreach ($teams as $team) { + try { + DB::table('discord_notification_settings')->updateOrInsert( + ['team_id' => $team->id], + [ + 'discord_enabled' => $team->discord_enabled ?? false, + 'discord_webhook_url' => $team->discord_webhook_url ? Crypt::encryptString($team->discord_webhook_url) : null, + + 'deployment_success_discord_notifications' => $team->discord_notifications_deployments ?? false, + 'deployment_failure_discord_notifications' => $team->discord_notifications_deployments ?? true, + 'backup_success_discord_notifications' => $team->discord_notifications_database_backups ?? false, + 'backup_failure_discord_notifications' => $team->discord_notifications_database_backups ?? true, + 'scheduled_task_success_discord_notifications' => $team->discord_notifications_scheduled_tasks ?? false, + 'scheduled_task_failure_discord_notifications' => $team->discord_notifications_scheduled_tasks ?? true, + 'status_change_discord_notifications' => $team->discord_notifications_status_changes ?? false, + 'server_disk_usage_discord_notifications' => $team->discord_notifications_server_disk_usage ?? true, + ] + ); + } catch (Exception $e) { + \Log::error('Error migrating discord notification settings from teams table: '.$e->getMessage()); + } + } + + Schema::table('teams', function (Blueprint $table) { + $table->dropColumn([ + 'discord_enabled', + 'discord_webhook_url', + 'discord_notifications_test', + 'discord_notifications_deployments', + 'discord_notifications_status_changes', + 'discord_notifications_database_backups', + 'discord_notifications_scheduled_tasks', + 'discord_notifications_server_disk_usage', + ]); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('teams', function (Blueprint $table) { + $table->boolean('discord_enabled')->default(false); + $table->string('discord_webhook_url')->nullable(); + + $table->boolean('discord_notifications_test')->default(true); + $table->boolean('discord_notifications_deployments')->default(true); + $table->boolean('discord_notifications_status_changes')->default(true); + $table->boolean('discord_notifications_database_backups')->default(true); + $table->boolean('discord_notifications_scheduled_tasks')->default(true); + $table->boolean('discord_notifications_server_disk_usage')->default(true); + }); + + $settings = DB::table('discord_notification_settings')->get(); + foreach ($settings as $setting) { + try { + DB::table('teams') + ->where('id', $setting->team_id) + ->update([ + 'discord_enabled' => $setting->discord_enabled, + 'discord_webhook_url' => Crypt::decryptString($setting->discord_webhook_url), + + 'discord_notifications_deployments' => $setting->deployment_success_discord_notifications || $setting->deployment_failure_discord_notifications, + 'discord_notifications_status_changes' => $setting->status_change_discord_notifications, + 'discord_notifications_database_backups' => $setting->backup_success_discord_notifications || $setting->backup_failure_discord_notifications, + 'discord_notifications_scheduled_tasks' => $setting->scheduled_task_success_discord_notifications || $setting->scheduled_task_failure_discord_notifications, + 'discord_notifications_server_disk_usage' => $setting->server_disk_usage_discord_notifications, + ]); + } catch (Exception $e) { + \Log::error('Error migrating discord notification settings from teams table: '.$e->getMessage()); + } + } + } +}; diff --git a/database/migrations/2024_12_05_212705_migrate_telegram_notification_settings_from_teams_table.php b/database/migrations/2024_12_05_212705_migrate_telegram_notification_settings_from_teams_table.php new file mode 100644 index 000000000..0c10646b9 --- /dev/null +++ b/database/migrations/2024_12_05_212705_migrate_telegram_notification_settings_from_teams_table.php @@ -0,0 +1,114 @@ +get(); + + foreach ($teams as $team) { + try { + DB::table('telegram_notification_settings')->updateOrInsert( + ['team_id' => $team->id], + [ + 'telegram_enabled' => $team->telegram_enabled ?? false, + 'telegram_token' => $team->telegram_token ? Crypt::encryptString($team->telegram_token) : null, + 'telegram_chat_id' => $team->telegram_chat_id ? Crypt::encryptString($team->telegram_chat_id) : null, + + 'deployment_success_telegram_notifications' => $team->telegram_notifications_deployments ?? false, + 'deployment_failure_telegram_notifications' => $team->telegram_notifications_deployments ?? true, + 'backup_success_telegram_notifications' => $team->telegram_notifications_database_backups ?? false, + 'backup_failure_telegram_notifications' => $team->telegram_notifications_database_backups ?? true, + 'scheduled_task_success_telegram_notifications' => $team->telegram_notifications_scheduled_tasks ?? false, + 'scheduled_task_failure_telegram_notifications' => $team->telegram_notifications_scheduled_tasks ?? true, + 'status_change_telegram_notifications' => $team->telegram_notifications_status_changes ?? false, + 'server_disk_usage_telegram_notifications' => $team->telegram_notifications_server_disk_usage ?? true, + + 'telegram_notifications_deployment_success_topic_id' => $team->telegram_notifications_deployments_message_thread_id ? Crypt::encryptString($team->telegram_notifications_deployments_message_thread_id) : null, + 'telegram_notifications_deployment_failure_topic_id' => $team->telegram_notifications_deployments_message_thread_id ? Crypt::encryptString($team->telegram_notifications_deployments_message_thread_id) : null, + 'telegram_notifications_backup_success_topic_id' => $team->telegram_notifications_database_backups_message_thread_id ? Crypt::encryptString($team->telegram_notifications_database_backups_message_thread_id) : null, + 'telegram_notifications_backup_failure_topic_id' => $team->telegram_notifications_database_backups_message_thread_id ? Crypt::encryptString($team->telegram_notifications_database_backups_message_thread_id) : null, + 'telegram_notifications_scheduled_task_success_topic_id' => $team->telegram_notifications_scheduled_tasks_thread_id ? Crypt::encryptString($team->telegram_notifications_scheduled_tasks_thread_id) : null, + 'telegram_notifications_scheduled_task_failure_topic_id' => $team->telegram_notifications_scheduled_tasks_thread_id ? Crypt::encryptString($team->telegram_notifications_scheduled_tasks_thread_id) : null, + 'telegram_notifications_status_change_topic_id' => $team->telegram_notifications_status_changes_message_thread_id ? Crypt::encryptString($team->telegram_notifications_status_changes_message_thread_id) : null, + ] + ); + } catch (Exception $e) { + \Log::error('Error migrating telegram notification settings from teams table: '.$e->getMessage()); + } + } + + Schema::table('teams', function (Blueprint $table) { + $table->dropColumn([ + 'telegram_enabled', + 'telegram_token', + 'telegram_chat_id', + 'telegram_notifications_test', + 'telegram_notifications_deployments', + 'telegram_notifications_status_changes', + 'telegram_notifications_database_backups', + 'telegram_notifications_scheduled_tasks', + 'telegram_notifications_server_disk_usage', + 'telegram_notifications_test_message_thread_id', + 'telegram_notifications_deployments_message_thread_id', + 'telegram_notifications_status_changes_message_thread_id', + 'telegram_notifications_database_backups_message_thread_id', + 'telegram_notifications_scheduled_tasks_thread_id', + ]); + }); + } + + public function down(): void + { + Schema::table('teams', function (Blueprint $table) { + $table->boolean('telegram_enabled')->default(false); + $table->text('telegram_token')->nullable(); + $table->text('telegram_chat_id')->nullable(); + + $table->boolean('telegram_notifications_test')->default(true); + $table->boolean('telegram_notifications_deployments')->default(true); + $table->boolean('telegram_notifications_status_changes')->default(true); + $table->boolean('telegram_notifications_database_backups')->default(true); + $table->boolean('telegram_notifications_scheduled_tasks')->default(true); + $table->boolean('telegram_notifications_server_disk_usage')->default(true); + + $table->text('telegram_notifications_test_message_thread_id')->nullable(); + $table->text('telegram_notifications_deployments_message_thread_id')->nullable(); + $table->text('telegram_notifications_status_changes_message_thread_id')->nullable(); + $table->text('telegram_notifications_database_backups_message_thread_id')->nullable(); + $table->text('telegram_notifications_scheduled_tasks_thread_id')->nullable(); + }); + + $settings = DB::table('telegram_notification_settings')->get(); + foreach ($settings as $setting) { + try { + DB::table('teams') + ->where('id', $setting->team_id) + ->update([ + 'telegram_enabled' => $setting->telegram_enabled, + 'telegram_token' => $setting->telegram_token ? Crypt::decryptString($setting->telegram_token) : null, + 'telegram_chat_id' => $setting->telegram_chat_id ? Crypt::decryptString($setting->telegram_chat_id) : null, + + 'telegram_notifications_deployments' => $setting->deployment_success_telegram_notifications || $setting->deployment_failure_telegram_notifications, + 'telegram_notifications_status_changes' => $setting->status_change_telegram_notifications, + 'telegram_notifications_database_backups' => $setting->backup_success_telegram_notifications || $setting->backup_failure_telegram_notifications, + 'telegram_notifications_scheduled_tasks' => $setting->scheduled_task_success_telegram_notifications || $setting->scheduled_task_failure_telegram_notifications, + 'telegram_notifications_server_disk_usage' => $setting->server_disk_usage_telegram_notifications, + + 'telegram_notifications_deployments_message_thread_id' => $setting->telegram_notifications_deployment_success_topic_id ? Crypt::decryptString($setting->telegram_notifications_deployment_success_topic_id) : null, + 'telegram_notifications_status_changes_message_thread_id' => $setting->telegram_notifications_status_change_topic_id ? Crypt::decryptString($setting->telegram_notifications_status_change_topic_id) : null, + 'telegram_notifications_database_backups_message_thread_id' => $setting->telegram_notifications_backup_success_topic_id ? Crypt::decryptString($setting->telegram_notifications_backup_success_topic_id) : null, + 'telegram_notifications_scheduled_tasks_thread_id' => $setting->telegram_notifications_scheduled_task_success_topic_id ? Crypt::decryptString($setting->telegram_notifications_scheduled_task_success_topic_id) : null, + ]); + } catch (Exception $e) { + \Log::error('Error migrating telegram notification settings from teams table: '.$e->getMessage()); + } + } + } +}; diff --git a/database/migrations/2024_12_06_142014_create_slack_notification_settings_table.php b/database/migrations/2024_12_06_142014_create_slack_notification_settings_table.php new file mode 100644 index 000000000..790e0f667 --- /dev/null +++ b/database/migrations/2024_12_06_142014_create_slack_notification_settings_table.php @@ -0,0 +1,57 @@ +id(); + $table->foreignId('team_id')->constrained()->cascadeOnDelete(); + + $table->boolean('slack_enabled')->default(false); + $table->text('slack_webhook_url')->nullable(); + + $table->boolean('deployment_success_slack_notifications')->default(false); + $table->boolean('deployment_failure_slack_notifications')->default(true); + $table->boolean('status_change_slack_notifications')->default(false); + $table->boolean('backup_success_slack_notifications')->default(false); + $table->boolean('backup_failure_slack_notifications')->default(true); + $table->boolean('scheduled_task_success_slack_notifications')->default(false); + $table->boolean('scheduled_task_failure_slack_notifications')->default(true); + $table->boolean('docker_cleanup_success_slack_notifications')->default(false); + $table->boolean('docker_cleanup_failure_slack_notifications')->default(true); + $table->boolean('server_disk_usage_slack_notifications')->default(true); + $table->boolean('server_reachable_slack_notifications')->default(false); + $table->boolean('server_unreachable_slack_notifications')->default(true); + + $table->unique(['team_id']); + }); + $teams = DB::table('teams')->get(); + + foreach ($teams as $team) { + try { + DB::table('slack_notification_settings')->insert([ + 'team_id' => $team->id, + ]); + } catch (\Throwable $e) { + \Log::error('Error migrating slack notification settings from teams table: '.$e->getMessage()); + } + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('slack_notification_settings'); + } +}; diff --git a/database/migrations/2024_12_09_105711_drop_waitlists_table.php b/database/migrations/2024_12_09_105711_drop_waitlists_table.php new file mode 100644 index 000000000..0e319369d --- /dev/null +++ b/database/migrations/2024_12_09_105711_drop_waitlists_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('uuid'); + $table->string('type'); + $table->string('email')->unique(); + $table->boolean('verified')->default(false); + $table->timestamps(); + }); + } +}; diff --git a/database/migrations/2024_12_10_122142_encrypt_instance_settings_email_columns.php b/database/migrations/2024_12_10_122142_encrypt_instance_settings_email_columns.php new file mode 100644 index 000000000..44e0f2f47 --- /dev/null +++ b/database/migrations/2024_12_10_122142_encrypt_instance_settings_email_columns.php @@ -0,0 +1,72 @@ +exists()) { + Schema::table('instance_settings', function (Blueprint $table) { + $table->text('smtp_from_address')->nullable()->change(); + $table->text('smtp_from_name')->nullable()->change(); + $table->text('smtp_recipients')->nullable()->change(); + $table->text('smtp_host')->nullable()->change(); + $table->text('smtp_username')->nullable()->change(); + }); + + $settings = DB::table('instance_settings')->get(); + foreach ($settings as $setting) { + try { + DB::table('instance_settings')->where('id', $setting->id)->update([ + 'smtp_from_address' => $setting->smtp_from_address ? Crypt::encryptString($setting->smtp_from_address) : null, + 'smtp_from_name' => $setting->smtp_from_name ? Crypt::encryptString($setting->smtp_from_name) : null, + 'smtp_recipients' => $setting->smtp_recipients ? Crypt::encryptString($setting->smtp_recipients) : null, + 'smtp_host' => $setting->smtp_host ? Crypt::encryptString($setting->smtp_host) : null, + 'smtp_username' => $setting->smtp_username ? Crypt::encryptString($setting->smtp_username) : null, + ]); + } catch (Exception $e) { + \Log::error('Error encrypting instance settings email columns: '.$e->getMessage()); + } + } + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('instance_settings', function (Blueprint $table) { + $table->string('smtp_from_address')->nullable()->change(); + $table->string('smtp_from_name')->nullable()->change(); + $table->string('smtp_recipients')->nullable()->change(); + $table->string('smtp_host')->nullable()->change(); + $table->string('smtp_username')->nullable()->change(); + }); + + if (DB::table('instance_settings')->exists()) { + $settings = DB::table('instance_settings')->get(); + foreach ($settings as $setting) { + try { + DB::table('instance_settings')->where('id', $setting->id)->update([ + 'smtp_from_address' => $setting->smtp_from_address ? Crypt::decryptString($setting->smtp_from_address) : null, + 'smtp_from_name' => $setting->smtp_from_name ? Crypt::decryptString($setting->smtp_from_name) : null, + 'smtp_recipients' => $setting->smtp_recipients ? Crypt::decryptString($setting->smtp_recipients) : null, + 'smtp_host' => $setting->smtp_host ? Crypt::decryptString($setting->smtp_host) : null, + 'smtp_username' => $setting->smtp_username ? Crypt::decryptString($setting->smtp_username) : null, + ]); + } catch (Exception $e) { + \Log::error('Error decrypting instance settings email columns: '.$e->getMessage()); + } + } + } + } +}; diff --git a/database/migrations/2024_12_10_122143_drop_resale_license.php b/database/migrations/2024_12_10_122143_drop_resale_license.php new file mode 100644 index 000000000..aaf498c3b --- /dev/null +++ b/database/migrations/2024_12_10_122143_drop_resale_license.php @@ -0,0 +1,30 @@ +dropColumn('is_resale_license_active'); + $table->dropColumn('resale_license'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('instance_settings', function (Blueprint $table) { + $table->boolean('is_resale_license_active')->default(false); + $table->longText('resale_license')->nullable(); + }); + } +}; diff --git a/database/seeders/InstanceSettingsSeeder.php b/database/seeders/InstanceSettingsSeeder.php index 35fc8506b..7f2deb3a6 100644 --- a/database/seeders/InstanceSettingsSeeder.php +++ b/database/seeders/InstanceSettingsSeeder.php @@ -16,7 +16,6 @@ class InstanceSettingsSeeder extends Seeder InstanceSettings::create([ 'id' => 0, 'is_registration_enabled' => true, - 'is_resale_license_active' => true, 'smtp_enabled' => true, 'smtp_host' => 'coolify-mail', 'smtp_port' => 1025, diff --git a/database/seeders/TestTeamSeeder.php b/database/seeders/TestTeamSeeder.php deleted file mode 100644 index 940c45cc5..000000000 --- a/database/seeders/TestTeamSeeder.php +++ /dev/null @@ -1,42 +0,0 @@ -create([ - 'name' => '1 personal, 1 other team, owner, no other members', - 'email' => '1@example.com', - ]); - $team = Team::create([ - 'name' => '1@example.com', - 'personal_team' => false, - 'show_boarding' => true, - ]); - $user->teams()->attach($team, ['role' => 'owner']); - - // User has 2 teams, 1 personal, 1 other where it is the owner and 1 other member is in the team - $user = User::factory()->create([ - 'name' => 'owner: 1 personal, 1 other team, owner, 1 other member', - 'email' => '2@example.com', - ]); - $team = Team::create([ - 'name' => '2@example.com', - 'personal_team' => false, - 'show_boarding' => true, - ]); - $user->teams()->attach($team, ['role' => 'owner']); - $user = User::factory()->create([ - 'name' => 'member: 1 personal, 1 other team, owner, 1 other member', - 'email' => '3@example.com', - ]); - $team->members()->attach($user, ['role' => 'member']); - } -} diff --git a/resources/views/auth/forgot-password.blade.php b/resources/views/auth/forgot-password.blade.php index 00cf95a44..a61a8fb32 100644 --- a/resources/views/auth/forgot-password.blade.php +++ b/resources/views/auth/forgot-password.blade.php @@ -9,7 +9,7 @@
- @if (is_transactional_emails_active()) + @if (is_transactional_emails_enabled())
@csrf diff --git a/resources/views/emails/docker-cleanup-failed.blade.php b/resources/views/emails/docker-cleanup-failed.blade.php new file mode 100644 index 000000000..a12f20fa8 --- /dev/null +++ b/resources/views/emails/docker-cleanup-failed.blade.php @@ -0,0 +1,8 @@ + +Docker Cleanup on {{ $name }} FAILED with the following error: + +
+{{ $text }}
+
+ +
diff --git a/resources/views/emails/docker-cleanup-success.blade.php b/resources/views/emails/docker-cleanup-success.blade.php new file mode 100644 index 000000000..8671d74c8 --- /dev/null +++ b/resources/views/emails/docker-cleanup-success.blade.php @@ -0,0 +1,9 @@ + +Docker Cleanup on {{ $name }} succeeded with the following message: + + +
+{{ $text }}
+
+ +
diff --git a/resources/views/emails/scheduled-task-success.blade.php b/resources/views/emails/scheduled-task-success.blade.php new file mode 100644 index 000000000..44ef8fa58 --- /dev/null +++ b/resources/views/emails/scheduled-task-success.blade.php @@ -0,0 +1,9 @@ + +Scheduled task ({{ $task->name }}) completed successfully with the following output: + +
+{{ $output }}
+
+ +Click [here]({{ $url }}) to view the task. +
diff --git a/resources/views/emails/waitlist-confirmation.blade.php b/resources/views/emails/waitlist-confirmation.blade.php deleted file mode 100644 index afd22916a..000000000 --- a/resources/views/emails/waitlist-confirmation.blade.php +++ /dev/null @@ -1,7 +0,0 @@ - -Someone added this email to the Coolify Cloud's waitlist. [Click here]({{ $confirmation_url }}) to confirm! - -The link will expire in {{ config('constants.waitlist.expiration') }} minutes. - -You have no idea what [Coolify Cloud](https://coolify.io) is or this waitlist? [Click here]({{ $cancel_url }}) to remove you from the waitlist. - diff --git a/resources/views/emails/waitlist-invitation.blade.php b/resources/views/emails/waitlist-invitation.blade.php deleted file mode 100644 index de8d64650..000000000 --- a/resources/views/emails/waitlist-invitation.blade.php +++ /dev/null @@ -1,3 +0,0 @@ - -You have been invited to join the Coolify Cloud: [Get Started]({{ $loginLink }}) - diff --git a/resources/views/livewire/notifications/discord.blade.php b/resources/views/livewire/notifications/discord.blade.php index af6f98b0a..1f83ed061 100644 --- a/resources/views/livewire/notifications/discord.blade.php +++ b/resources/views/livewire/notifications/discord.blade.php @@ -3,7 +3,7 @@ Notifications | Coolify - +

Discord

@@ -12,7 +12,11 @@ @if ($discordEnabled) - Send Test Notifications + Send Test Notification + + @else + + Send Test Notification @endif
@@ -23,21 +27,55 @@ helper="Generate a webhook in Discord.
Example: https://discord.com/api/webhooks/...." required id="discordWebhookUrl" label="Webhook" /> - @if ($discordEnabled) -

Subscribe to events

-
- @if (isDev()) - - @endif - - - - - +

Notification Settings

+

+ Select events for which you would like to receive Discord notifications. +

+
+
+

Deployments

+
+ + + +
- @endif +
+

Backups

+
+ + +
+
+
+

Scheduled Tasks

+
+ + +
+
+
+

Server

+
+ + + + + +
+
+
diff --git a/resources/views/livewire/notifications/email.blade.php b/resources/views/livewire/notifications/email.blade.php index fba21d0b2..1977df516 100644 --- a/resources/views/livewire/notifications/email.blade.php +++ b/resources/views/livewire/notifications/email.blade.php @@ -9,26 +9,27 @@ Save - @if (isInstanceAdmin() && !$useInstanceEmailSettings) - - Copy from Instance Settings - - @endif - @if (isEmailEnabled($team) && auth()->user()->isAdminFromSession() && isTestEmailEnabled($team)) - -
- - - Send Email - - -
+ @if (auth()->user()->isAdminFromSession()) + @if ($team->isNotificationEnabled('email')) + +
+ + + Send Email + + +
+ @else + + Send Test Email + + @endif @endif
@if (!isCloud())
-
@endif @@ -38,17 +39,22 @@
+ @if (isInstanceAdmin() && !$useInstanceEmailSettings) + + Copy from Instance Settings + + @endif @endif @if (isCloud())
-
@endif @if (!$useInstanceEmailSettings)
-
+

SMTP Server

@@ -56,7 +62,8 @@
- +
@@ -78,7 +85,7 @@
-
+

Resend

@@ -86,7 +93,8 @@
- +
@@ -99,20 +107,55 @@
@endif - @if (isEmailEnabled($team) || $useInstanceEmailSettings) -

Subscribe to events

-
- @if (isDev()) - - @endif - - - - - +

Notification Settings

+

+ Select events for which you would like to receive email notifications. +

+
+
+

Deployments

+
+ + + +
- @endif +
+

Backups

+
+ + +
+
+
+

Scheduled Tasks

+
+ + +
+
+
+

Server

+
+ + + + + +
+
+
diff --git a/resources/views/livewire/notifications/slack.blade.php b/resources/views/livewire/notifications/slack.blade.php index b3685173c..774f8e7e5 100644 --- a/resources/views/livewire/notifications/slack.blade.php +++ b/resources/views/livewire/notifications/slack.blade.php @@ -1,43 +1,79 @@
Notifications | Coolify - - -
-
-

Slack

- - Save + + + +
+

Slack

+ + Save + + @if ($slackEnabled) + + Send Test Notification - @if ($slackEnabled) - - Send Test Notifications - - @endif + @else + + Send Test Notification + + @endif +
+
+ +
+ + +

Notification Settings

+

+ Select events for which you would like to receive Slack notifications. +

+
+
+

Deployments

+
+ + +
-
- +
+
+

Backups

+
+ +
- - - @if ($slackEnabled) -

Subscribe to events

-
- @if (isDev()) - - @endif - - - - - +
+

Scheduled Tasks

+
+ + +
+
+
+

Server

+
+ + + + +
- @endif -
\ No newline at end of file +
+
+
diff --git a/resources/views/livewire/notifications/telegram.blade.php b/resources/views/livewire/notifications/telegram.blade.php index 76378ada1..5dd859e9e 100644 --- a/resources/views/livewire/notifications/telegram.blade.php +++ b/resources/views/livewire/notifications/telegram.blade.php @@ -3,7 +3,7 @@ Notifications | Coolify -
+

Telegram

@@ -12,7 +12,11 @@ @if ($telegramEnabled) - Send Test Notifications + Send Test Notification + + @else + + Send Test Notification @endif
@@ -20,61 +24,143 @@
-
- @if ($telegramEnabled) -

Subscribe to events

-
- @if (isDev()) -
-

Test Notification

- - + +

Notification Settings

+

+ Select events for which you would like to receive Telegram notifications. +

+
+
+

Deployments

+
+
+
+
- @endif -
-

Container Status Changes

- - +
-
-

Application Deployments

- - +
+
+ +
+
-
-

Database Backup Status

- - -
-
-

Scheduled Tasks Status

- - -
-
-

Server Disk Usage

- +
+
+ +
+
- @endif - +
+
+

Backups

+
+
+
+ +
+ +
+ +
+
+ +
+ +
+
+
+ +
+

Scheduled Tasks

+
+
+
+ +
+ +
+ +
+
+ +
+ +
+
+
+ +
+

Server

+
+
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ + +
+
+ +
+ +
+
+
+
diff --git a/resources/views/livewire/settings-email.blade.php b/resources/views/livewire/settings-email.blade.php index 141922584..19c77971c 100644 --- a/resources/views/livewire/settings-email.blade.php +++ b/resources/views/livewire/settings-email.blade.php @@ -9,17 +9,27 @@ Save + @if (is_transactional_emails_enabled() && auth()->user()->isAdminFromSession()) + +
+ + + Send Email + + +
+ @endif
-
Email settings for password resets, invitations, etc.
-
+
Instance wide email settings for password resets, invitations, etc.
+
-
-
+

SMTP Server

@@ -49,7 +59,7 @@
-
+

Resend

diff --git a/resources/views/livewire/subscription/index.blade.php b/resources/views/livewire/subscription/index.blade.php index 5131ebd56..c29fdae89 100644 --- a/resources/views/livewire/subscription/index.blade.php +++ b/resources/views/livewire/subscription/index.blade.php @@ -2,42 +2,38 @@ Subscribe | Coolify - @if ($settings->is_resale_license_active) - @if (auth()->user()->isAdminFromSession()) -
-
-

Subscriptions

- @if (subscriptionProvider() === 'stripe' && $alreadySubscribed) - Manage My Subscription - @endif -
- @if (request()->query->get('cancelled')) -
- - - - Something went wrong with your subscription. Please try again or contact - support. -
+ @if (auth()->user()->isAdminFromSession()) +
+
+

Subscriptions

+ @if (subscriptionProvider() === 'stripe' && $alreadySubscribed) + Manage My Subscription @endif +
+ @if (request()->query->get('cancelled')) +
+ + + + Something went wrong with your subscription. Please try again or contact + support. +
+ @endif - @if (config('subscription.provider') === 'stripe') - - @endif -
- @else -
-
-

Subscription

-
-
You are not an admin or have been removed from this team. If this does not make sense, please contact - us.
-
- @endif + @if (config('subscription.provider') === 'stripe') + + @endif +
@else -
Resale license is not active. Please contact your instance admin.
+
+
+

Subscription

+
+
You are not an admin or have been removed from this team. If this does not make sense, please contact + us.
+
@endif
diff --git a/resources/views/livewire/team/invite-link.blade.php b/resources/views/livewire/team/invite-link.blade.php index 2e0f02078..1f8702b6f 100644 --- a/resources/views/livewire/team/invite-link.blade.php +++ b/resources/views/livewire/team/invite-link.blade.php @@ -11,7 +11,7 @@
Generate Invitation Link - @if (is_transactional_emails_active()) + @if (is_transactional_emails_enabled()) Send Invitation via Email @endif
diff --git a/resources/views/livewire/team/member/index.blade.php b/resources/views/livewire/team/member/index.blade.php index f756414b6..122c03402 100644 --- a/resources/views/livewire/team/member/index.blade.php +++ b/resources/views/livewire/team/member/index.blade.php @@ -33,7 +33,7 @@
@if (auth()->user()->isAdminFromSession())
- @if (is_transactional_emails_active()) + @if (is_transactional_emails_enabled())

Invite New Member

@else

Invite New Member

diff --git a/resources/views/livewire/waitlist/index.blade.php b/resources/views/livewire/waitlist/index.blade.php deleted file mode 100644 index 548e722fc..000000000 --- a/resources/views/livewire/waitlist/index.blade.php +++ /dev/null @@ -1,37 +0,0 @@ -
-
- -
-

Self-hosting in the cloud - - - - - - -

-
- - - Join Waitlist - -
People waiting in the line: {{ $waitingInLine }}
-
Already using Coolify Cloud: {{ $users }}
-
- This is a paid & hosted version of Coolify.
See the pricing here. -
-
- If you are looking for the self-hosted version go here. -
-
-
diff --git a/routes/web.php b/routes/web.php index f7bec5be5..3570fe0ed 100644 --- a/routes/web.php +++ b/routes/web.php @@ -88,7 +88,6 @@ Route::get('/admin', AdminIndex::class)->name('admin.index'); Route::post('/forgot-password', [Controller::class, 'forgot_password'])->name('password.forgot')->middleware('throttle:forgot-password'); Route::get('/realtime', [Controller::class, 'realtime_test'])->middleware('auth'); -// Route::get('/waitlist', WaitlistIndex::class)->name('waitlist.index'); Route::get('/verify', [Controller::class, 'verify'])->middleware('auth')->name('verify.email'); Route::get('/email/verify/{id}/{hash}', [Controller::class, 'email_verify'])->middleware(['auth'])->name('verify.verify'); Route::middleware(['throttle:login'])->group(function () { diff --git a/routes/webhooks.php b/routes/webhooks.php index ed5c2e233..d8d8e094a 100644 --- a/routes/webhooks.php +++ b/routes/webhooks.php @@ -5,7 +5,6 @@ use App\Http\Controllers\Webhook\Gitea; use App\Http\Controllers\Webhook\Github; use App\Http\Controllers\Webhook\Gitlab; use App\Http\Controllers\Webhook\Stripe; -use App\Http\Controllers\Webhook\Waitlist; use Illuminate\Support\Facades\Route; Route::get('/source/github/redirect', [Github::class, 'redirect']); @@ -20,6 +19,3 @@ Route::post('/source/bitbucket/events/manual', [Bitbucket::class, 'manual']); Route::post('/source/gitea/events/manual', [Gitea::class, 'manual']); Route::post('/payments/stripe/events', [Stripe::class, 'events']); - -Route::get('/waitlist/confirm', [Waitlist::class, 'confirm'])->name('webhooks.waitlist.confirm'); -Route::get('/waitlist/cancel', [Waitlist::class, 'cancel'])->name('webhooks.waitlist.cancel'); diff --git a/scripts/run b/scripts/run index 8764ef23f..466b04530 100755 --- a/scripts/run +++ b/scripts/run @@ -32,18 +32,18 @@ function sync:bunny { } function db:reset { - bash spin exec -u webuser coolify php artisan migrate:fresh --seed + bash spin exec -u www-data coolify php artisan migrate:fresh --seed } function db:reset-prod { - bash spin exec -u webuser coolify php artisan migrate:fresh --force --seed --seeder=ProductionSeeder || + bash spin exec -u www-data coolify php artisan migrate:fresh --force --seed --seeder=ProductionSeeder || php artisan migrate:fresh --force --seed --seeder=ProductionSeeder } function coolify { - bash spin exec -u webuser coolify bash + bash spin exec -u www-data coolify sh } function coolify:root { - bash spin exec coolify bash + bash spin exec coolify sh } function coolify:proxy { docker exec -ti coolify-proxy sh @@ -58,7 +58,7 @@ function vite { } function tinker { - bash spin exec -u webuser coolify php artisan tinker + bash spin exec -u www-data coolify php artisan tinker } function default { @@ -66,4 +66,4 @@ function default { } TIMEFORMAT="Task completed in %3lR" -time "${@:-default}" \ No newline at end of file +time "${@:-default}"