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 @@