fix(backups): show setup prompt when no S3 storage is available

This commit is contained in:
Andras Bacsai 2026-07-15 20:36:23 +02:00
parent 91d97bfad2
commit 6ae1b4eba2
4 changed files with 63 additions and 30 deletions

View file

@ -1,10 +1,19 @@
@if ($availableS3Storages->isEmpty())
<div class="flex flex-col gap-4">
<h2>S3</h2>
<div>
No validated S3 available. Configure one <a class="underline dark:text-white" {{ wireNavigate() }}
href="{{ route('storage.index') }}">here</a>.
</div>
</div>
@else
<form wire:submit="submit" class="flex flex-col gap-4"> <form wire:submit="submit" class="flex flex-col gap-4">
<div class="flex flex-col gap-3 sm:flex-row sm:items-center"> <div class="flex flex-col gap-3 sm:flex-row sm:items-center">
<h2>S3</h2> <h2>S3</h2>
<x-forms.button type="submit" class="w-full sm:w-auto">Save</x-forms.button> <x-forms.button type="submit" class="w-full sm:w-auto">Save</x-forms.button>
@if (!$saveS3) @if (!$saveS3)
<x-forms.button type="button" wire:click="toggleS3" wire:loading.attr="disabled" <x-forms.button type="button" wire:click="toggleS3" wire:loading.attr="disabled"
wire:target="toggleS3" isHighlighted :disabled="$availableS3Storages->isEmpty()">Enable S3</x-forms.button> wire:target="toggleS3" isHighlighted>Enable S3</x-forms.button>
@else @else
<x-forms.button type="button" wire:click="toggleS3" wire:loading.attr="disabled" <x-forms.button type="button" wire:click="toggleS3" wire:loading.attr="disabled"
wire:target="toggleS3">Disable S3</x-forms.button> wire:target="toggleS3">Disable S3</x-forms.button>
@ -20,15 +29,10 @@
<x-highlighted text="*" /> <x-highlighted text="*" />
@endif @endif
</div> </div>
<x-forms.select id="s3StorageId" wire:model.live="s3StorageId" :required="$saveS3" <x-forms.select id="s3StorageId" wire:model.live="s3StorageId" :required="$saveS3">
:disabled="$availableS3Storages->isEmpty()"> @foreach ($availableS3Storages as $s3)
@if ($availableS3Storages->isEmpty()) <option value="{{ $s3->id }}">{{ $s3->name }}</option>
<option value="">No S3 storage available</option> @endforeach
@else
@foreach ($availableS3Storages as $s3)
<option value="{{ $s3->id }}">{{ $s3->name }}</option>
@endforeach
@endif
</x-forms.select> </x-forms.select>
</div> </div>
@ -43,3 +47,4 @@
</div> </div>
</form> </form>
@endif

View file

@ -1,10 +1,19 @@
@if ($availableS3Storages->isEmpty())
<div class="flex flex-col gap-4">
<h2>S3</h2>
<div>
No validated S3 available. Configure one <a class="underline dark:text-white" {{ wireNavigate() }}
href="{{ route('storage.index') }}">here</a>.
</div>
</div>
@else
<form wire:submit="save" class="flex flex-col gap-4"> <form wire:submit="save" class="flex flex-col gap-4">
<div class="flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center"> <div class="flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center">
<h2>S3</h2> <h2>S3</h2>
<x-forms.button type="submit" class="w-full sm:w-auto">Save</x-forms.button> <x-forms.button type="submit" class="w-full sm:w-auto">Save</x-forms.button>
@if (!$saveToS3) @if (!$saveToS3)
<x-forms.button type="button" wire:click="toggleS3" wire:loading.attr="disabled" <x-forms.button type="button" wire:click="toggleS3" wire:loading.attr="disabled"
wire:target="toggleS3" isHighlighted :disabled="$availableS3Storages->isEmpty()">Enable S3</x-forms.button> wire:target="toggleS3" isHighlighted>Enable S3</x-forms.button>
@else @else
<x-forms.button type="button" wire:click="toggleS3" wire:loading.attr="disabled" <x-forms.button type="button" wire:click="toggleS3" wire:loading.attr="disabled"
wire:target="toggleS3">Disable S3</x-forms.button> wire:target="toggleS3">Disable S3</x-forms.button>
@ -20,15 +29,10 @@
<x-highlighted text="*" /> <x-highlighted text="*" />
@endif @endif
</div> </div>
<x-forms.select id="s3StorageId" wire:model.live="s3StorageId" :required="$saveToS3" <x-forms.select id="s3StorageId" wire:model.live="s3StorageId" :required="$saveToS3">
:disabled="$availableS3Storages->isEmpty()"> @foreach ($availableS3Storages as $s3Storage)
@if ($availableS3Storages->isEmpty()) <option value="{{ $s3Storage->id }}">{{ $s3Storage->name }}</option>
<option value="">No S3 storage available</option> @endforeach
@else
@foreach ($availableS3Storages as $s3Storage)
<option value="{{ $s3Storage->id }}">{{ $s3Storage->name }}</option>
@endforeach
@endif
</x-forms.select> </x-forms.select>
</div> </div>
@ -43,3 +47,4 @@
</div> </div>
</form> </form>
@endif

View file

@ -171,8 +171,10 @@ function createS3StorageForBackupEditValidationTest(Team|int $team, string $name
$this->get($generalUrl.'/s3') $this->get($generalUrl.'/s3')
->assertOk() ->assertOk()
->assertSeeInOrder(['S3 Storage', 'Disable Local Backup']) ->assertSeeText('No validated S3 available. Configure one here.')
->assertSee('S3 Storage') ->assertDontSee('Disable Local Backup')
->assertDontSee('Enable S3')
->assertDontSee('Disable S3')
->assertDontSee('S3 Storage Retention') ->assertDontSee('S3 Storage Retention')
->assertDontSee('Local Backup Retention') ->assertDontSee('Local Backup Retention')
->assertDontSee('Frequency') ->assertDontSee('Frequency')
@ -357,14 +359,23 @@ function createS3StorageForBackupEditValidationTest(Team|int $team, string $name
->assertSee('Second S3'); ->assertSee('Second S3');
}); });
it('shows disabled S3 storage dropdown when no storages are available', function () { it('shows only an empty S3 state when no storages are available', function () {
$backup = createBackupForEditValidationTest($this->team, [ $backup = createBackupForEditValidationTest($this->team, [
'save_s3' => false, 'save_s3' => false,
's3_storage_id' => null, 's3_storage_id' => null,
]); ]);
Livewire::test(BackupEdit::class, ['backup' => $backup->fresh(), 'availableS3Storages' => $this->team->s3s, 'section' => 's3']) Livewire::test(BackupEdit::class, ['backup' => $backup->fresh(), 'availableS3Storages' => $this->team->s3s, 'section' => 's3'])
->assertSee('No S3 storage available'); ->assertSeeHtml('<h2>S3</h2>')
->assertSeeText('No validated S3 available. Configure one here.')
->assertSeeHtml('href="'.route('storage.index').'"')
->assertSeeHtml('>here</a>')
->assertDontSee('Save')
->assertDontSee('Enable S3')
->assertDontSee('Disable S3')
->assertDontSee('S3 Storage')
->assertDontSee('Disable Local Backup')
->assertDontSee('No S3 storage available');
}); });
it('allows S3 backups to be disabled when no usable storage remains', function () { it('allows S3 backups to be disabled when no usable storage remains', function () {
@ -379,10 +390,12 @@ function createS3StorageForBackupEditValidationTest(Team|int $team, string $name
'section' => 's3', 'section' => 's3',
]) ])
->assertSet('saveS3', true) ->assertSet('saveS3', true)
->assertSee('Disable S3') ->assertSeeText('No validated S3 available. Configure one here.')
->assertDontSee('Disable S3')
->call('toggleS3') ->call('toggleS3')
->assertDispatched('success') ->assertDispatched('success')
->assertSee('Enable S3'); ->assertSet('saveS3', false)
->assertDontSee('Enable S3');
expect($backup->refresh()->save_s3)->toBeFalsy() expect($backup->refresh()->save_s3)->toBeFalsy()
->and($backup->s3_storage_id)->toBeNull(); ->and($backup->s3_storage_id)->toBeNull();

View file

@ -252,8 +252,10 @@
$this->get($generalUrl.'/s3') $this->get($generalUrl.'/s3')
->assertOk() ->assertOk()
->assertSeeInOrder(['S3 Storage', 'Disable Local Backup']) ->assertSeeText('No validated S3 available. Configure one here.')
->assertSee('S3 Storage') ->assertDontSee('Disable Local Backup')
->assertDontSee('Enable S3')
->assertDontSee('Disable S3')
->assertDontSee('S3 Storage Retention') ->assertDontSee('S3 Storage Retention')
->assertDontSee('Local Backup Retention') ->assertDontSee('Local Backup Retention')
->assertDontSee('Frequency') ->assertDontSee('Frequency')
@ -957,10 +959,18 @@ function signInForVolumeBackups($testCase, Team $team): User
'section' => 's3', 'section' => 's3',
]) ])
->assertSet('saveToS3', true) ->assertSet('saveToS3', true)
->assertSee('Disable S3') ->assertSeeHtml('<h2>S3</h2>')
->assertSeeText('No validated S3 available. Configure one here.')
->assertSeeHtml('href="'.route('storage.index').'"')
->assertSeeHtml('>here</a>')
->assertDontSee('Save')
->assertDontSee('Disable S3')
->assertDontSee('S3 Storage')
->assertDontSee('Disable Local Backup')
->call('toggleS3') ->call('toggleS3')
->assertDispatched('success') ->assertDispatched('success')
->assertSee('Enable S3'); ->assertSet('saveToS3', false)
->assertDontSee('Enable S3');
expect($backup->refresh()->save_s3)->toBeFalse() expect($backup->refresh()->save_s3)->toBeFalse()
->and($backup->s3_storage_id)->toBeNull(); ->and($backup->s3_storage_id)->toBeNull();