fix: backups

This commit is contained in:
Andras Bacsai 2024-04-29 09:38:45 +02:00
parent ae12222687
commit bb6c9cf49e
17 changed files with 71 additions and 96 deletions

View File

@ -289,7 +289,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
if ($this->backup->save_s3) { if ($this->backup->save_s3) {
$this->upload_to_s3(); $this->upload_to_s3();
} }
$this->team?->notify(new BackupSuccess($this->backup, $this->database)); $this->team?->notify(new BackupSuccess($this->backup, $this->database, $database));
$this->backup_log->update([ $this->backup_log->update([
'status' => 'success', 'status' => 'success',
'message' => $this->backup_output, 'message' => $this->backup_output,
@ -305,8 +305,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
]); ]);
} }
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage()); send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
$this->team?->notify(new BackupFailed($this->backup, $this->database, $this->backup_output)); $this->team?->notify(new BackupFailed($this->backup, $this->database, $this->backup_output, $database));
throw $e;
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@ -57,7 +57,7 @@ class SendMessageToTelegramJob implements ShouldQueue, ShouldBeEncrypted
} }
} }
$payload = [ $payload = [
'parse_mode' => 'markdown', // 'parse_mode' => 'markdown',
'reply_markup' => json_encode([ 'reply_markup' => json_encode([
'inline_keyboard' => [ 'inline_keyboard' => [
[...$inlineButtons], [...$inlineButtons],

View File

@ -35,11 +35,6 @@ class Execution extends Component
$this->executions = $executions; $this->executions = $executions;
$this->s3s = currentTeam()->s3s; $this->s3s = currentTeam()->s3s;
} }
public function cleanupFailed()
{
$this->backup->executions()->where('status', 'failed')->delete();
$this->dispatch('refreshBackupExecutions');
}
public function render() public function render()
{ {
return view('livewire.project.database.backup.execution'); return view('livewire.project.database.backup.execution');

View File

@ -2,9 +2,7 @@
namespace App\Livewire\Project\Database; namespace App\Livewire\Project\Database;
use Illuminate\Support\Facades\Storage;
use Livewire\Component; use Livewire\Component;
use Symfony\Component\HttpFoundation\StreamedResponse;
class BackupExecutions extends Component class BackupExecutions extends Component
{ {
@ -16,11 +14,15 @@ class BackupExecutions extends Component
$userId = auth()->user()->id; $userId = auth()->user()->id;
return [ return [
"echo-private:team.{$userId},BackupCreated" => 'refreshBackupExecutions', "echo-private:team.{$userId},BackupCreated" => 'refreshBackupExecutions',
"refreshBackupExecutions",
"deleteBackup" "deleteBackup"
]; ];
} }
public function cleanupFailed()
{
$this->backup->executions()->where('status', 'failed')->delete();
$this->refreshBackupExecutions();
}
public function deleteBackup($exeuctionId) public function deleteBackup($exeuctionId)
{ {
$execution = $this->backup->executions()->where('id', $exeuctionId)->first(); $execution = $this->backup->executions()->where('id', $exeuctionId)->first();

View File

@ -207,7 +207,4 @@ class StandaloneClickhouse extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function database_name() {
return $this->clickhouse_db;
}
} }

View File

@ -207,7 +207,4 @@ class StandaloneDragonfly extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function database_name() {
return '0';
}
} }

View File

@ -208,7 +208,4 @@ class StandaloneKeydb extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function database_name() {
return '0';
}
} }

View File

@ -208,7 +208,4 @@ class StandaloneMariadb extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function database_name() {
return $this->mariadb_database;
}
} }

View File

@ -223,7 +223,4 @@ class StandaloneMongodb extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function database_name() {
return null;
}
} }

View File

@ -209,7 +209,4 @@ class StandaloneMysql extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function database_name() {
return $this->mysql_database;
}
} }

View File

@ -208,7 +208,4 @@ class StandalonePostgresql extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function database_name() {
return $this->postgres_db;
}
} }

View File

@ -204,7 +204,4 @@ class StandaloneRedis extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function database_name() {
return '0';
}
} }

View File

@ -15,21 +15,20 @@ class BackupFailed extends Notification implements ShouldQueue
{ {
use Queueable; use Queueable;
public $tries = 1; public $backoff = 10;
public $tries = 2;
public string $name; public string $name;
public ?string $database_name = null;
public string $frequency; public string $frequency;
public function __construct(ScheduledDatabaseBackup $backup, public $database, public $output) public function __construct(ScheduledDatabaseBackup $backup, public $database, public $output, public $database_name)
{ {
$this->name = $database->name; $this->name = $database->name;
$this->database_name = $database->database_name();
$this->frequency = $backup->frequency; $this->frequency = $backup->frequency;
} }
public function via(object $notifiable): array public function via(object $notifiable): array
{ {
return [DiscordChannel::class, TelegramChannel::class, MailChannel::class]; return setNotificationChannels($notifiable, 'database_backups');
} }
public function toMail(): MailMessage public function toMail(): MailMessage
@ -47,11 +46,11 @@ class BackupFailed extends Notification implements ShouldQueue
public function toDiscord(): string public function toDiscord(): string
{ {
return "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}"; return "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was FAILED.\n\nReason:\n{$this->output}";
} }
public function toTelegram(): array public function toTelegram(): array
{ {
$message = "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}"; $message = "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was FAILED.\n\nReason:\n{$this->output}";
return [ return [
"message" => $message, "message" => $message,
]; ];

View File

@ -12,15 +12,14 @@ class BackupSuccess extends Notification implements ShouldQueue
{ {
use Queueable; use Queueable;
public $tries = 1; public $backoff = 10;
public $tries = 3;
public string $name; public string $name;
public ?string $database_name = null;
public string $frequency; public string $frequency;
public function __construct(ScheduledDatabaseBackup $backup, public $database) public function __construct(ScheduledDatabaseBackup $backup, public $database, public $database_name)
{ {
$this->name = $database->name; $this->name = $database->name;
$this->database_name = $database->database_name();
$this->frequency = $backup->frequency; $this->frequency = $backup->frequency;
} }
@ -48,6 +47,7 @@ class BackupSuccess extends Notification implements ShouldQueue
public function toTelegram(): array public function toTelegram(): array
{ {
$message = "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was successful."; $message = "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was successful.";
ray($message);
return [ return [
"message" => $message, "message" => $message,
]; ];

View File

@ -1,43 +1,51 @@
<div class="flex flex-col-reverse gap-2"> <div>
@forelse($executions as $execution) <div class="flex items-center gap-2">
<form wire:key="{{ data_get($execution, 'id') }}" <h3 class="py-4">Executions</h3>
class="relative flex flex-col p-4 bg-white box-without-bg dark:bg-coolgray-100" @class([ <x-forms.button wire:click='cleanupFailed'>Cleanup Failed Backups</x-forms.button>
'border-green-500' => data_get($execution, 'status') === 'success', </div>
'border-red-500' => data_get($execution, 'status') === 'failed', <div class="flex flex-col-reverse gap-2">
])> @forelse($executions as $execution)
@if (data_get($execution, 'status') === 'running') <form wire:key="{{ data_get($execution, 'id') }}"
<div class="absolute top-2 right-2"> class="relative flex flex-col p-4 bg-white box-without-bg dark:bg-coolgray-100"
<x-loading /> @class([
</div> 'border-green-500' => data_get($execution, 'status') === 'success',
@endif 'border-red-500' => data_get($execution, 'status') === 'failed',
<div>Database: {{ data_get($execution, 'database_name', 'N/A') }}</div> ])>
<div>Status: {{ data_get($execution, 'status') }}</div> @if (data_get($execution, 'status') === 'running')
<div>Started At: {{ data_get($execution, 'created_at') }}</div> <div class="absolute top-2 right-2">
@if (data_get($execution, 'message')) <x-loading />
<div>Message: {{ data_get($execution, 'message') }}</div> </div>
@endif
<div>Size: {{ data_get($execution, 'size') }} B / {{ round((int) data_get($execution, 'size') / 1024, 2) }}
kB / {{ round((int) data_get($execution, 'size') / 1024 / 1024, 3) }} MB
</div>
<div>Location: {{ data_get($execution, 'filename', 'N/A') }}</div>
<div class="flex gap-2">
<div class="flex-1"></div>
@if (data_get($execution, 'status') === 'success')
<x-forms.button class=" dark:hover:bg-coolgray-400"
x-on:click="download_file('{{ data_get($execution, 'id') }}')">Download</x-forms.button>
@endif @endif
<x-modal-confirmation isErrorButton action="deleteBackup({{ data_get($execution, 'id') }})"> <div>Database: {{ data_get($execution, 'database_name', 'N/A') }}</div>
<x-slot:button-title> <div>Status: {{ data_get($execution, 'status') }}</div>
Delete <div>Started At: {{ data_get($execution, 'created_at') }}</div>
</x-slot:button-title> @if (data_get($execution, 'message'))
This will delete this backup. It is not reversible.<br>Please think again. <div>Message: {{ data_get($execution, 'message') }}</div>
</x-modal-confirmation> @endif
</div> <div>Size: {{ data_get($execution, 'size') }} B /
</form> {{ round((int) data_get($execution, 'size') / 1024, 2) }}
kB / {{ round((int) data_get($execution, 'size') / 1024 / 1024, 3) }} MB
</div>
<div>Location: {{ data_get($execution, 'filename', 'N/A') }}</div>
<div class="flex gap-2">
<div class="flex-1"></div>
@if (data_get($execution, 'status') === 'success')
<x-forms.button class=" dark:hover:bg-coolgray-400"
x-on:click="download_file('{{ data_get($execution, 'id') }}')">Download</x-forms.button>
@endif
<x-modal-confirmation isErrorButton action="deleteBackup({{ data_get($execution, 'id') }})">
<x-slot:button-title>
Delete
</x-slot:button-title>
This will delete this backup. It is not reversible.<br>Please think again.
</x-modal-confirmation>
</div>
</form>
@empty @empty
<div>No executions found.</div> <div>No executions found.</div>
@endforelse @endforelse
</div>
<script> <script>
function download_file(executionId) { function download_file(executionId) {
window.open('/download/backup/' + executionId, '_blank'); window.open('/download/backup/' + executionId, '_blank');

View File

@ -4,10 +4,6 @@
<livewire:project.database.heading :database="$database" /> <livewire:project.database.heading :database="$database" />
<div class="pt-6"> <div class="pt-6">
<livewire:project.database.backup-edit :backup="$backup" :s3s="$s3s" :status="data_get($database, 'status')" /> <livewire:project.database.backup-edit :backup="$backup" :s3s="$s3s" :status="data_get($database, 'status')" />
<div class="flex items-center gap-2">
<h3 class="py-4">Executions</h3>
<x-forms.button wire:click='cleanupFailed'>Cleanup Failed Backups</x-forms.button>
</div>
<livewire:project.database.backup-executions :backup="$backup" :executions="$executions" /> <livewire:project.database.backup-executions :backup="$backup" :executions="$executions" />
</div> </div>
</div> </div>

View File

@ -2,21 +2,21 @@
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
@forelse($database->scheduledBackups as $backup) @forelse($database->scheduledBackups as $backup)
@if ($type == 'database') @if ($type == 'database')
<div class="box"> <a class="box"
<a class="flex flex-col" href="{{ route('project.database.backup.execution', [...$parameters, 'backup_uuid' => $backup->uuid]) }}">
href="{{ route('project.database.backup.execution', [...$parameters, 'backup_uuid' => $backup->uuid]) }}"> <div class="flex flex-col">
<div>Frequency: {{ $backup->frequency }}</div> <div>Frequency: {{ $backup->frequency }}</div>
<div>Last backup: {{ data_get($backup->latest_log, 'status', 'No backup yet') }}</div> <div>Last backup: {{ data_get($backup->latest_log, 'status', 'No backup yet') }}</div>
<div>Number of backups to keep (locally): {{ $backup->number_of_backups_locally }}</div> <div>Number of backups to keep (locally): {{ $backup->number_of_backups_locally }}</div>
</a> </div>
</div> </a>
@else @else
<div class="box"> <div class="box" wire:click="setSelectedBackup('{{ data_get($backup, 'id') }}')">
<div @class([ <div @class([
'border-coollabs' => 'border-coollabs' =>
data_get($backup, 'id') === data_get($selectedBackup, 'id'), data_get($backup, 'id') === data_get($selectedBackup, 'id'),
'flex flex-col border-l-2 border-transparent', 'flex flex-col border-l-2 border-transparent',
]) wire:click="setSelectedBackup('{{ data_get($backup, 'id') }}')"> ])>
<div>Frequency: {{ $backup->frequency }}</div> <div>Frequency: {{ $backup->frequency }}</div>
<div>Last backup: {{ data_get($backup->latest_log, 'status', 'No backup yet') }}</div> <div>Last backup: {{ data_get($backup->latest_log, 'status', 'No backup yet') }}</div>
<div>Number of backups to keep (locally): {{ $backup->number_of_backups_locally }}</div> <div>Number of backups to keep (locally): {{ $backup->number_of_backups_locally }}</div>