Merge pull request #5573 from coollabsio/next

v4.0.0-beta.407
This commit is contained in:
Andras Bacsai
2025-04-09 09:21:34 +02:00
committed by GitHub
13 changed files with 314 additions and 43 deletions

View File

@@ -329,7 +329,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
} else { } else {
$this->write_deployment_configurations(); $this->write_deployment_configurations();
} }
$this->application_deployment_queue->addLogEntry("Starting graceful shutdown container: {$this->deployment_uuid}"); $this->application_deployment_queue->addLogEntry("Gracefully shutting down build container: {$this->deployment_uuid}");
$this->graceful_shutdown_container($this->deployment_uuid); $this->graceful_shutdown_container($this->deployment_uuid);
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id')); ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
@@ -1361,7 +1361,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
} }
} }
$this->application_deployment_queue->addLogEntry("Preparing container with helper image: $helperImage."); $this->application_deployment_queue->addLogEntry("Preparing container with helper image: $helperImage.");
$this->application_deployment_queue->addLogEntry("Starting graceful shutdown container: {$this->deployment_uuid}");
$this->graceful_shutdown_container($this->deployment_uuid); $this->graceful_shutdown_container($this->deployment_uuid);
$this->execute_remote_command( $this->execute_remote_command(
[ [
@@ -1710,6 +1709,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
]); ]);
$this->application->parseHealthcheckFromDockerfile($this->saved_outputs->get('dockerfile_from_repo')); $this->application->parseHealthcheckFromDockerfile($this->saved_outputs->get('dockerfile_from_repo'));
} }
$custom_network_aliases = [];
if (is_array($this->application->custom_network_aliases) && count($this->application->custom_network_aliases) > 0) {
$custom_network_aliases = $this->application->custom_network_aliases;
}
$docker_compose = [ $docker_compose = [
'services' => [ 'services' => [
$this->container_name => [ $this->container_name => [
@@ -1719,9 +1722,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
'expose' => $ports, 'expose' => $ports,
'networks' => [ 'networks' => [
$this->destination->network => [ $this->destination->network => [
'aliases' => [ 'aliases' => array_merge(
$this->container_name, [$this->container_name],
], $custom_network_aliases
),
], ],
], ],
'mem_limit' => $this->application->limits_memory, 'mem_limit' => $this->application->limits_memory,

View File

@@ -68,6 +68,7 @@ class General extends Component
'application.publish_directory' => 'nullable', 'application.publish_directory' => 'nullable',
'application.ports_exposes' => 'required', 'application.ports_exposes' => 'required',
'application.ports_mappings' => 'nullable', 'application.ports_mappings' => 'nullable',
'application.custom_network_aliases' => 'nullable',
'application.dockerfile' => 'nullable', 'application.dockerfile' => 'nullable',
'application.docker_registry_image_name' => 'nullable', 'application.docker_registry_image_name' => 'nullable',
'application.docker_registry_image_tag' => 'nullable', 'application.docker_registry_image_tag' => 'nullable',
@@ -121,6 +122,7 @@ class General extends Component
'application.custom_labels' => 'Custom labels', 'application.custom_labels' => 'Custom labels',
'application.dockerfile_target_build' => 'Dockerfile target build', 'application.dockerfile_target_build' => 'Dockerfile target build',
'application.custom_docker_run_options' => 'Custom docker run commands', 'application.custom_docker_run_options' => 'Custom docker run commands',
'application.custom_network_aliases' => 'Custom docker network aliases',
'application.docker_compose_custom_start_command' => 'Docker compose custom start command', 'application.docker_compose_custom_start_command' => 'Docker compose custom start command',
'application.docker_compose_custom_build_command' => 'Docker compose custom build command', 'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
'application.custom_nginx_configuration' => 'Custom Nginx configuration', 'application.custom_nginx_configuration' => 'Custom Nginx configuration',

View File

@@ -21,7 +21,7 @@ class General extends Component
public string $redis_username; public string $redis_username;
public string $redis_password; public ?string $redis_password;
public string $redis_version; public string $redis_version;

View File

@@ -3,10 +3,13 @@
namespace App\Livewire\Project\Shared\EnvironmentVariable; namespace App\Livewire\Project\Shared\EnvironmentVariable;
use App\Models\EnvironmentVariable; use App\Models\EnvironmentVariable;
use App\Traits\EnvironmentVariableProtection;
use Livewire\Component; use Livewire\Component;
class All extends Component class All extends Component
{ {
use EnvironmentVariableProtection;
public $resource; public $resource;
public string $resourceClass; public string $resourceClass;
@@ -138,17 +141,57 @@ class All extends Component
private function handleBulkSubmit() private function handleBulkSubmit()
{ {
$variables = parseEnvFormatToArray($this->variables); $variables = parseEnvFormatToArray($this->variables);
$changesMade = false;
$errorOccurred = false;
$this->deleteRemovedVariables(false, $variables); // Try to delete removed variables
$this->updateOrCreateVariables(false, $variables); $deletedCount = $this->deleteRemovedVariables(false, $variables);
if ($deletedCount > 0) {
$changesMade = true;
} elseif ($deletedCount === 0 && $this->resource->environment_variables()->whereNotIn('key', array_keys($variables))->exists()) {
// If we tried to delete but couldn't (due to Docker Compose), mark as error
$errorOccurred = true;
}
// Update or create variables
$updatedCount = $this->updateOrCreateVariables(false, $variables);
if ($updatedCount > 0) {
$changesMade = true;
}
if ($this->showPreview) { if ($this->showPreview) {
$previewVariables = parseEnvFormatToArray($this->variablesPreview); $previewVariables = parseEnvFormatToArray($this->variablesPreview);
$this->deleteRemovedVariables(true, $previewVariables);
$this->updateOrCreateVariables(true, $previewVariables); // Try to delete removed preview variables
$deletedPreviewCount = $this->deleteRemovedVariables(true, $previewVariables);
if ($deletedPreviewCount > 0) {
$changesMade = true;
} elseif ($deletedPreviewCount === 0 && $this->resource->environment_variables_preview()->whereNotIn('key', array_keys($previewVariables))->exists()) {
// If we tried to delete but couldn't (due to Docker Compose), mark as error
$errorOccurred = true;
}
// Update or create preview variables
$updatedPreviewCount = $this->updateOrCreateVariables(true, $previewVariables);
if ($updatedPreviewCount > 0) {
$changesMade = true;
}
} }
$this->dispatch('success', 'Environment variables updated.'); // Debug information
\Log::info('Environment variables update status', [
'deletedCount' => $deletedCount,
'updatedCount' => $updatedCount,
'deletedPreviewCount' => $deletedPreviewCount ?? 0,
'updatedPreviewCount' => $updatedPreviewCount ?? 0,
'changesMade' => $changesMade,
'errorOccurred' => $errorOccurred,
]);
// Only show success message if changes were actually made and no errors occurred
if ($changesMade && ! $errorOccurred) {
$this->dispatch('success', 'Environment variables updated.');
}
} }
private function handleSingleSubmit($data) private function handleSingleSubmit($data)
@@ -184,11 +227,46 @@ class All extends Component
private function deleteRemovedVariables($isPreview, $variables) private function deleteRemovedVariables($isPreview, $variables)
{ {
$method = $isPreview ? 'environment_variables_preview' : 'environment_variables'; $method = $isPreview ? 'environment_variables_preview' : 'environment_variables';
// Get all environment variables that will be deleted
$variablesToDelete = $this->resource->$method()->whereNotIn('key', array_keys($variables))->get();
// If there are no variables to delete, return 0
if ($variablesToDelete->isEmpty()) {
return 0;
}
// Check for system variables that shouldn't be deleted
foreach ($variablesToDelete as $envVar) {
if ($this->isProtectedEnvironmentVariable($envVar->key)) {
$this->dispatch('error', "Cannot delete system environment variable '{$envVar->key}'.");
return 0;
}
}
// Check if any of these variables are used in Docker Compose
if ($this->resource->type() === 'service' || $this->resource->build_pack === 'dockercompose') {
foreach ($variablesToDelete as $envVar) {
[$isUsed, $reason] = $this->isEnvironmentVariableUsedInDockerCompose($envVar->key, $this->resource->docker_compose);
if ($isUsed) {
$this->dispatch('error', "Cannot delete environment variable '{$envVar->key}' <br><br>Please remove it from the Docker Compose file first.");
return 0;
}
}
}
// If we get here, no variables are used in Docker Compose, so we can delete them
$this->resource->$method()->whereNotIn('key', array_keys($variables))->delete(); $this->resource->$method()->whereNotIn('key', array_keys($variables))->delete();
return $variablesToDelete->count();
} }
private function updateOrCreateVariables($isPreview, $variables) private function updateOrCreateVariables($isPreview, $variables)
{ {
$count = 0;
foreach ($variables as $key => $value) { foreach ($variables as $key => $value) {
if (str($key)->startsWith('SERVICE_FQDN') || str($key)->startsWith('SERVICE_URL')) { if (str($key)->startsWith('SERVICE_FQDN') || str($key)->startsWith('SERVICE_URL')) {
continue; continue;
@@ -198,8 +276,12 @@ class All extends Component
if ($found) { if ($found) {
if (! $found->is_shown_once && ! $found->is_multiline) { if (! $found->is_shown_once && ! $found->is_multiline) {
$found->value = $value; // Only count as a change if the value actually changed
$found->save(); if ($found->value !== $value) {
$found->value = $value;
$found->save();
$count++;
}
} }
} else { } else {
$environment = new EnvironmentVariable; $environment = new EnvironmentVariable;
@@ -212,8 +294,11 @@ class All extends Component
$environment->resourceable_type = $this->resource->getMorphClass(); $environment->resourceable_type = $this->resource->getMorphClass();
$environment->save(); $environment->save();
$count++;
} }
} }
return $count;
} }
public function refreshEnvs() public function refreshEnvs()

View File

@@ -4,10 +4,13 @@ namespace App\Livewire\Project\Shared\EnvironmentVariable;
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable; use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
use App\Models\SharedEnvironmentVariable; use App\Models\SharedEnvironmentVariable;
use App\Traits\EnvironmentVariableProtection;
use Livewire\Component; use Livewire\Component;
class Show extends Component class Show extends Component
{ {
use EnvironmentVariableProtection;
public $parameters; public $parameters;
public ModelsEnvironmentVariable|SharedEnvironmentVariable $env; public ModelsEnvironmentVariable|SharedEnvironmentVariable $env;
@@ -40,6 +43,8 @@ class Show extends Component
public bool $is_really_required = false; public bool $is_really_required = false;
public bool $is_redis_credential = false;
protected $listeners = [ protected $listeners = [
'refreshEnvs' => 'refresh', 'refreshEnvs' => 'refresh',
'refresh', 'refresh',
@@ -65,7 +70,9 @@ class Show extends Component
} }
$this->parameters = get_route_parameters(); $this->parameters = get_route_parameters();
$this->checkEnvs(); $this->checkEnvs();
if ($this->type === 'standalone-redis' && ($this->env->key === 'REDIS_PASSWORD' || $this->env->key === 'REDIS_USERNAME')) {
$this->is_redis_credential = true;
}
} }
public function refresh() public function refresh()
@@ -171,6 +178,24 @@ class Show extends Component
public function delete() public function delete()
{ {
try { try {
// Check if the variable is protected
if ($this->isProtectedEnvironmentVariable($this->env->key)) {
$this->dispatch('error', "Cannot delete system environment variable '{$this->env->key}'.");
return;
}
// Check if the variable is used in Docker Compose
if ($this->type === 'service' || $this->type === 'application' && $this->env->resource()?->docker_compose) {
[$isUsed, $reason] = $this->isEnvironmentVariableUsedInDockerCompose($this->env->key, $this->env->resource()?->docker_compose);
if ($isUsed) {
$this->dispatch('error', "Cannot delete environment variable '{$this->env->key}' <br><br>Please remove it from the Docker Compose file first.");
return;
}
}
$this->env->delete(); $this->env->delete();
$this->dispatch('environmentVariableDeleted'); $this->dispatch('environmentVariableDeleted');
$this->dispatch('success', 'Environment variable deleted successfully.'); $this->dispatch('success', 'Environment variable deleted successfully.');

View File

@@ -45,6 +45,7 @@ use Visus\Cuid2\Cuid2;
'start_command' => ['type' => 'string', 'description' => 'Start command.'], 'start_command' => ['type' => 'string', 'description' => 'Start command.'],
'ports_exposes' => ['type' => 'string', 'description' => 'Ports exposes.'], 'ports_exposes' => ['type' => 'string', 'description' => 'Ports exposes.'],
'ports_mappings' => ['type' => 'string', 'nullable' => true, 'description' => 'Ports mappings.'], 'ports_mappings' => ['type' => 'string', 'nullable' => true, 'description' => 'Ports mappings.'],
'custom_network_aliases' => ['type' => 'string', 'nullable' => true, 'description' => 'Network aliases for Docker container.'],
'base_directory' => ['type' => 'string', 'description' => 'Base directory for all commands.'], 'base_directory' => ['type' => 'string', 'description' => 'Base directory for all commands.'],
'publish_directory' => ['type' => 'string', 'description' => 'Publish directory.'], 'publish_directory' => ['type' => 'string', 'description' => 'Publish directory.'],
'health_check_enabled' => ['type' => 'boolean', 'description' => 'Health check enabled.'], 'health_check_enabled' => ['type' => 'boolean', 'description' => 'Health check enabled.'],
@@ -115,6 +116,68 @@ class Application extends BaseModel
protected $appends = ['server_status']; protected $appends = ['server_status'];
protected $casts = ['custom_network_aliases' => 'array'];
public function customNetworkAliases(): Attribute
{
return Attribute::make(
set: function ($value) {
if (is_null($value) || $value === '') {
return null;
}
// If it's already a JSON string, decode it
if (is_string($value) && $this->isJson($value)) {
$value = json_decode($value, true);
}
// If it's a string but not JSON, treat it as a comma-separated list
if (is_string($value) && ! is_array($value)) {
$value = explode(',', $value);
}
$value = collect($value)
->map(function ($alias) {
if (is_string($alias)) {
return str_replace(' ', '-', trim($alias));
}
return null;
})
->filter()
->unique() // Remove duplicate values
->values()
->toArray();
return empty($value) ? null : json_encode($value);
},
get: function ($value) {
if (is_null($value)) {
return null;
}
if (is_string($value) && $this->isJson($value)) {
return json_decode($value, true);
}
return is_array($value) ? $value : [];
}
);
}
/**
* Check if a string is a valid JSON
*/
private function isJson($string)
{
if (! is_string($string)) {
return false;
}
json_decode($string);
return json_last_error() === JSON_ERROR_NONE;
}
protected static function booted() protected static function booted()
{ {
static::addGlobalScope('withRelations', function ($builder) { static::addGlobalScope('withRelations', function ($builder) {

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Traits;
use Symfony\Component\Yaml\Yaml;
trait EnvironmentVariableProtection
{
/**
* Check if an environment variable is protected from deletion
*
* @param string $key The environment variable key to check
* @return bool True if the variable is protected, false otherwise
*/
protected function isProtectedEnvironmentVariable(string $key): bool
{
return str($key)->startsWith('SERVICE_FQDN') || str($key)->startsWith('SERVICE_URL');
}
/**
* Check if an environment variable is used in Docker Compose
*
* @param string $key The environment variable key to check
* @param string|null $dockerCompose The Docker Compose YAML content
* @return array [bool $isUsed, string $reason] Whether the variable is used and the reason if it is
*/
protected function isEnvironmentVariableUsedInDockerCompose(string $key, ?string $dockerCompose): array
{
if (empty($dockerCompose)) {
return [false, ''];
}
try {
$dockerComposeData = Yaml::parse($dockerCompose);
$dockerEnvVars = data_get($dockerComposeData, 'services.*.environment');
foreach ($dockerEnvVars as $serviceEnvs) {
if (! is_array($serviceEnvs)) {
continue;
}
// Check for direct variable usage
foreach ($serviceEnvs as $env => $value) {
if ($env === $key) {
return [true, "Environment variable '{$key}' is used directly in the Docker Compose file."];
}
}
// Check for variable references in values
foreach ($serviceEnvs as $env => $value) {
if (is_string($value) && str_contains($value, '$'.$key)) {
return [true, "Environment variable '{$key}' is referenced in the Docker Compose file."];
}
}
}
} catch (\Exception $e) {
// If there's an error parsing the Docker Compose file, we'll assume it's not used
return [false, ''];
}
return [false, ''];
}
}

View File

@@ -2,7 +2,7 @@
return [ return [
'coolify' => [ 'coolify' => [
'version' => '4.0.0-beta.406', 'version' => '4.0.0-beta.407',
'helper_version' => '1.0.8', 'helper_version' => '1.0.8',
'realtime_version' => '1.0.6', 'realtime_version' => '1.0.6',
'self_hosted' => env('SELF_HOSTED', true), 'self_hosted' => env('SELF_HOSTED', true),

View File

@@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::table('applications', function (Blueprint $table) {
$table->text('custom_network_aliases')->nullable();
});
}
public function down()
{
Schema::table('applications', function (Blueprint $table) {
$table->dropColumn('custom_network_aliases');
});
}
};

View File

@@ -1,10 +1,10 @@
{ {
"coolify": { "coolify": {
"v4": { "v4": {
"version": "4.0.0-beta.406" "version": "4.0.0-beta.407"
}, },
"nightly": { "nightly": {
"version": "4.0.0-beta.407" "version": "4.0.0-beta.408"
}, },
"helper": { "helper": {
"version": "1.0.8" "version": "1.0.8"

View File

@@ -342,6 +342,11 @@
<x-forms.input placeholder="3000:3000" id="application.ports_mappings" label="Ports Mappings" <x-forms.input placeholder="3000:3000" id="application.ports_mappings" label="Ports Mappings"
helper="A comma separated list of ports you would like to map to the host system. Useful when you do not want to use domains.<br><br><span class='inline-block font-bold dark:text-warning'>Example:</span><br>3000:3000,3002:3002<br><br>Rolling update is not supported if you have a port mapped to the host." /> helper="A comma separated list of ports you would like to map to the host system. Useful when you do not want to use domains.<br><br><span class='inline-block font-bold dark:text-warning'>Example:</span><br>3000:3000,3002:3002<br><br>Rolling update is not supported if you have a port mapped to the host." />
@endif @endif
@if (!$application->destination->server->isSwarm())
<x-forms.input id="application.custom_network_aliases" label="Network Aliases"
helper="A comma separated list of custom network aliases you would like to add for container in Docker network.<br><br><span class='inline-block font-bold dark:text-warning'>Example:</span><br>api.internal,api.local"
wire:model="application.custom_network_aliases" />
@endif
</div> </div>
@if ($application->settings->is_container_label_readonly_enabled) @if ($application->settings->is_container_label_readonly_enabled)

View File

@@ -31,46 +31,48 @@
@else @else
<div class="flex flex-col w-full gap-2 lg:flex-row"> <div class="flex flex-col w-full gap-2 lg:flex-row">
@if ($is_multiline) @if ($is_multiline)
<x-forms.input isMultiline="{{ $is_multiline }}" id="key" /> <x-forms.input :required="$is_redis_credential" isMultiline="{{ $is_multiline }}" id="key" />
<x-forms.textarea type="password" id="value" /> <x-forms.textarea :required="$is_redis_credential" type="password" id="value" />
@else @else
<x-forms.input id="key" /> <x-forms.input :disabled="$is_redis_credential" :required="$is_redis_credential" id="key" />
<x-forms.input type="password" id="value" /> <x-forms.input :required="$is_redis_credential" type="password" id="value" />
@endif @endif
@if ($is_shared) @if ($is_shared)
<x-forms.input disabled type="password" id="real_value" /> <x-forms.input :disabled="$is_redis_credential" :required="$is_redis_credential" disabled type="password" id="real_value" />
@endif @endif
</div> </div>
@endif @endif
<div class="flex flex-col w-full gap-2 lg:flex-row"> <div class="flex flex-col w-full gap-2 lg:flex-row">
@if ($type === 'service') @if (!$is_redis_credential)
<x-forms.checkbox instantSave id="is_build_time" @if ($type === 'service')
helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for docker file, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`"
label="Build Variable?" />
<x-forms.checkbox instantSave id="is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" />
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
@else
@if ($is_shared)
<x-forms.checkbox instantSave id="is_build_time" <x-forms.checkbox instantSave id="is_build_time"
helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for docker file, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`" helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for docker file, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`"
label="Build Variable?" /> label="Build Variable?" />
<x-forms.checkbox instantSave id="is_literal" <x-forms.checkbox instantSave id="is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true." helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" /> label="Is Literal?" />
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
@else @else
@if ($isSharedVariable) @if ($is_shared)
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
@else
<x-forms.checkbox instantSave id="is_build_time" <x-forms.checkbox instantSave id="is_build_time"
helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for dockerfile, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`" helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for docker file, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`"
label="Build Variable?" /> label="Build Variable?" />
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" /> <x-forms.checkbox instantSave id="is_literal"
@if ($is_multiline === false) helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
<x-forms.checkbox instantSave id="is_literal" label="Is Literal?" />
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true." @else
label="Is Literal?" /> @if ($isSharedVariable)
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
@else
<x-forms.checkbox instantSave id="is_build_time"
helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for dockerfile, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`"
label="Build Variable?" />
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
@if ($is_multiline === false)
<x-forms.checkbox instantSave id="is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" />
@endif
@endif @endif
@endif @endif
@endif @endif

View File

@@ -1,10 +1,10 @@
{ {
"coolify": { "coolify": {
"v4": { "v4": {
"version": "4.0.0-beta.406" "version": "4.0.0-beta.407"
}, },
"nightly": { "nightly": {
"version": "4.0.0-beta.407" "version": "4.0.0-beta.408"
}, },
"helper": { "helper": {
"version": "1.0.8" "version": "1.0.8"