diff --git a/app/Actions/Shared/DeleteScheduledVolumeBackup.php b/app/Actions/Shared/DeleteScheduledVolumeBackup.php index 55972ab520..d56cce9384 100644 --- a/app/Actions/Shared/DeleteScheduledVolumeBackup.php +++ b/app/Actions/Shared/DeleteScheduledVolumeBackup.php @@ -12,8 +12,12 @@ class DeleteScheduledVolumeBackup { use AsAction; - public function handle(ScheduledVolumeBackup $backup, ?Server $server = null): void - { + public function handle( + ScheduledVolumeBackup $backup, + ?Server $server = null, + bool $deleteLocalArchives = true, + bool $deleteS3Archives = true, + ): void { $lock = Cache::lock(VolumeBackupJob::lockKey($backup->id), $backup->timeout + 300); if (! $lock->get()) { @@ -30,36 +34,40 @@ class DeleteScheduledVolumeBackup throw new \RuntimeException('Wait for the running storage backup and recovery operations to finish before deleting this schedule.'); } - $localFilenames = $backup->executions() - ->where('local_storage_deleted', false) - ->pluck('filename') - ->filter() - ->all(); + if ($deleteLocalArchives) { + $localFilenames = $backup->executions() + ->where('local_storage_deleted', false) + ->pluck('filename') + ->filter() + ->all(); - if ($localFilenames !== []) { - $server ??= $backup->server(); - if (! $server) { - throw new \RuntimeException('The server is unavailable, so local backup archives cannot be deleted.'); + if ($localFilenames !== []) { + $server ??= $backup->server(); + if (! $server) { + throw new \RuntimeException('The server is unavailable, so local backup archives cannot be deleted.'); + } + + deleteBackupsLocally($localFilenames, $server, throwError: true); } - - deleteBackupsLocally($localFilenames, $server, throwError: true); } - $s3Executions = $backup->executions() - ->with('s3') - ->where('s3_uploaded', true) - ->where('s3_storage_deleted', false) - ->get(); + if ($deleteS3Archives) { + $s3Executions = $backup->executions() + ->with('s3') + ->where('s3_uploaded', true) + ->where('s3_storage_deleted', false) + ->get(); - foreach ($s3Executions->groupBy('s3_storage_id') as $executions) { - $s3 = $executions->first()->s3; - if (! $s3) { - throw new \RuntimeException('The S3 storage used by an existing backup is unavailable.'); - } + foreach ($s3Executions->groupBy('s3_storage_id') as $executions) { + $s3 = $executions->first()->s3; + if (! $s3) { + throw new \RuntimeException('The S3 storage used by an existing backup is unavailable.'); + } - $filenames = $executions->pluck('filename')->filter()->all(); - if ($filenames !== []) { - deleteBackupsS3($filenames, $s3); + $filenames = $executions->pluck('filename')->filter()->all(); + if ($filenames !== []) { + deleteBackupsS3($filenames, $s3); + } } } diff --git a/app/Livewire/Project/Shared/Storages/VolumeBackups.php b/app/Livewire/Project/Shared/Storages/VolumeBackups.php index a8e2d72df1..d522e66939 100644 --- a/app/Livewire/Project/Shared/Storages/VolumeBackups.php +++ b/app/Livewire/Project/Shared/Storages/VolumeBackups.php @@ -69,6 +69,10 @@ class VolumeBackups extends Component public bool $delete_backup_s3 = false; + public bool $delete_associated_backups_locally = false; + + public bool $delete_associated_backups_s3 = false; + public Collection $availableS3Storages; protected function rules(): array @@ -226,14 +230,18 @@ class VolumeBackups extends Component } try { - DeleteScheduledVolumeBackup::run($this->backup); + DeleteScheduledVolumeBackup::run( + $this->backup, + deleteLocalArchives: in_array('delete_associated_backups_locally', $selectedActions, true), + deleteS3Archives: in_array('delete_associated_backups_s3', $selectedActions, true), + ); $this->backup = null; - $this->dispatch('success', 'Storage backup schedule and archives deleted.'); + $this->dispatch('success', 'Storage backup schedule deleted.'); $this->redirectRoute($this->routeName('index'), $this->routeParameters(includeBackup: false), navigate: true); return true; } catch (Throwable $exception) { - $this->dispatch('error', 'Could not delete the backup archives: '.$exception->getMessage()); + $this->dispatch('error', 'Could not delete the backup schedule: '.$exception->getMessage()); return false; } @@ -334,6 +342,10 @@ class VolumeBackups extends Component return view('livewire.project.shared.storages.volume-backups', [ 'executions' => $executions ?? collect(), 'latestExecution' => $this->backup?->executions()->first(), + 'deleteScheduleCheckboxes' => [ + ['id' => 'delete_associated_backups_locally', 'label' => 'Delete all local archives created by this schedule.'], + ['id' => 'delete_associated_backups_s3', 'label' => 'Delete all S3 archives created by this schedule.'], + ], ]); } diff --git a/resources/css/app.css b/resources/css/app.css index 15035abb92..263233eef9 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -3150,59 +3150,8 @@ input[type="search"]::-webkit-search-results-decoration { } .volume-backup-executions-grid { - grid-template-columns: 7.5rem minmax(0, 1fr) 8.5rem 7rem 9rem minmax(9rem, 0.7fr); -} - -.volume-backup-execution-label { - display: none; -} - -.volume-backup-execution-message { - grid-column: 1 / -1; -} - -@media (max-width: 768px) { - .data-table-header.volume-backup-executions-grid { - display: none; - } - - .data-table-row.volume-backup-executions-grid { - grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); - min-height: 0; - align-items: start; - gap: 0.875rem 1rem; - padding: 1rem; - } - - .volume-backup-execution-archive, - .volume-backup-execution-availability, - .volume-backup-execution-message { - grid-column: 1 / -1; - } - - .volume-backup-execution-status, - .volume-backup-execution-archive, - .volume-backup-execution-time, - .volume-backup-execution-size, - .volume-backup-execution-availability, - .volume-backup-execution-actions { - display: flex; - min-width: 0; - flex-direction: column; - gap: 0.25rem; - } - - .volume-backup-execution-label { - display: block; - font-size: 12px; - font-weight: 500; - line-height: 1rem; - color: var(--coollabs-subtle); - } - - .volume-backup-execution-actions > span:last-child { - justify-content: flex-start; - } + grid-template-columns: 7.5rem minmax(14rem, 1fr) 8.5rem 7rem 9rem minmax(9rem, 0.7fr); + min-width: 50rem; } .clone-destinations-table-grid { diff --git a/resources/views/components/danger-zone.blade.php b/resources/views/components/danger-zone.blade.php new file mode 100644 index 0000000000..cd2f75fbce --- /dev/null +++ b/resources/views/components/danger-zone.blade.php @@ -0,0 +1,16 @@ +@props(['title']) + +
- Permanently delete {{ $destination->name }} - from Coolify. The Docker network is also removed from the server. -
-- Delete or move every attached resource before deleting this destination. -
-+ Permanently delete {{ $destination->name }} + from Coolify. The Docker network is also removed from the server. +
+Delete or move every attached resource before deleting this destination.
+- Permanently remove this schedule and optionally its backup archives. This cannot be undone. -
-You can select which backup archives to remove.
+
+
Permanently delete
{{ $resourceName }},
stop its containers, and remove the selected Docker resources and configuration.
+
-
This action cannot be undone.
-- Existing archives created by this schedule are removed with it. -
-You can select which backup archives to remove.
+{{ $execution->filename ?? 'No archive name' }}
- The server will be removed from Coolify. @if ($server->definedResources()->count() > 0) It currently contains managed resources. Enable force deletion in the confirmation only if those resources should also be removed. @endif -
Delete {{ $server->name }}
-- Type the server name in the confirmation dialog to continue. -
-Type the server name in the confirmation dialog to continue.
+
+
Permanently delete
{{ $name ?: 'this GitHub App' }}
from Coolify. Applications using this source will need another Git provider configured.
+
-
+
Permanently delete
{{ $storage->name }}
from Coolify. Existing objects in the bucket are not deleted.
+
-
+
Only team owners can delete this team.
@elseif (session('currentTeam.id') === 0) -+
The default team cannot be deleted.
@elseif(auth()->user()->teams()->count() === 1 || auth()->user()->currentTeam()->personal_team) -+
Your last or personal team cannot be deleted.
@elseif(currentTeam()->subscription) -+
Cancel your subscription before deleting this team.
@elseif($deletionBlockers === []) -+
Permanently delete {{ currentTeam()->name }} from Coolify. This action cannot be undone.
-+
This team still owns:
-+
Remove or move these resources before deleting the team.
@endif -