diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php
index 4b0cfc6ab..6188651a1 100644
--- a/app/Http/Controllers/Api/ApplicationsController.php
+++ b/app/Http/Controllers/Api/ApplicationsController.php
@@ -18,6 +18,7 @@
use App\Rules\ValidGitBranch;
use App\Rules\ValidGitRepositoryUrl;
use App\Services\DockerImageParser;
+use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Validator;
@@ -3919,4 +3920,260 @@ private function validateDataApplications(Request $request, Server $server)
}
}
}
+
+ #[OA\Get(
+ summary: 'List Storages',
+ description: 'List all persistent storages and file storages by application UUID.',
+ path: '/applications/{uuid}/storages',
+ operationId: 'list-storages-by-application-uuid',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Applications'],
+ parameters: [
+ new OA\Parameter(
+ name: 'uuid',
+ in: 'path',
+ description: 'UUID of the application.',
+ required: true,
+ schema: new OA\Schema(
+ type: 'string',
+ )
+ ),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'All storages by application UUID.',
+ content: new OA\JsonContent(
+ properties: [
+ new OA\Property(property: 'persistent_storages', type: 'array', items: new OA\Items(type: 'object')),
+ new OA\Property(property: 'file_storages', type: 'array', items: new OA\Items(type: 'object')),
+ ],
+ ),
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ public function storages(Request $request): JsonResponse
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
+
+ if (! $application) {
+ return response()->json([
+ 'message' => 'Application not found',
+ ], 404);
+ }
+
+ $this->authorize('view', $application);
+
+ $persistentStorages = $application->persistentStorages->sortBy('id')->values();
+ $fileStorages = $application->fileStorages->sortBy('id')->values();
+
+ return response()->json([
+ 'persistent_storages' => $persistentStorages,
+ 'file_storages' => $fileStorages,
+ ]);
+ }
+
+ #[OA\Patch(
+ summary: 'Update Storage',
+ description: 'Update a persistent storage or file storage by application UUID.',
+ path: '/applications/{uuid}/storages',
+ operationId: 'update-storage-by-application-uuid',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Applications'],
+ parameters: [
+ new OA\Parameter(
+ name: 'uuid',
+ in: 'path',
+ description: 'UUID of the application.',
+ required: true,
+ schema: new OA\Schema(
+ type: 'string',
+ )
+ ),
+ ],
+ requestBody: new OA\RequestBody(
+ description: 'Storage updated. For read-only storages (from docker-compose or services), only is_preview_suffix_enabled can be updated.',
+ required: true,
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ required: ['id', 'type'],
+ properties: [
+ 'id' => ['type' => 'integer', 'description' => 'The ID of the storage.'],
+ 'type' => ['type' => 'string', 'enum' => ['persistent', 'file'], 'description' => 'The type of storage: persistent or file.'],
+ 'is_preview_suffix_enabled' => ['type' => 'boolean', 'description' => 'Whether to add -pr-N suffix for preview deployments.'],
+ 'name' => ['type' => 'string', 'description' => 'The volume name (persistent only, not allowed for read-only storages).'],
+ 'mount_path' => ['type' => 'string', 'description' => 'The container mount path (not allowed for read-only storages).'],
+ 'host_path' => ['type' => 'string', 'nullable' => true, 'description' => 'The host path (persistent only, not allowed for read-only storages).'],
+ 'content' => ['type' => 'string', 'nullable' => true, 'description' => 'The file content (file only, not allowed for read-only storages).'],
+ ],
+ additionalProperties: false,
+ ),
+ ),
+ ],
+ ),
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Storage updated.',
+ content: new OA\JsonContent(type: 'object'),
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ new OA\Response(
+ response: 422,
+ ref: '#/components/responses/422',
+ ),
+ ]
+ )]
+ public function update_storage(Request $request): JsonResponse
+ {
+ $teamId = getTeamIdFromToken();
+
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+
+ $return = validateIncomingRequest($request);
+ if ($return instanceof \Illuminate\Http\JsonResponse) {
+ return $return;
+ }
+
+ $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
+
+ if (! $application) {
+ return response()->json([
+ 'message' => 'Application not found',
+ ], 404);
+ }
+
+ $this->authorize('update', $application);
+
+ $validator = customApiValidator($request->all(), [
+ 'id' => 'required|integer',
+ 'type' => 'required|string|in:persistent,file',
+ 'is_preview_suffix_enabled' => 'boolean',
+ 'name' => 'string',
+ 'mount_path' => 'string',
+ 'host_path' => 'string|nullable',
+ 'content' => 'string|nullable',
+ ]);
+
+ $allAllowedFields = ['id', 'type', 'is_preview_suffix_enabled', 'name', 'mount_path', 'host_path', 'content'];
+ $extraFields = array_diff(array_keys($request->all()), $allAllowedFields);
+ if ($validator->fails() || ! empty($extraFields)) {
+ $errors = $validator->errors();
+ if (! empty($extraFields)) {
+ foreach ($extraFields as $field) {
+ $errors->add($field, 'This field is not allowed.');
+ }
+ }
+
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => $errors,
+ ], 422);
+ }
+
+ if ($request->type === 'persistent') {
+ $storage = $application->persistentStorages->where('id', $request->id)->first();
+ } else {
+ $storage = $application->fileStorages->where('id', $request->id)->first();
+ }
+
+ if (! $storage) {
+ return response()->json([
+ 'message' => 'Storage not found.',
+ ], 404);
+ }
+
+ $isReadOnly = $storage->shouldBeReadOnlyInUI();
+ $editableOnlyFields = ['name', 'mount_path', 'host_path', 'content'];
+ $requestedEditableFields = array_intersect($editableOnlyFields, array_keys($request->all()));
+
+ if ($isReadOnly && ! empty($requestedEditableFields)) {
+ return response()->json([
+ 'message' => 'This storage is read-only (managed by docker-compose or service definition). Only is_preview_suffix_enabled can be updated.',
+ 'read_only_fields' => array_values($requestedEditableFields),
+ ], 422);
+ }
+
+ // Reject fields that don't apply to the given storage type
+ if (! $isReadOnly) {
+ $typeSpecificInvalidFields = $request->type === 'persistent'
+ ? array_intersect(['content'], array_keys($request->all()))
+ : array_intersect(['name', 'host_path'], array_keys($request->all()));
+
+ if (! empty($typeSpecificInvalidFields)) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => collect($typeSpecificInvalidFields)
+ ->mapWithKeys(fn ($field) => [$field => "Field '{$field}' is not valid for type '{$request->type}'."]),
+ ], 422);
+ }
+ }
+
+ // Always allowed
+ if ($request->has('is_preview_suffix_enabled')) {
+ $storage->is_preview_suffix_enabled = $request->is_preview_suffix_enabled;
+ }
+
+ // Only for editable storages
+ if (! $isReadOnly) {
+ if ($request->type === 'persistent') {
+ if ($request->has('name')) {
+ $storage->name = $request->name;
+ }
+ if ($request->has('mount_path')) {
+ $storage->mount_path = $request->mount_path;
+ }
+ if ($request->has('host_path')) {
+ $storage->host_path = $request->host_path;
+ }
+ } else {
+ if ($request->has('mount_path')) {
+ $storage->mount_path = $request->mount_path;
+ }
+ if ($request->has('content')) {
+ $storage->content = $request->content;
+ }
+ }
+ }
+
+ $storage->save();
+
+ return response()->json($storage);
+ }
}
diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php
index cbec016e9..7adb938c5 100644
--- a/app/Jobs/ApplicationDeploymentJob.php
+++ b/app/Jobs/ApplicationDeploymentJob.php
@@ -2745,7 +2745,8 @@ private function generate_local_persistent_volumes()
} else {
$volume_name = $persistentStorage->name;
}
- if ($this->pull_request_id !== 0) {
+ $isPreviewSuffixEnabled = (bool) data_get($persistentStorage, 'is_preview_suffix_enabled', true);
+ if ($this->pull_request_id !== 0 && $isPreviewSuffixEnabled) {
$volume_name = addPreviewDeploymentSuffix($volume_name, $this->pull_request_id);
}
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
@@ -2763,7 +2764,8 @@ private function generate_local_persistent_volumes_only_volume_names()
}
$name = $persistentStorage->name;
- if ($this->pull_request_id !== 0) {
+ $isPreviewSuffixEnabled = (bool) data_get($persistentStorage, 'is_preview_suffix_enabled', true);
+ if ($this->pull_request_id !== 0 && $isPreviewSuffixEnabled) {
$name = addPreviewDeploymentSuffix($name, $this->pull_request_id);
}
diff --git a/app/Livewire/Project/Service/FileStorage.php b/app/Livewire/Project/Service/FileStorage.php
index 5d948bffd..844e37854 100644
--- a/app/Livewire/Project/Service/FileStorage.php
+++ b/app/Livewire/Project/Service/FileStorage.php
@@ -40,12 +40,16 @@ class FileStorage extends Component
#[Validate(['required', 'boolean'])]
public bool $isBasedOnGit = false;
+ #[Validate(['required', 'boolean'])]
+ public bool $isPreviewSuffixEnabled = true;
+
protected $rules = [
'fileStorage.is_directory' => 'required',
'fileStorage.fs_path' => 'required',
'fileStorage.mount_path' => 'required',
'content' => 'nullable',
'isBasedOnGit' => 'required|boolean',
+ 'isPreviewSuffixEnabled' => 'required|boolean',
];
public function mount()
@@ -71,12 +75,14 @@ public function syncData(bool $toModel = false): void
// Sync to model
$this->fileStorage->content = $this->content;
$this->fileStorage->is_based_on_git = $this->isBasedOnGit;
+ $this->fileStorage->is_preview_suffix_enabled = $this->isPreviewSuffixEnabled;
$this->fileStorage->save();
} else {
// Sync from model
$this->content = $this->fileStorage->content;
$this->isBasedOnGit = $this->fileStorage->is_based_on_git;
+ $this->isPreviewSuffixEnabled = $this->fileStorage->is_preview_suffix_enabled ?? true;
}
}
@@ -175,6 +181,7 @@ public function submit()
// Sync component properties to model
$this->fileStorage->content = $this->content;
$this->fileStorage->is_based_on_git = $this->isBasedOnGit;
+ $this->fileStorage->is_preview_suffix_enabled = $this->isPreviewSuffixEnabled;
$this->fileStorage->save();
$this->fileStorage->saveStorageOnServer();
$this->dispatch('success', 'File updated.');
@@ -187,9 +194,11 @@ public function submit()
}
}
- public function instantSave()
+ public function instantSave(): void
{
- $this->submit();
+ $this->authorize('update', $this->resource);
+ $this->syncData(true);
+ $this->dispatch('success', 'File updated.');
}
public function render()
diff --git a/app/Livewire/Project/Shared/Storages/Show.php b/app/Livewire/Project/Shared/Storages/Show.php
index 69395a591..eee5a0776 100644
--- a/app/Livewire/Project/Shared/Storages/Show.php
+++ b/app/Livewire/Project/Shared/Storages/Show.php
@@ -29,10 +29,13 @@ class Show extends Component
public ?string $hostPath = null;
+ public bool $isPreviewSuffixEnabled = true;
+
protected $rules = [
'name' => 'required|string',
'mountPath' => 'required|string',
'hostPath' => 'string|nullable',
+ 'isPreviewSuffixEnabled' => 'required|boolean',
];
protected $validationAttributes = [
@@ -53,11 +56,13 @@ private function syncData(bool $toModel = false): void
$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;
}
}
@@ -67,6 +72,16 @@ public function mount()
$this->isReadOnly = $this->storage->shouldBeReadOnlyInUI();
}
+ 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);
diff --git a/app/Models/LocalFileVolume.php b/app/Models/LocalFileVolume.php
index 9d7095cb5..da58ed2f9 100644
--- a/app/Models/LocalFileVolume.php
+++ b/app/Models/LocalFileVolume.php
@@ -14,6 +14,7 @@ class LocalFileVolume extends BaseModel
// 'mount_path' => 'encrypted',
'content' => 'encrypted',
'is_directory' => 'boolean',
+ 'is_preview_suffix_enabled' => 'boolean',
];
use HasFactory;
diff --git a/app/Models/LocalPersistentVolume.php b/app/Models/LocalPersistentVolume.php
index 7126253ea..1721f4afe 100644
--- a/app/Models/LocalPersistentVolume.php
+++ b/app/Models/LocalPersistentVolume.php
@@ -10,6 +10,10 @@ class LocalPersistentVolume extends Model
{
protected $guarded = [];
+ protected $casts = [
+ 'is_preview_suffix_enabled' => 'boolean',
+ ];
+
public function resource()
{
return $this->morphTo('resource');
diff --git a/bootstrap/helpers/parsers.php b/bootstrap/helpers/parsers.php
index e84df55f9..cd4928d63 100644
--- a/bootstrap/helpers/parsers.php
+++ b/bootstrap/helpers/parsers.php
@@ -789,7 +789,10 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
$mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
}
$source = replaceLocalSource($source, $mainDirectory);
- if ($isPullRequest) {
+ $isPreviewSuffixEnabled = $foundConfig
+ ? (bool) data_get($foundConfig, 'is_preview_suffix_enabled', true)
+ : true;
+ if ($isPullRequest && $isPreviewSuffixEnabled) {
$source = addPreviewDeploymentSuffix($source, $pull_request_id);
}
LocalFileVolume::updateOrCreate(
@@ -1315,19 +1318,19 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
}
if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) {
$shouldGenerateLabelsExactly = $resource->destination->server->settings->generate_exact_labels;
- $uuid = $resource->uuid;
- $network = data_get($resource, 'destination.network');
+ $labelUuid = $resource->uuid;
+ $labelNetwork = data_get($resource, 'destination.network');
if ($isPullRequest) {
- $uuid = "{$resource->uuid}-{$pullRequestId}";
+ $labelUuid = "{$resource->uuid}-{$pullRequestId}";
}
if ($isPullRequest) {
- $network = "{$resource->destination->network}-{$pullRequestId}";
+ $labelNetwork = "{$resource->destination->network}-{$pullRequestId}";
}
if ($shouldGenerateLabelsExactly) {
switch ($server->proxyType()) {
case ProxyTypes::TRAEFIK->value:
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
- uuid: $uuid,
+ uuid: $labelUuid,
domains: $fqdns,
is_force_https_enabled: $originalResource->isForceHttpsEnabled(),
serviceLabels: $serviceLabels,
@@ -1339,8 +1342,8 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
break;
case ProxyTypes::CADDY->value:
$serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
- network: $network,
- uuid: $uuid,
+ network: $labelNetwork,
+ uuid: $labelUuid,
domains: $fqdns,
is_force_https_enabled: $originalResource->isForceHttpsEnabled(),
serviceLabels: $serviceLabels,
@@ -1354,7 +1357,7 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
}
} else {
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
- uuid: $uuid,
+ uuid: $labelUuid,
domains: $fqdns,
is_force_https_enabled: $originalResource->isForceHttpsEnabled(),
serviceLabels: $serviceLabels,
@@ -1364,8 +1367,8 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
image: $image
));
$serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
- network: $network,
- uuid: $uuid,
+ network: $labelNetwork,
+ uuid: $labelUuid,
domains: $fqdns,
is_force_https_enabled: $originalResource->isForceHttpsEnabled(),
serviceLabels: $serviceLabels,
diff --git a/database/migrations/2026_03_16_000000_add_is_preview_suffix_enabled_to_volume_tables.php b/database/migrations/2026_03_16_000000_add_is_preview_suffix_enabled_to_volume_tables.php
new file mode 100644
index 000000000..a1f1d9ea1
--- /dev/null
+++ b/database/migrations/2026_03_16_000000_add_is_preview_suffix_enabled_to_volume_tables.php
@@ -0,0 +1,30 @@
+boolean('is_preview_suffix_enabled')->default(true)->after('is_based_on_git');
+ });
+
+ Schema::table('local_persistent_volumes', function (Blueprint $table) {
+ $table->boolean('is_preview_suffix_enabled')->default(true)->after('host_path');
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::table('local_file_volumes', function (Blueprint $table) {
+ $table->dropColumn('is_preview_suffix_enabled');
+ });
+
+ Schema::table('local_persistent_volumes', function (Blueprint $table) {
+ $table->dropColumn('is_preview_suffix_enabled');
+ });
+ }
+};
diff --git a/openapi.json b/openapi.json
index f5d9813b3..5477420ab 100644
--- a/openapi.json
+++ b/openapi.json
@@ -3442,6 +3442,167 @@
]
}
},
+ "\/applications\/{uuid}\/storages": {
+ "get": {
+ "tags": [
+ "Applications"
+ ],
+ "summary": "List Storages",
+ "description": "List all persistent storages and file storages by application UUID.",
+ "operationId": "list-storages-by-application-uuid",
+ "parameters": [
+ {
+ "name": "uuid",
+ "in": "path",
+ "description": "UUID of the application.",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "All storages by application UUID.",
+ "content": {
+ "application\/json": {
+ "schema": {
+ "properties": {
+ "persistent_storages": {
+ "type": "array",
+ "items": {
+ "type": "object"
+ }
+ },
+ "file_storages": {
+ "type": "array",
+ "items": {
+ "type": "object"
+ }
+ }
+ },
+ "type": "object"
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#\/components\/responses\/401"
+ },
+ "400": {
+ "$ref": "#\/components\/responses\/400"
+ },
+ "404": {
+ "$ref": "#\/components\/responses\/404"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ },
+ "patch": {
+ "tags": [
+ "Applications"
+ ],
+ "summary": "Update Storage",
+ "description": "Update a persistent storage or file storage by application UUID.",
+ "operationId": "update-storage-by-application-uuid",
+ "parameters": [
+ {
+ "name": "uuid",
+ "in": "path",
+ "description": "UUID of the application.",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "Storage updated. For read-only storages (from docker-compose or services), only is_preview_suffix_enabled can be updated.",
+ "required": true,
+ "content": {
+ "application\/json": {
+ "schema": {
+ "required": [
+ "id",
+ "type"
+ ],
+ "properties": {
+ "id": {
+ "type": "integer",
+ "description": "The ID of the storage."
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "persistent",
+ "file"
+ ],
+ "description": "The type of storage: persistent or file."
+ },
+ "is_preview_suffix_enabled": {
+ "type": "boolean",
+ "description": "Whether to add -pr-N suffix for preview deployments."
+ },
+ "name": {
+ "type": "string",
+ "description": "The volume name (persistent only, not allowed for read-only storages)."
+ },
+ "mount_path": {
+ "type": "string",
+ "description": "The container mount path (not allowed for read-only storages)."
+ },
+ "host_path": {
+ "type": "string",
+ "nullable": true,
+ "description": "The host path (persistent only, not allowed for read-only storages)."
+ },
+ "content": {
+ "type": "string",
+ "nullable": true,
+ "description": "The file content (file only, not allowed for read-only storages)."
+ }
+ },
+ "type": "object",
+ "additionalProperties": false
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Storage updated.",
+ "content": {
+ "application\/json": {
+ "schema": {
+ "type": "object"
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#\/components\/responses\/401"
+ },
+ "400": {
+ "$ref": "#\/components\/responses\/400"
+ },
+ "404": {
+ "$ref": "#\/components\/responses\/404"
+ },
+ "422": {
+ "$ref": "#\/components\/responses\/422"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ }
+ },
"\/cloud-tokens": {
"get": {
"tags": [
diff --git a/openapi.yaml b/openapi.yaml
index 81753544f..dd03f9c42 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -2170,6 +2170,108 @@ paths:
security:
-
bearerAuth: []
+ '/applications/{uuid}/storages':
+ get:
+ tags:
+ - Applications
+ summary: 'List Storages'
+ description: 'List all persistent storages and file storages by application UUID.'
+ operationId: list-storages-by-application-uuid
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'UUID of the application.'
+ required: true
+ schema:
+ type: string
+ responses:
+ '200':
+ description: 'All storages by application UUID.'
+ content:
+ application/json:
+ schema:
+ properties:
+ persistent_storages: { type: array, items: { type: object } }
+ file_storages: { type: array, items: { type: object } }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ patch:
+ tags:
+ - Applications
+ summary: 'Update Storage'
+ description: 'Update a persistent storage or file storage by application UUID.'
+ operationId: update-storage-by-application-uuid
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'UUID of the application.'
+ required: true
+ schema:
+ type: string
+ requestBody:
+ description: 'Storage updated. For read-only storages (from docker-compose or services), only is_preview_suffix_enabled can be updated.'
+ required: true
+ content:
+ application/json:
+ schema:
+ required:
+ - id
+ - type
+ properties:
+ id:
+ type: integer
+ description: 'The ID of the storage.'
+ type:
+ type: string
+ enum: [persistent, file]
+ description: 'The type of storage: persistent or file.'
+ is_preview_suffix_enabled:
+ type: boolean
+ description: 'Whether to add -pr-N suffix for preview deployments.'
+ name:
+ type: string
+ description: 'The volume name (persistent only, not allowed for read-only storages).'
+ mount_path:
+ type: string
+ description: 'The container mount path (not allowed for read-only storages).'
+ host_path:
+ type: string
+ nullable: true
+ description: 'The host path (persistent only, not allowed for read-only storages).'
+ content:
+ type: string
+ nullable: true
+ description: 'The file content (file only, not allowed for read-only storages).'
+ type: object
+ additionalProperties: false
+ responses:
+ '200':
+ description: 'Storage updated.'
+ content:
+ application/json:
+ schema:
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ '422':
+ $ref: '#/components/responses/422'
+ security:
+ -
+ bearerAuth: []
/cloud-tokens:
get:
tags:
diff --git a/resources/views/livewire/project/service/file-storage.blade.php b/resources/views/livewire/project/service/file-storage.blade.php
index 1dd58fe17..4bd88d761 100644
--- a/resources/views/livewire/project/service/file-storage.blade.php
+++ b/resources/views/livewire/project/service/file-storage.blade.php
@@ -15,6 +15,15 @@