From 22b31f567149faf3db865cd0791c1867a38fc166 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 1 Jul 2026 11:14:20 +0200 Subject: [PATCH] fix(backups): require valid S3 storage selection Preserve S3 backups when a single valid storage is available, require explicit selection when multiple storages exist, and disable S3 when none are available. Make backup action controls responsive on narrow screens. --- app/Livewire/Project/Database/BackupEdit.php | 37 ++++++++-- .../components/modal-confirmation.blade.php | 6 +- .../project/database/backup-edit.blade.php | 59 ++++++++------- .../database/backup-executions.blade.php | 26 ++++--- .../database/backup/execution.blade.php | 2 +- .../database/scheduled-backups.blade.php | 2 +- .../views/livewire/settings-backup.blade.php | 2 +- tests/Feature/BackupEditValidationTest.php | 73 ++++++++++++++++++- 8 files changed, 157 insertions(+), 50 deletions(-) diff --git a/app/Livewire/Project/Database/BackupEdit.php b/app/Livewire/Project/Database/BackupEdit.php index 99426c120..a387b6f88 100644 --- a/app/Livewire/Project/Database/BackupEdit.php +++ b/app/Livewire/Project/Database/BackupEdit.php @@ -3,10 +3,12 @@ namespace App\Livewire\Project\Database; use App\Jobs\DatabaseBackupJob; +use App\Models\S3Storage; use App\Models\ScheduledDatabaseBackup; use App\Models\ServiceDatabase; use Exception; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; +use Illuminate\Support\Collection; use Livewire\Attributes\Locked; use Livewire\Attributes\Validate; use Livewire\Component; @@ -18,7 +20,7 @@ class BackupEdit extends Component public ScheduledDatabaseBackup $backup; #[Locked] - public $s3s; + public $availableS3Storages; #[Locked] public $parameters; @@ -69,7 +71,7 @@ class BackupEdit extends Component public bool $disableLocalBackup = false; #[Validate(['nullable', 'integer'])] - public ?int $s3StorageId = 1; + public ?int $s3StorageId = null; #[Validate(['nullable', 'string'])] public ?string $databasesToBackup = null; @@ -222,10 +224,18 @@ private function customValidate() } // S3 backup cannot be enabled without a valid S3 storage owned by the team - $availableS3Ids = collect($this->s3s)->pluck('id'); + $availableS3Ids = $this->availableS3StorageIds(); if ($this->backup->save_s3 && ! $availableS3Ids->contains($this->backup->s3_storage_id)) { - $this->backup->save_s3 = $this->saveS3 = false; - $this->backup->s3_storage_id = $this->s3StorageId = null; + if ($availableS3Ids->isEmpty()) { + $this->backup->s3_storage_id = $this->s3StorageId = null; + $this->backup->save_s3 = $this->saveS3 = false; + } elseif ($this->backup->s3_storage_id === null && $availableS3Ids->count() === 1) { + $this->backup->s3_storage_id = $this->s3StorageId = $availableS3Ids->first(); + } else { + $this->backup->s3_storage_id = $this->s3StorageId = null; + + throw new Exception('Please select a valid S3 storage to enable S3 backups.'); + } } // Validate that disable_local_backup can only be true when S3 backup is enabled @@ -240,6 +250,23 @@ private function customValidate() $this->validate(); } + private function availableS3StorageIds(): Collection + { + $storages = collect($this->availableS3Storages); + $storageIds = $storages->pluck('id')->filter()->all(); + $teamIds = $storages->pluck('team_id')->reject(fn ($teamId) => $teamId === null)->unique()->values()->all(); + + if (empty($storageIds) || empty($teamIds)) { + return collect(); + } + + return S3Storage::query() + ->whereKey($storageIds) + ->whereIn('team_id', $teamIds) + ->where('is_usable', true) + ->pluck('id'); + } + public function submit() { try { diff --git a/resources/views/components/modal-confirmation.blade.php b/resources/views/components/modal-confirmation.blade.php index 4629e3b96..a25e0141f 100644 --- a/resources/views/components/modal-confirmation.blade.php +++ b/resources/views/components/modal-confirmation.blade.php @@ -129,7 +129,11 @@ } }" @keydown.escape.window="if (modalOpen) { modalOpen = false; resetModal(); }" :class="{ 'z-40': modalOpen }" - class="relative w-auto h-auto"> + @class([ + 'relative h-auto', + 'w-full' => $buttonFullWidth, + 'w-auto' => ! $buttonFullWidth, + ])> @if (isset($trigger))