diff --git a/app/Livewire/Project/Application/Backup/Index.php b/app/Livewire/Project/Application/Backup/Index.php index 130396a9b..fe7e2eac5 100644 --- a/app/Livewire/Project/Application/Backup/Index.php +++ b/app/Livewire/Project/Application/Backup/Index.php @@ -31,7 +31,7 @@ class Index extends Component public function render(): View { $backups = ScheduledVolumeBackup::query() - ->with(['backupable', 'latestExecution']) + ->with(['backupable', 'latestExecution', 's3']) ->withCount('executions') ->forApplication($this->application) ->latest() diff --git a/app/Livewire/Project/Service/VolumeBackup/Index.php b/app/Livewire/Project/Service/VolumeBackup/Index.php index ad6b7d6c2..a56653597 100644 --- a/app/Livewire/Project/Service/VolumeBackup/Index.php +++ b/app/Livewire/Project/Service/VolumeBackup/Index.php @@ -31,7 +31,7 @@ class Index extends Component public function render(): View { $backups = ScheduledVolumeBackup::query() - ->with(['backupable.resource', 'latestExecution']) + ->with(['backupable.resource', 'latestExecution', 's3']) ->withCount('executions') ->forService($this->service) ->latest() diff --git a/app/Livewire/Project/Shared/Storages/All.php b/app/Livewire/Project/Shared/Storages/All.php index 54175d21a..15a154e67 100644 --- a/app/Livewire/Project/Shared/Storages/All.php +++ b/app/Livewire/Project/Shared/Storages/All.php @@ -28,7 +28,7 @@ class All extends Component /** * Precomputed per-volume backup badge/link data. * - * @var array + * @var array */ public array $volumeBackupMeta = []; @@ -241,6 +241,7 @@ class All extends Component $this->volumeBackupMeta[(int) $storage->id] = [ 'enabled' => $enabled, + 's3' => $enabled && (bool) $backup?->save_s3, 'url' => $url, ]; } diff --git a/resources/css/app.css b/resources/css/app.css index 81f63b1b9..c61458fe8 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -2149,7 +2149,7 @@ input[type="search"]::-webkit-search-results-decoration { } .backup-table-grid { - grid-template-columns: minmax(10rem, 1.7fr) 6rem minmax(7rem, 0.8fr) 7.5rem minmax(8rem, 1fr) 5rem; + grid-template-columns: minmax(10rem, 1.7fr) 6rem minmax(7rem, 0.8fr) 7.5rem 6.5rem minmax(8rem, 1fr) 5rem; } /* Persistent storage volumes: Name | Source | Destination | [PR suffix] | Backup | [Actions] */ @@ -2383,11 +2383,12 @@ input[type="search"]::-webkit-search-results-decoration { @media (max-width: 900px) { .backup-table-grid { - grid-template-columns: minmax(10rem, 1.5fr) 6rem 7.5rem minmax(8rem, 1fr); + grid-template-columns: minmax(10rem, 1.5fr) 6rem 7.5rem 6.5rem; } .backup-table-grid > :nth-child(3), - .backup-table-grid > :nth-child(6) { + .backup-table-grid > :nth-child(6), + .backup-table-grid > :nth-child(7) { display: none; } @@ -2443,11 +2444,10 @@ input[type="search"]::-webkit-search-results-decoration { } .backup-table-grid { - grid-template-columns: minmax(0, 1fr) 7.5rem; + grid-template-columns: minmax(0, 1fr) 7.5rem 6.5rem; } - .backup-table-grid > :nth-child(2), - .backup-table-grid > :nth-child(5) { + .backup-table-grid > :nth-child(2) { display: none; } diff --git a/resources/views/livewire/project/application/backup/index.blade.php b/resources/views/livewire/project/application/backup/index.blade.php index 8fd104b28..12660d527 100644 --- a/resources/views/livewire/project/application/backup/index.blade.php +++ b/resources/views/livewire/project/application/backup/index.blade.php @@ -178,6 +178,7 @@ Type Schedule Status + S3 Last run Executions @@ -212,6 +213,10 @@ {{ $backup->targetType() }} {{ $backup->frequency }} + + + {{ $latestExecution?->finished_at?->diffForHumans() ?? ($status === 'running' ? 'Running now' : 'Never') }} diff --git a/resources/views/livewire/project/service/resource-card.blade.php b/resources/views/livewire/project/service/resource-card.blade.php index 43feffcd6..be026313a 100644 --- a/resources/views/livewire/project/service/resource-card.blade.php +++ b/resources/views/livewire/project/service/resource-card.blade.php @@ -39,17 +39,10 @@ @endif @if ($isApplication && $resource->fqdn) -
+
{{ $resource->fqdn }} - @can('update', $service) - - - - @endcan
@endif
@@ -63,6 +56,14 @@ @endif + @if ($isApplication && $resource->fqdn) + @can('update', $service) + + + + @endcan + @endif @@ -109,6 +110,14 @@ @endif + @if ($isApplication && $resource->fqdn) + @can('update', $service) + + + + @endcan + @endif diff --git a/resources/views/livewire/project/service/volume-backup/index.blade.php b/resources/views/livewire/project/service/volume-backup/index.blade.php index 6c531d2da..78c7d969b 100644 --- a/resources/views/livewire/project/service/volume-backup/index.blade.php +++ b/resources/views/livewire/project/service/volume-backup/index.blade.php @@ -179,6 +179,7 @@ Type Schedule Status + S3 Last run Executions @@ -213,6 +214,10 @@ {{ $backup->targetType() }} {{ $backup->frequency }} + + + {{ $latestExecution?->finished_at?->diffForHumans() ?? ($status === 'running' ? 'Running now' : 'Never') }} diff --git a/resources/views/livewire/project/shared/storages/all.blade.php b/resources/views/livewire/project/shared/storages/all.blade.php index 4a15f919e..e0b2b61a5 100644 --- a/resources/views/livewire/project/shared/storages/all.blade.php +++ b/resources/views/livewire/project/shared/storages/all.blade.php @@ -39,8 +39,9 @@ if (! $form) { continue; } - $backupMeta = $volumeBackupMeta[$id] ?? ['enabled' => false, 'url' => null]; + $backupMeta = $volumeBackupMeta[$id] ?? ['enabled' => false, 's3' => false, 'url' => null]; $hasEnabledBackup = $backupMeta['enabled']; + $hasS3Backup = $backupMeta['s3']; $backupUrl = $backupMeta['url']; $inputsReadonly = $form['isReadOnly']; $displayHostPath = filled($form['hostPath']) ? $form['hostPath'] : '—'; @@ -78,7 +79,7 @@ @endif -
+
Backup @if ($hasEnabledBackup) @@ -87,6 +88,10 @@ + $hasS3Backup]) + title="{{ $hasS3Backup ? 'Backups are saved to S3' : 'Backups are stored locally only' }}"> + {{ $hasS3Backup ? 'S3' : 'Local' }} + @else - @endif @@ -153,7 +158,7 @@
@endif -
+
Backup @if ($hasEnabledBackup) @@ -162,6 +167,10 @@ + $hasS3Backup]) + title="{{ $hasS3Backup ? 'Backups are saved to S3' : 'Backups are stored locally only' }}"> + {{ $hasS3Backup ? 'S3' : 'Local' }} + @else - @endif diff --git a/tests/Feature/DomainChipsComponentTest.php b/tests/Feature/DomainChipsComponentTest.php index 5b43f72c6..ee4e0bc5e 100644 --- a/tests/Feature/DomainChipsComponentTest.php +++ b/tests/Feature/DomainChipsComponentTest.php @@ -66,8 +66,8 @@ it('styles icon buttons with a visible hover state', function () { ->toContain('dark:hover:bg-white/[0.07]'); expect($resourceCard) - ->toContain('class="icon-button shrink-0"') - ->toContain('title="Edit domains"'); + ->toContain('class="icon-button"') + ->toContain('title="Manage domains"'); }); it('exposes enable and disable public access methods on service index', function () { diff --git a/tests/Feature/PersistentStoragePerformanceTest.php b/tests/Feature/PersistentStoragePerformanceTest.php index b4a10e7a0..caad5822a 100644 --- a/tests/Feature/PersistentStoragePerformanceTest.php +++ b/tests/Feature/PersistentStoragePerformanceTest.php @@ -107,11 +107,12 @@ it('renders volume rows without nesting Livewire Show components', function () { it('batches volume backup meta and exposes forms for every volume', function () { [$application, , $team] = createPerfApplicationWithVolumes(5); - foreach ($application->persistentStorages as $storage) { + foreach ($application->persistentStorages as $index => $storage) { $storage->scheduledBackups()->create([ 'team_id' => $team->id, 'frequency' => 'daily', 'enabled' => true, + 'save_s3' => $index === 0, ]); } @@ -120,7 +121,10 @@ it('batches volume backup meta and exposes forms for every volume', function () expect($component->get('volumeBackupMeta'))->toHaveCount(5) ->and($component->get('forms'))->toHaveCount(5) ->and($component->html())->toContain('volumes-col-backup') - ->not->toContain('table-badge table-badge-success'); + ->toContain('Backups are saved to S3') + ->toContain('Backups are stored locally only'); + + expect($component->get("volumeBackupMeta.{$application->persistentStorages->first()->id}.s3"))->toBeTrue(); foreach ($component->get('volumeBackupMeta') as $meta) { expect($meta['enabled'])->toBeTrue() diff --git a/tests/Feature/ServiceComposeResourcesViewSwitcherTest.php b/tests/Feature/ServiceComposeResourcesViewSwitcherTest.php index 0a2551e9d..c859edc37 100644 --- a/tests/Feature/ServiceComposeResourcesViewSwitcherTest.php +++ b/tests/Feature/ServiceComposeResourcesViewSwitcherTest.php @@ -44,3 +44,13 @@ it('aligns compose resource columns and uses icon actions', function () { ->not->toContain('>Settings') ->not->toContain('>Backups'); }); + +it('distinguishes domain management from resource settings', function () { + $resourceCard = file_get_contents(resource_path('views/livewire/project/service/resource-card.blade.php')); + + expect($resourceCard) + ->toContain('title="Manage domains" aria-label="Manage domains"') + ->toContain('') + ->toContain('title="Resource settings" aria-label="Resource settings"') + ->not->toContain('title="Edit domains" aria-label="Edit domains"'); +}); diff --git a/tests/Feature/VolumeBackupTest.php b/tests/Feature/VolumeBackupTest.php index 3b5da0649..0be0b2a41 100644 --- a/tests/Feature/VolumeBackupTest.php +++ b/tests/Feature/VolumeBackupTest.php @@ -56,6 +56,21 @@ it('keeps the volume backup script inside the Livewire root element', function ( expect(strrpos($view, '@endscript'))->toBeLessThan(strrpos($view, '
')); }); +it('shows the S3 configuration state in application and service backup tables', function () { + $views = [ + resource_path('views/livewire/project/application/backup/index.blade.php'), + resource_path('views/livewire/project/service/volume-backup/index.blade.php'), + ]; + + foreach ($views as $view) { + expect(file_get_contents($view)) + ->toContain('S3') + ->toContain("'Configured'") + ->toContain("'Unavailable'") + ->toContain("'Not set'"); + } +}); + it('targets named volumes and application directory mounts through one backup relation', function () { $team = Team::factory()->create(); [$application, $volume] = createVolumeBackupApplication($team);