diff --git a/app/Livewire/Project/Shared/Storages/Show.php b/app/Livewire/Project/Shared/Storages/Show.php deleted file mode 100644 index c70ebc57fd..0000000000 --- a/app/Livewire/Project/Shared/Storages/Show.php +++ /dev/null @@ -1,201 +0,0 @@ - 'name', - 'mountPath' => 'mount', - 'hostPath' => 'host', - ]; - - protected function rules(): array - { - return [ - 'name' => ValidationPatterns::volumeNameRules(), - 'mountPath' => ['required', 'string', 'regex:'.ValidationPatterns::DIRECTORY_PATH_PATTERN], - 'hostPath' => ['nullable', 'string', 'regex:'.ValidationPatterns::DIRECTORY_PATH_PATTERN], - 'isPreviewSuffixEnabled' => 'required|boolean', - ]; - } - - protected function messages(): array - { - return array_merge( - ValidationPatterns::volumeNameMessages(), - [ - 'mountPath.regex' => 'Mount path must start with / and only contain safe path characters.', - 'hostPath.regex' => 'Host path must start with / and only contain safe path characters.', - ] - ); - } - - /** - * Sync data between component properties and model - * - * @param bool $toModel If true, sync FROM properties TO model. If false, sync FROM model TO properties. - */ - private function syncData(bool $toModel = false): void - { - if ($toModel) { - // Sync TO model (before save) - $this->storage->name = $this->name; - $this->storage->mount_path = $this->mountPath; - $this->storage->host_path = $this->hostPath; - $this->storage->is_preview_suffix_enabled = $this->isPreviewSuffixEnabled; - } else { - // Sync FROM model (on load/refresh) - $this->name = $this->storage->name; - $this->mountPath = $this->storage->mount_path; - $this->hostPath = $this->storage->host_path; - $this->isPreviewSuffixEnabled = $this->storage->is_preview_suffix_enabled ?? true; - } - } - - public function mount(): void - { - $this->syncData(false); - $this->isReadOnly = $this->storage->shouldBeReadOnlyInUI(); - // PR deployment volume suffixes only apply to git-based applications. - $this->supportsPreviewSuffix = $this->resource instanceof Application - && $this->resource->git_based() - && filled($this->resource->git_repository) - && ! $this->isService; - // Parent All batches badge/url; isolated embeds still hydrate themselves. - if (! $this->backupMetaHydrated) { - $this->refreshBackupStatus(); - } - } - - #[On('refreshVolumeBackups')] - public function refreshBackupStatus(): void - { - $backup = $this->storage->scheduledBackups()->first(); - - $this->hasEnabledBackup = $backup?->enabled ?? false; - $this->backupUrl = null; - - if (! $this->hasEnabledBackup || ! $this->resource instanceof Application) { - return; - } - - $this->resource->loadMissing('environment.project'); - - $parameters = [ - 'project_uuid' => $this->resource->project()->uuid, - 'environment_uuid' => $this->resource->environment->uuid, - 'application_uuid' => $this->resource->uuid, - ]; - $hasOtherBackups = ScheduledVolumeBackup::query() - ->forApplication($this->resource) - ->where('id', '!=', $backup->id) - ->exists(); - - $this->backupUrl = $hasOtherBackups - ? route('project.application.backup.index', [...$parameters, 'search' => $this->storage->name]) - : route('project.application.backup.show', [...$parameters, 'backup_uuid' => $backup->uuid]); - } - - public function openBackupModal(): void - { - $this->authorize('update', $this->resource); - $this->showBackupModal = true; - } - - #[On('modalClosed')] - public function onModalClosed(): void - { - // Drop the nested Create component from the DOM after close to free snapshot weight. - if ($this->showBackupModal) { - $this->showBackupModal = false; - } - } - - public function instantSave(): void - { - $this->authorize('update', $this->resource); - $this->validate(); - - $this->syncData(true); - $this->storage->save(); - $this->dispatch('success', 'Storage updated successfully'); - } - - public function submit() - { - $this->authorize('update', $this->resource); - - $this->validate(); - $this->syncData(true); - $this->storage->save(); - $this->dispatch('success', 'Storage updated successfully'); - } - - public function delete($password, $selectedActions = []) - { - $this->authorize('update', $this->resource); - - if (! verifyPasswordConfirmation($password, $this)) { - return 'The provided password is incorrect.'; - } - - if ($this->storage->scheduledBackups()->exists()) { - $this->dispatch('error', 'Delete this volume backup schedule and its archives before deleting the volume.'); - - return false; - } - - $this->storage->delete(); - $this->dispatch('storageCountsChanged')->to(StorageComponent::class); - $this->dispatch('configurationChanged'); - - return true; - } -} diff --git a/resources/views/livewire/project/shared/storages/all.blade.php b/resources/views/livewire/project/shared/storages/all.blade.php index dbe21fd7b8..cfbc58972b 100644 --- a/resources/views/livewire/project/shared/storages/all.blade.php +++ b/resources/views/livewire/project/shared/storages/all.blade.php @@ -183,7 +183,7 @@ @if ($supportsPreviewSuffix)
PR suffix - diff --git a/resources/views/livewire/project/shared/storages/show.blade.php b/resources/views/livewire/project/shared/storages/show.blade.php deleted file mode 100644 index 3662568087..0000000000 --- a/resources/views/livewire/project/shared/storages/show.blade.php +++ /dev/null @@ -1,156 +0,0 @@ -@php - $showActionsColumn = $resource instanceof \App\Models\Application; - $gridClass = match (true) { - $supportsPreviewSuffix => 'volumes-table-grid-with-pr', - $showActionsColumn => 'volumes-table-grid', - default => 'volumes-table-grid-readonly', - }; - $canUpdate = auth()->user()?->can('update', $resource) ?? false; - $inputsReadonly = $isReadOnly || ! $canUpdate; - $displayHostPath = filled($hostPath) ? $hostPath : '—'; -@endphp - -@if ($inputsReadonly) - {{-- Read-only: plain data-table row (service / compose / no permission) --}} -
-
-
- Volume Name -
- {{ $name }} - @if ($hasEnabledBackup) - @if ($backupUrl) - - Backup - - @else - - Backup - - @endif - @endif -
-
- -
- Source Path - - {{ $displayHostPath }} - -
- -
- Destination Path - {{ $mountPath }} -
- - @if ($supportsPreviewSuffix) -
- PR suffix - {{ $isPreviewSuffixEnabled ? 'Add suffix' : 'Share volume' }} -
- @endif - - @if ($showActionsColumn) -
- @if ($canUpdate) - @if ($showBackupModal) - - - - @else - - Backup - - @endif - @else - — - @endif -
- @endif -
-
-@else - {{-- Editable volume row --}} -
-
-
- Volume Name -
-
- -
- @if ($hasEnabledBackup) - @if ($backupUrl) - - Backup - - @else - - Backup - - @endif - @endif -
-
- -
- Source Path - -
- -
- Destination Path - -
- - @if ($supportsPreviewSuffix) -
- PR suffix - -
- @endif - -
- - Update - - - @if ($resource instanceof \App\Models\Application) - @if ($showBackupModal) - - - - @else - - Backup - - @endif - @endif - - -
-
-
-@endif diff --git a/tests/Feature/FileStorageMountPathTest.php b/tests/Feature/FileStorageMountPathTest.php index 0b9e450a29..cd301c2557 100644 --- a/tests/Feature/FileStorageMountPathTest.php +++ b/tests/Feature/FileStorageMountPathTest.php @@ -4,7 +4,6 @@ use App\Jobs\ServerStorageSaveJob; use App\Livewire\Project\Service\FileStorage; use App\Livewire\Project\Service\Storage; use App\Livewire\Project\Shared\Storages\All; -use App\Livewire\Project\Shared\Storages\Show; use App\Models\Application; use App\Models\Environment; use App\Models\InstanceSettings; @@ -247,8 +246,8 @@ test('deleting a volume mount refreshes the configuration warning', function () 'resource_type' => $database->getMorphClass(), ]); - Livewire::test(Show::class, ['storage' => $volume, 'resource' => $database]) - ->call('delete', 'password') + Livewire::test(All::class, ['resource' => $database]) + ->call('delete', $volume->id, 'password') ->assertDispatched('configurationChanged'); expect($volume->fresh())->toBeNull(); diff --git a/tests/Feature/PersistentStoragePerformanceTest.php b/tests/Feature/PersistentStoragePerformanceTest.php index caad5822a1..6d1df54607 100644 --- a/tests/Feature/PersistentStoragePerformanceTest.php +++ b/tests/Feature/PersistentStoragePerformanceTest.php @@ -91,7 +91,7 @@ function createPerfApplicationWithVolumes(int $volumeCount = 5): array return [$application, $firstVolume, $team]; } -it('renders volume rows without nesting Livewire Show components', function () { +it('renders volume rows inline without nested Livewire row components', function () { [$application] = createPerfApplicationWithVolumes(5); $html = Livewire::test(All::class, ['resource' => $application])->html(); @@ -100,7 +100,6 @@ it('renders volume rows without nesting Livewire Show components', function () { ->toContain('data-table') ->toContain('openBackupModal') ->toContain('wire:submit="submit(') - ->not->toContain('livewire:project.shared.storages.show') ->not->toContain('shared-configure-volume-backup-'); }); diff --git a/tests/Feature/PersistentStorageVolumesLayoutTest.php b/tests/Feature/PersistentStorageVolumesLayoutTest.php index d7ae4ba8c0..6546a00e31 100644 --- a/tests/Feature/PersistentStorageVolumesLayoutTest.php +++ b/tests/Feature/PersistentStorageVolumesLayoutTest.php @@ -141,7 +141,6 @@ function createApplicationWithVolume(array $applicationAttributes = [], array $v it('renders volumes as a data table with shared column headers', function () { $allView = file_get_contents(resource_path('views/livewire/project/shared/storages/all.blade.php')); - $showView = file_get_contents(resource_path('views/livewire/project/shared/storages/show.blade.php')); $storageView = file_get_contents(resource_path('views/livewire/project/service/storage.blade.php')); expect($allView) @@ -159,17 +158,10 @@ it('renders volumes as a data table with shared column headers', function () { ->toContain('data-table-row') ->toContain('volumes-mobile-label') ->not->toContain('table-badge table-badge-success') - ->not->toContain('livewire:project.shared.storages.show') ->not->toContain('x-status-badge') ->not->toContain('font-mono') ->not->toContain('Service volume mounts are read-only here.'); - // Show remains available for isolated embeds/tests but is no longer nested from All. - expect($showView) - ->toContain('data-table-row') - ->toContain('volumes-table-grid') - ->not->toContain('font-mono'); - // Service stack page: one settings-section card per compose service/resource. expect($storageView) ->toContain('Str::headline($resource->name)') @@ -434,16 +426,13 @@ it('hides PR deployment suffix for databases', function () { }); it('uses a compact table badge for enabled backups instead of status-badge', function () { - $showView = file_get_contents(resource_path('views/livewire/project/shared/storages/show.blade.php')); + $allView = file_get_contents(resource_path('views/livewire/project/shared/storages/all.blade.php')); - expect($showView) - ->toContain('table-badge-success') + expect($allView) + ->toContain("'table-badge-success' => \$hasS3Backup") ->toContain('Volume backup is enabled') ->not->toContain('x-status-badge') ->not->toContain('status="Backup enabled"'); - - // Badge label is the short "Backup" text, not the old pill-with-label that broke the input row. - expect(preg_match('/table-badge-success[^>]*>\s*Backup\s*toBeGreaterThan(0); }); it('gates file storage PR suffix markup behind git_based applications', function () { diff --git a/tests/Feature/VolumeBackupTest.php b/tests/Feature/VolumeBackupTest.php index b24b45dcad..519b323f6b 100644 --- a/tests/Feature/VolumeBackupTest.php +++ b/tests/Feature/VolumeBackupTest.php @@ -8,7 +8,7 @@ use App\Livewire\Project\Application\Backup\Create as CreateScheduledVolumeBacku use App\Livewire\Project\Service\FileStorage; use App\Livewire\Project\Service\VolumeBackup\Create as CreateServiceVolumeBackup; use App\Livewire\Project\Service\VolumeBackup\Index as ServiceVolumeBackupIndex; -use App\Livewire\Project\Shared\Storages\Show; +use App\Livewire\Project\Shared\Storages\All; use App\Livewire\Project\Shared\Storages\VolumeBackups; use App\Models\Application; use App\Models\Environment; @@ -483,11 +483,8 @@ it('shows the configure backup modal trigger inside the volume card instead of i signInForVolumeBackups($this, $team); [$application, $volume] = createVolumeBackupApplication($team); - $component = Livewire::test(Show::class, [ - 'storage' => $volume, - 'resource' => $application, - ]) - ->set('isReadOnly', true) + $component = Livewire::test(All::class, ['resource' => $application]) + ->set("forms.{$volume->id}.isReadOnly", true) ->assertSee('Backup') ->assertDontSee('Backups made while the application is writing'); @@ -497,6 +494,7 @@ it('shows the configure backup modal trigger inside the volume card instead of i expect($html) ->toContain('Configure Volume Backup') ->toContain('data-table-row') + ->not->toContain('wire:submit="submit('.$volume->id.')"') ->toContain('Backup'); }); @@ -512,10 +510,10 @@ it('only shows the backup enabled badge for an enabled volume backup', function 'enabled' => false, ]); - $component = Livewire::test(Show::class, [ - 'storage' => $volume, - 'resource' => $application, - ])->assertDontSee('table-badge-success', false); + $component = Livewire::test(All::class, ['resource' => $application]) + ->assertDontSee('Volume backup is enabled'); + + expect($component->get("volumeBackupMeta.{$volume->id}.enabled"))->toBeFalse(); $backup->update(['enabled' => true]); @@ -528,17 +526,10 @@ it('only shows the backup enabled badge for an enabled volume backup', function $component ->dispatch('refreshVolumeBackups') - ->assertSee('table-badge-success', false) ->assertSee('Volume backup is enabled') ->assertSee('href="'.$backupUrl.'"', false); - Livewire::test(Show::class, [ - 'storage' => $volume, - 'resource' => $application, - 'isFirst' => false, - ]) - ->assertSee('table-badge-success', false) - ->assertSee('Volume backup is enabled'); + expect($component->get("volumeBackupMeta.{$volume->id}.url"))->toBe($backupUrl); }); it('links the backup enabled badge to a filtered backup list when the application has multiple schedules', function () { @@ -564,11 +555,7 @@ it('links the backup enabled badge to a filtered backup list when the applicatio 'search' => $volume->name, ]); - Livewire::test(Show::class, [ - 'storage' => $volume, - 'resource' => $application, - ]) - ->assertSee('table-badge-success', false) + Livewire::test(All::class, ['resource' => $application]) ->assertSee('Volume backup is enabled') ->assertSee('href="'.$backupUrl.'"', false); });