From 9bc33d65abd022884ddc6d0e3c463ad4032bb144 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Thu, 11 Dec 2025 21:25:33 +0100 Subject: [PATCH] fix: Improve read-only volume detection and UI messaging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add isServiceResource() and shouldBeReadOnlyInUI() to LocalFileVolume - Update path matching to handle leading slashes in volume comparisons - Update FileStorage and Show components to use shouldBeReadOnlyInUI() - Show consolidated warning message for service/compose resources in all.blade.php - Remove redundant per-volume warnings for service resources - Clean up configuration.blade.php formatting 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- app/Livewire/Project/Service/FileStorage.php | 2 +- app/Livewire/Project/Shared/Storages/Show.php | 2 +- app/Models/LocalFileVolume.php | 33 ++++++++++++++++--- .../project/service/configuration.blade.php | 13 +++----- .../project/service/storage.blade.php | 11 ++----- .../project/shared/storages/all.blade.php | 6 ++++ .../project/shared/storages/show.blade.php | 8 +++-- 7 files changed, 49 insertions(+), 26 deletions(-) diff --git a/app/Livewire/Project/Service/FileStorage.php b/app/Livewire/Project/Service/FileStorage.php index 8a61e3c38..54ef82872 100644 --- a/app/Livewire/Project/Service/FileStorage.php +++ b/app/Livewire/Project/Service/FileStorage.php @@ -62,7 +62,7 @@ public function mount() $this->fs_path = $this->fileStorage->fs_path; } - $this->isReadOnly = $this->fileStorage->isReadOnlyVolume(); + $this->isReadOnly = $this->fileStorage->shouldBeReadOnlyInUI(); $this->syncData(); } diff --git a/app/Livewire/Project/Shared/Storages/Show.php b/app/Livewire/Project/Shared/Storages/Show.php index 5970ec904..c8dc68d66 100644 --- a/app/Livewire/Project/Shared/Storages/Show.php +++ b/app/Livewire/Project/Shared/Storages/Show.php @@ -67,7 +67,7 @@ private function syncData(bool $toModel = false): void public function mount() { $this->syncData(false); - $this->isReadOnly = $this->storage->isReadOnlyVolume(); + $this->isReadOnly = $this->storage->shouldBeReadOnlyInUI(); } public function submit() diff --git a/app/Models/LocalFileVolume.php b/app/Models/LocalFileVolume.php index dda5de194..9d7095cb5 100644 --- a/app/Models/LocalFileVolume.php +++ b/app/Models/LocalFileVolume.php @@ -209,6 +209,23 @@ public function scopeWherePlainMountPath($query, $path) return $query->get()->where('plain_mount_path', $path); } + // Check if this volume belongs to a service resource + public function isServiceResource(): bool + { + return in_array($this->resource_type, [ + 'App\Models\ServiceApplication', + 'App\Models\ServiceDatabase', + ]); + } + + // Determine if this volume should be read-only in the UI + // File/directory mounts can be edited even for services + public function shouldBeReadOnlyInUI(): bool + { + // Check for explicit :ro flag in compose (existing logic) + return $this->isReadOnlyVolume(); + } + // Check if this volume is read-only by parsing the docker-compose content public function isReadOnlyVolume(): bool { @@ -251,8 +268,12 @@ public function isReadOnlyVolume(): bool $containerPath = $parts[1]; $options = $parts[2] ?? null; - // Match based on mount_path (container path) - if ($containerPath === $this->mount_path) { + // Match based on mount_path + // Remove leading slash from mount_path if present for comparison + $mountPath = str($this->mount_path)->ltrim('/')->toString(); + $containerPathClean = str($containerPath)->ltrim('/')->toString(); + + if ($mountPath === $containerPathClean || $this->mount_path === $containerPath) { return $options === 'ro'; } } @@ -261,8 +282,12 @@ public function isReadOnlyVolume(): bool $containerPath = data_get($volume, 'target'); $readOnly = data_get($volume, 'read_only', false); - // Match based on mount_path (container path) - if ($containerPath === $this->mount_path) { + // Match based on mount_path + // Remove leading slash from mount_path if present for comparison + $mountPath = str($this->mount_path)->ltrim('/')->toString(); + $containerPathClean = str($containerPath)->ltrim('/')->toString(); + + if ($mountPath === $containerPathClean || $this->mount_path === $containerPath) { return $readOnly === true; } } diff --git a/resources/views/livewire/project/service/configuration.blade.php b/resources/views/livewire/project/service/configuration.blade.php index 7379ca706..f1ad3e06a 100644 --- a/resources/views/livewire/project/service/configuration.blade.php +++ b/resources/views/livewire/project/service/configuration.blade.php @@ -37,7 +37,7 @@

Services

- @if($applications->isEmpty() && $databases->isEmpty()) + @if ($applications->isEmpty() && $databases->isEmpty())
No services defined in this Docker Compose file.
@@ -76,7 +76,8 @@ @if ($application->fqdn) {{ Str::limit($application->fqdn, 60) }} @can('update', $service) - + @endif -
{{ formatContainerStatus($application->status) }}
+
{{ formatContainerStatus($application->status) }}
@if ($database->isBackupSolutionAvailable() || $database->is_migrated) @@ -185,10 +186,6 @@ class="w-4 h-4 dark:text-warning text-coollabs"

Storages

Persistent storage to preserve data between deployments.
-
@foreach ($applications as $application) diff --git a/resources/views/livewire/project/service/storage.blade.php b/resources/views/livewire/project/service/storage.blade.php index d55bd801a..9e32cd22d 100644 --- a/resources/views/livewire/project/service/storage.blade.php +++ b/resources/views/livewire/project/service/storage.blade.php @@ -275,15 +275,9 @@ class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5
Persistent storage to preserve data between deployments.
- @if ($resource?->build_pack === 'dockercompose') -
Please modify storage layout in your Docker Compose - file or reload the compose file to reread the storage layout.
- @else - @if ($resource->persistentStorages()->get()->count() === 0 && $fileStorage->count() == 0) -
No storage found.
- @endif + @if ($resource->persistentStorages()->get()->count() === 0 && $fileStorage->count() == 0) +
No storage found.
@endif - @php $hasVolumes = $this->volumeCount > 0; $hasFiles = $this->fileCount > 0; @@ -370,7 +364,6 @@ class="px-4 py-2 -mb-px font-medium transition-colors {{ $hasDirectories ? 'dark

{{ Str::headline($resource->name) }}

- @if ($resource->persistentStorages()->get()->count() === 0 && $fileStorage->count() == 0)
No storage found.
@endif diff --git a/resources/views/livewire/project/shared/storages/all.blade.php b/resources/views/livewire/project/shared/storages/all.blade.php index d62362562..ea8e55e41 100644 --- a/resources/views/livewire/project/shared/storages/all.blade.php +++ b/resources/views/livewire/project/shared/storages/all.blade.php @@ -1,5 +1,11 @@
+ @if ($resource->type() === 'service' || data_get($resource, 'build_pack') === 'dockercompose') +
+ Volume mounts are read-only. If you would like to add or modify a volume, you must edit your Docker + Compose file and reload the compose file. +
+ @endif @foreach ($resource->persistentStorages as $storage) @if ($resource->type() === 'service')
@if ($isReadOnly) -
- This volume is mounted as read-only and cannot be modified from the UI. -
+ @if (!$storage->isServiceResource() && !$storage->isDockerComposeResource()) +
+ This volume is mounted as read-only and cannot be modified from the UI. +
+ @endif @if ($isFirst)
@if (