diff --git a/app/Http/Livewire/Notifications/DiscordSettings.php b/app/Http/Livewire/Notifications/DiscordSettings.php index ac64c9184..03cac4753 100644 --- a/app/Http/Livewire/Notifications/DiscordSettings.php +++ b/app/Http/Livewire/Notifications/DiscordSettings.php @@ -15,6 +15,7 @@ class DiscordSettings extends Component 'model.discord_notifications_test' => 'nullable|boolean', 'model.discord_notifications_deployments' => 'nullable|boolean', 'model.discord_notifications_status_changes' => 'nullable|boolean', + 'model.discord_notifications_database_backups' => 'nullable|boolean', ]; protected $validationAttributes = [ 'model.discord_webhook_url' => 'Discord Webhook', diff --git a/app/Http/Livewire/Notifications/EmailSettings.php b/app/Http/Livewire/Notifications/EmailSettings.php index 8c7389afd..eceb4e88b 100644 --- a/app/Http/Livewire/Notifications/EmailSettings.php +++ b/app/Http/Livewire/Notifications/EmailSettings.php @@ -26,6 +26,7 @@ class EmailSettings extends Component 'model.smtp_notifications_test' => 'nullable|boolean', 'model.smtp_notifications_deployments' => 'nullable|boolean', 'model.smtp_notifications_status_changes' => 'nullable|boolean', + 'model.smtp_notifications_database_backups' => 'nullable|boolean', ]; protected $validationAttributes = [ 'model.smtp_from_address' => 'From Address', diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php index ddb5f743f..82dd85623 100644 --- a/app/Jobs/DatabaseBackupJob.php +++ b/app/Jobs/DatabaseBackupJob.php @@ -8,6 +8,8 @@ use App\Models\ScheduledDatabaseBackupExecution; use App\Models\Server; use App\Models\StandalonePostgresql; use App\Models\Team; +use App\Notifications\Database\BackupFailed; +use App\Notifications\Database\BackupSuccess; use Carbon\Carbon; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -73,10 +75,10 @@ class DatabaseBackupJob implements ShouldQueue } $this->calculate_size(); $this->remove_old_backups(); - $this->save_backup_logs(); if ($this->backup->save_s3) { - $this->upload_to_s3(); +// $this->upload_to_s3(); } + $this->save_backup_logs(); // TODO: Notify user } @@ -98,10 +100,13 @@ class DatabaseBackupJob implements ShouldQueue ray('Backup done for ' . $this->database->uuid . ' at ' . $this->server->name . ':' . $this->backup_filename); $this->backup_status = 'success'; + throw new \Error('test'); + $this->team->notify(new BackupSuccess($this->backup, $this->database)); } catch (Throwable $th) { $this->backup_status = 'failed'; $this->add_to_backup_output($th->getMessage()); ray('Backup failed for ' . $this->database->uuid . ' at ' . $this->server->name . ':' . $this->backup_filename . '\n\nError:' . $th->getMessage()); + $this->team->notify(new BackupFailed($this->backup, $this->database)); } finally { $this->backup_log->update([ 'status' => $this->backup_status, @@ -130,7 +135,6 @@ class DatabaseBackupJob implements ShouldQueue } else { $deletable = $this->backup->executions()->where('status', 'success')->orderByDesc('created_at')->skip($this->backup->number_of_backups_locally); } - ray($deletable->get()); foreach ($deletable->get() as $execution) { delete_backup_locally($execution->filename, $this->server); $execution->delete(); @@ -159,13 +163,15 @@ class DatabaseBackupJob implements ShouldQueue $bucket = $this->s3->bucket; $endpoint = $this->s3->endpoint; $backup_dir = backup_dir() . "/{$this->database->uuid}"; + $base_command = "docker run -t --network {$this->database->destination->network} -v {$this->backup_filename}:{$this->backup_filename}:ro --rm --entrypoint=/bin/sh minio/mc -c \"mc config host add temporary {$endpoint} $key $secret && mc cp $this->backup_filename temporary/$bucket/$backup_dir/ \""; + instant_remote_process([$base_command], $this->server); + $this->add_to_backup_output('Uploaded to S3.'); } catch (\Throwable $th) { $this->add_to_backup_output($th->getMessage()); ray($th->getMessage()); - //throw $th; } } } diff --git a/app/Notifications/Database/BackupFailed.php b/app/Notifications/Database/BackupFailed.php new file mode 100644 index 000000000..839ec0da9 --- /dev/null +++ b/app/Notifications/Database/BackupFailed.php @@ -0,0 +1,55 @@ +message = "❌ Database backup for {$database->name} with frequency of $backup->frequency was FAILED."; + } + + public function via(object $notifiable): array + { + $channels = []; + $isEmailEnabled = data_get($notifiable, 'smtp_enabled'); + $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); + $isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_database_backups'); + $isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_database_backups'); + + if ($isEmailEnabled && $isSubscribedToEmailEvent) { + $channels[] = EmailChannel::class; + } + if ($isDiscordEnabled && $isSubscribedToDiscordEvent) { + $channels[] = DiscordChannel::class; + } + ray($channels); + return $channels; + } + + public function toMail(): MailMessage + { + $mail = new MailMessage(); + $mail->subject("❌ Backup FAILED for {$this->database->name}"); + $mail->line($this->message); + return $mail; + } + + public function toDiscord(): string + { + return $this->message; + } +} diff --git a/app/Notifications/Database/BackupSuccess.php b/app/Notifications/Database/BackupSuccess.php new file mode 100644 index 000000000..36c8961ba --- /dev/null +++ b/app/Notifications/Database/BackupSuccess.php @@ -0,0 +1,55 @@ +message = "✅ Database backup for {$database->name} with frequency of $backup->frequency was successful."; + } + + public function via(object $notifiable): array + { + $channels = []; + $isEmailEnabled = data_get($notifiable, 'smtp_enabled'); + $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); + $isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_database_backups'); + $isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_database_backups'); + + if ($isEmailEnabled && $isSubscribedToEmailEvent) { + $channels[] = EmailChannel::class; + } + if ($isDiscordEnabled && $isSubscribedToDiscordEvent) { + $channels[] = DiscordChannel::class; + } + ray($channels); + return $channels; + } + + public function toMail(): MailMessage + { + $mail = new MailMessage(); + $mail->subject("✅ Backup success for {$this->database->name}"); + $mail->line($this->message); + return $mail; + } + + public function toDiscord(): string + { + return $this->message; + } +} diff --git a/app/Notifications/TransactionalEmails/InvitationLink.php b/app/Notifications/TransactionalEmails/InvitationLink.php index f08c36fbc..409234fd5 100644 --- a/app/Notifications/TransactionalEmails/InvitationLink.php +++ b/app/Notifications/TransactionalEmails/InvitationLink.php @@ -15,7 +15,7 @@ class InvitationLink extends Notification implements ShouldQueue { use Queueable; - public function via() + public function via(): array { return [TransactionalEmailChannel::class]; } diff --git a/config/coolify.php b/config/coolify.php index 459fefcc6..b0ae2ea2d 100644 --- a/config/coolify.php +++ b/config/coolify.php @@ -9,6 +9,6 @@ return [ 'lemon_squeezy_checkout_id_3' => env('LEMON_SQUEEZY_CHECKOUT_ID_3'), 'mux_enabled' => env('MUX_ENABLED', true), 'dev_webhook' => env('SERVEO_URL'), - 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), + 'base_config_path' => env('BASE_CONFIG_PATH', '/_data/coolify'), 'dev_config_path' => env('DEV_CONFIG_PATH', './_data/coolify'), ]; diff --git a/database/migrations/2023_08_10_201311_add_backup_notifications_to_teams.php b/database/migrations/2023_08_10_201311_add_backup_notifications_to_teams.php new file mode 100644 index 000000000..865403d8e --- /dev/null +++ b/database/migrations/2023_08_10_201311_add_backup_notifications_to_teams.php @@ -0,0 +1,23 @@ +boolean('smtp_notifications_database_backups')->default(true); + $table->boolean('discord_notifications_database_backups')->default(true); + }); + } + + public function down(): void + { + Schema::table('teams', function (Blueprint $table) { + $table->dropColumn('smtp_notifications_database_backups'); + $table->dropColumn('discord_notifications_database_backups'); + }); + } +}; diff --git a/resources/views/livewire/notifications/discord-settings.blade.php b/resources/views/livewire/notifications/discord-settings.blade.php index 9ec66ea1c..ba6f182a4 100644 --- a/resources/views/livewire/notifications/discord-settings.blade.php +++ b/resources/views/livewire/notifications/discord-settings.blade.php @@ -21,16 +21,20 @@ id="model.discord_webhook_url" label="Webhook"/> @if (data_get($model, 'discord_enabled')) -

Subscribe to events

-
+

Subscribe to events

+
@if (is_dev()) @endif -
Applications
+

General

+ +

Applications

- +

Databases

+
@endif
diff --git a/resources/views/livewire/notifications/email-settings.blade.php b/resources/views/livewire/notifications/email-settings.blade.php index 09d0f5acb..775694564 100644 --- a/resources/views/livewire/notifications/email-settings.blade.php +++ b/resources/views/livewire/notifications/email-settings.blade.php @@ -63,10 +63,15 @@ @if (is_dev()) @endif -
Applications
- +

General

+ label="Container Status Changes"/> +

Applications

+ +

Databases

+ @endif diff --git a/resources/views/vendor/mail/html/button.blade.php b/resources/views/vendor/mail/html/button.blade.php new file mode 100644 index 000000000..4a9bf7d00 --- /dev/null +++ b/resources/views/vendor/mail/html/button.blade.php @@ -0,0 +1,24 @@ +@props([ + 'url', + 'color' => 'primary', + 'align' => 'center', +]) + + + + + diff --git a/resources/views/vendor/mail/html/footer.blade.php b/resources/views/vendor/mail/html/footer.blade.php new file mode 100644 index 000000000..3ff41f89c --- /dev/null +++ b/resources/views/vendor/mail/html/footer.blade.php @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/resources/views/vendor/mail/html/header.blade.php b/resources/views/vendor/mail/html/header.blade.php new file mode 100644 index 000000000..6e30a3df9 --- /dev/null +++ b/resources/views/vendor/mail/html/header.blade.php @@ -0,0 +1,12 @@ +@props(['url']) + + + + @if (trim($slot) === 'Laravel') + + @else + {{ $slot }} + @endif + + + diff --git a/resources/views/vendor/mail/html/layout.blade.php b/resources/views/vendor/mail/html/layout.blade.php new file mode 100644 index 000000000..e55f6a6d9 --- /dev/null +++ b/resources/views/vendor/mail/html/layout.blade.php @@ -0,0 +1,57 @@ + + + +{{ config('app.name') }} + + + + + + + + + + + + + + + diff --git a/resources/views/vendor/mail/html/message.blade.php b/resources/views/vendor/mail/html/message.blade.php new file mode 100644 index 000000000..f272460da --- /dev/null +++ b/resources/views/vendor/mail/html/message.blade.php @@ -0,0 +1,27 @@ + +{{-- Header --}} + + +{{ config('app.name') }} + + + +{{-- Body --}} +{{ $slot }} + +{{-- Subcopy --}} +@isset($subcopy) + + +{{ $subcopy }} + + +@endisset + +{{-- Footer --}} + + +© {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.') + + + diff --git a/resources/views/vendor/mail/html/panel.blade.php b/resources/views/vendor/mail/html/panel.blade.php new file mode 100644 index 000000000..2975a60a0 --- /dev/null +++ b/resources/views/vendor/mail/html/panel.blade.php @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/views/vendor/mail/html/subcopy.blade.php b/resources/views/vendor/mail/html/subcopy.blade.php new file mode 100644 index 000000000..790ce6c24 --- /dev/null +++ b/resources/views/vendor/mail/html/subcopy.blade.php @@ -0,0 +1,7 @@ + + + + + diff --git a/resources/views/vendor/mail/html/table.blade.php b/resources/views/vendor/mail/html/table.blade.php new file mode 100644 index 000000000..a5f3348b2 --- /dev/null +++ b/resources/views/vendor/mail/html/table.blade.php @@ -0,0 +1,3 @@ +
+{{ Illuminate\Mail\Markdown::parse($slot) }} +
diff --git a/resources/views/vendor/mail/html/themes/default.css b/resources/views/vendor/mail/html/themes/default.css new file mode 100644 index 000000000..03d811653 --- /dev/null +++ b/resources/views/vendor/mail/html/themes/default.css @@ -0,0 +1,290 @@ +/* Base */ + +body, +body *:not(html):not(style):not(br):not(tr):not(code) { + box-sizing: border-box; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, + 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; + position: relative; +} + +body { + -webkit-text-size-adjust: none; + background-color: #ffffff; + color: #718096; + height: 100%; + line-height: 1.4; + margin: 0; + padding: 0; + width: 100% !important; +} + +p, +ul, +ol, +blockquote { + line-height: 1.4; + text-align: left; +} + +a { + color: #3869d4; +} + +a img { + border: none; +} + +/* Typography */ + +h1 { + color: #3d4852; + font-size: 18px; + font-weight: bold; + margin-top: 0; + text-align: left; +} + +h2 { + font-size: 16px; + font-weight: bold; + margin-top: 0; + text-align: left; +} + +h3 { + font-size: 14px; + font-weight: bold; + margin-top: 0; + text-align: left; +} + +p { + font-size: 16px; + line-height: 1.5em; + margin-top: 0; + text-align: left; +} + +p.sub { + font-size: 12px; +} + +img { + max-width: 100%; +} + +/* Layout */ + +.wrapper { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; + background-color: #edf2f7; + margin: 0; + padding: 0; + width: 100%; +} + +.content { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; + margin: 0; + padding: 0; + width: 100%; +} + +/* Header */ + +.header { + padding: 25px 0; + text-align: center; +} + +.header a { + color: #3d4852; + font-size: 19px; + font-weight: bold; + text-decoration: none; +} + +/* Logo */ + +.logo { + height: 75px; + max-height: 75px; + width: 75px; +} + +/* Body */ + +.body { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; + background-color: #edf2f7; + border-bottom: 1px solid #edf2f7; + border-top: 1px solid #edf2f7; + margin: 0; + padding: 0; + width: 100%; +} + +.inner-body { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 570px; + background-color: #ffffff; + border-color: #e8e5ef; + border-radius: 2px; + border-width: 1px; + box-shadow: 0 2px 0 rgba(0, 0, 150, 0.025), 2px 4px 0 rgba(0, 0, 150, 0.015); + margin: 0 auto; + padding: 0; + width: 570px; +} + +/* Subcopy */ + +.subcopy { + border-top: 1px solid #e8e5ef; + margin-top: 25px; + padding-top: 25px; +} + +.subcopy p { + font-size: 14px; +} + +/* Footer */ + +.footer { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 570px; + margin: 0 auto; + padding: 0; + text-align: center; + width: 570px; +} + +.footer p { + color: #b0adc5; + font-size: 12px; + text-align: center; +} + +.footer a { + color: #b0adc5; + text-decoration: underline; +} + +/* Tables */ + +.table table { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; + margin: 30px auto; + width: 100%; +} + +.table th { + border-bottom: 1px solid #edeff2; + margin: 0; + padding-bottom: 8px; +} + +.table td { + color: #74787e; + font-size: 15px; + line-height: 18px; + margin: 0; + padding: 10px 0; +} + +.content-cell { + max-width: 100vw; + padding: 32px; +} + +/* Buttons */ + +.action { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; + margin: 30px auto; + padding: 0; + text-align: center; + width: 100%; +} + +.button { + -webkit-text-size-adjust: none; + border-radius: 4px; + color: #fff; + display: inline-block; + overflow: hidden; + text-decoration: none; +} + +.button-blue, +.button-primary { + background-color: #2d3748; + border-bottom: 8px solid #2d3748; + border-left: 18px solid #2d3748; + border-right: 18px solid #2d3748; + border-top: 8px solid #2d3748; +} + +.button-green, +.button-success { + background-color: #48bb78; + border-bottom: 8px solid #48bb78; + border-left: 18px solid #48bb78; + border-right: 18px solid #48bb78; + border-top: 8px solid #48bb78; +} + +.button-red, +.button-error { + background-color: #e53e3e; + border-bottom: 8px solid #e53e3e; + border-left: 18px solid #e53e3e; + border-right: 18px solid #e53e3e; + border-top: 8px solid #e53e3e; +} + +/* Panels */ + +.panel { + border-left: #2d3748 solid 4px; + margin: 21px 0; +} + +.panel-content { + background-color: #edf2f7; + color: #718096; + padding: 16px; +} + +.panel-content p { + color: #718096; +} + +.panel-item { + padding: 0; +} + +.panel-item p:last-of-type { + margin-bottom: 0; + padding-bottom: 0; +} + +/* Utilities */ + +.break-all { + word-break: break-all; +} diff --git a/resources/views/vendor/mail/text/button.blade.php b/resources/views/vendor/mail/text/button.blade.php new file mode 100644 index 000000000..97444ebdc --- /dev/null +++ b/resources/views/vendor/mail/text/button.blade.php @@ -0,0 +1 @@ +{{ $slot }}: {{ $url }} diff --git a/resources/views/vendor/mail/text/footer.blade.php b/resources/views/vendor/mail/text/footer.blade.php new file mode 100644 index 000000000..3338f620e --- /dev/null +++ b/resources/views/vendor/mail/text/footer.blade.php @@ -0,0 +1 @@ +{{ $slot }} diff --git a/resources/views/vendor/mail/text/header.blade.php b/resources/views/vendor/mail/text/header.blade.php new file mode 100644 index 000000000..aaa3e5754 --- /dev/null +++ b/resources/views/vendor/mail/text/header.blade.php @@ -0,0 +1 @@ +[{{ $slot }}]({{ $url }}) diff --git a/resources/views/vendor/mail/text/layout.blade.php b/resources/views/vendor/mail/text/layout.blade.php new file mode 100644 index 000000000..9378baa07 --- /dev/null +++ b/resources/views/vendor/mail/text/layout.blade.php @@ -0,0 +1,9 @@ +{!! strip_tags($header) !!} + +{!! strip_tags($slot) !!} +@isset($subcopy) + +{!! strip_tags($subcopy) !!} +@endisset + +{!! strip_tags($footer) !!} diff --git a/resources/views/vendor/mail/text/message.blade.php b/resources/views/vendor/mail/text/message.blade.php new file mode 100644 index 000000000..80bce2112 --- /dev/null +++ b/resources/views/vendor/mail/text/message.blade.php @@ -0,0 +1,27 @@ + + {{-- Header --}} + + + {{ config('app.name') }} + + + + {{-- Body --}} + {{ $slot }} + + {{-- Subcopy --}} + @isset($subcopy) + + + {{ $subcopy }} + + + @endisset + + {{-- Footer --}} + + + © {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.') + + + diff --git a/resources/views/vendor/mail/text/panel.blade.php b/resources/views/vendor/mail/text/panel.blade.php new file mode 100644 index 000000000..3338f620e --- /dev/null +++ b/resources/views/vendor/mail/text/panel.blade.php @@ -0,0 +1 @@ +{{ $slot }} diff --git a/resources/views/vendor/mail/text/subcopy.blade.php b/resources/views/vendor/mail/text/subcopy.blade.php new file mode 100644 index 000000000..3338f620e --- /dev/null +++ b/resources/views/vendor/mail/text/subcopy.blade.php @@ -0,0 +1 @@ +{{ $slot }} diff --git a/resources/views/vendor/mail/text/table.blade.php b/resources/views/vendor/mail/text/table.blade.php new file mode 100644 index 000000000..3338f620e --- /dev/null +++ b/resources/views/vendor/mail/text/table.blade.php @@ -0,0 +1 @@ +{{ $slot }}