feat: Update ApplicationSetting model to include additional boolean casts

- Changed `$cast` to `$casts` in ApplicationSetting model to enable proper boolean casting for new fields.
- Added boolean fields: `is_spa`, `is_build_server_enabled`, `is_preserve_repository_enabled`, `is_container_label_escape_enabled`, `is_container_label_readonly_enabled`, and `use_build_secrets`.

fix: Update Livewire component to reflect new property names

- Updated references in the Livewire component for the new camelCase property names.
- Adjusted bindings and IDs for consistency with the updated model.

test: Add unit tests for ApplicationSetting boolean casting

- Created tests to verify boolean casting for `is_static` and other boolean fields in ApplicationSetting.
- Ensured all boolean fields are correctly defined in the casts array.

test: Implement tests for SynchronizesModelData trait

- Added tests to verify the functionality of the SynchronizesModelData trait, ensuring it correctly syncs properties between the component and the model.
- Included tests for handling non-existent properties gracefully.
This commit is contained in:
Andras Bacsai
2025-11-04 08:43:33 +01:00
parent d291d85311
commit fbaa5eb369
5 changed files with 615 additions and 288 deletions

View File

@@ -3,7 +3,6 @@
namespace App\Livewire\Project\Application;
use App\Actions\Application\GenerateConfig;
use App\Livewire\Concerns\SynchronizesModelData;
use App\Models\Application;
use App\Support\ValidationPatterns;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
@@ -15,7 +14,6 @@ use Visus\Cuid2\Cuid2;
class General extends Component
{
use AuthorizesRequests;
use SynchronizesModelData;
public string $applicationId;
@@ -29,85 +27,83 @@ class General extends Component
public ?string $fqdn = null;
public string $git_repository;
public string $gitRepository;
public string $git_branch;
public string $gitBranch;
public ?string $git_commit_sha = null;
public ?string $gitCommitSha = null;
public ?string $install_command = null;
public ?string $installCommand = null;
public ?string $build_command = null;
public ?string $buildCommand = null;
public ?string $start_command = null;
public ?string $startCommand = null;
public string $build_pack;
public string $buildPack;
public string $static_image;
public string $staticImage;
public string $base_directory;
public string $baseDirectory;
public ?string $publish_directory = null;
public ?string $publishDirectory = null;
public ?string $ports_exposes = null;
public ?string $portsExposes = null;
public ?string $ports_mappings = null;
public ?string $portsMappings = null;
public ?string $custom_network_aliases = null;
public ?string $customNetworkAliases = null;
public ?string $dockerfile = null;
public ?string $dockerfile_location = null;
public ?string $dockerfileLocation = null;
public ?string $dockerfile_target_build = null;
public ?string $dockerfileTargetBuild = null;
public ?string $docker_registry_image_name = null;
public ?string $dockerRegistryImageName = null;
public ?string $docker_registry_image_tag = null;
public ?string $dockerRegistryImageTag = null;
public ?string $docker_compose_location = null;
public ?string $dockerComposeLocation = null;
public ?string $docker_compose = null;
public ?string $dockerCompose = null;
public ?string $docker_compose_raw = null;
public ?string $dockerComposeRaw = null;
public ?string $docker_compose_custom_start_command = null;
public ?string $dockerComposeCustomStartCommand = null;
public ?string $docker_compose_custom_build_command = null;
public ?string $dockerComposeCustomBuildCommand = null;
public ?string $custom_labels = null;
public ?string $customDockerRunOptions = null;
public ?string $custom_docker_run_options = null;
public ?string $preDeploymentCommand = null;
public ?string $pre_deployment_command = null;
public ?string $preDeploymentCommandContainer = null;
public ?string $pre_deployment_command_container = null;
public ?string $postDeploymentCommand = null;
public ?string $post_deployment_command = null;
public ?string $postDeploymentCommandContainer = null;
public ?string $post_deployment_command_container = null;
public ?string $customNginxConfiguration = null;
public ?string $custom_nginx_configuration = null;
public bool $isStatic = false;
public bool $is_static = false;
public bool $isSpa = false;
public bool $is_spa = false;
public bool $isBuildServerEnabled = false;
public bool $is_build_server_enabled = false;
public bool $isPreserveRepositoryEnabled = false;
public bool $is_preserve_repository_enabled = false;
public bool $isContainerLabelEscapeEnabled = true;
public bool $is_container_label_escape_enabled = true;
public bool $isContainerLabelReadonlyEnabled = false;
public bool $is_container_label_readonly_enabled = false;
public bool $isHttpBasicAuthEnabled = false;
public bool $is_http_basic_auth_enabled = false;
public ?string $httpBasicAuthUsername = null;
public ?string $http_basic_auth_username = null;
public ?string $httpBasicAuthPassword = null;
public ?string $http_basic_auth_password = null;
public ?string $watch_paths = null;
public ?string $watchPaths = null;
public string $redirect;
@@ -141,46 +137,46 @@ class General extends Component
'name' => ValidationPatterns::nameRules(),
'description' => ValidationPatterns::descriptionRules(),
'fqdn' => 'nullable',
'git_repository' => 'required',
'git_branch' => 'required',
'git_commit_sha' => 'nullable',
'install_command' => 'nullable',
'build_command' => 'nullable',
'start_command' => 'nullable',
'build_pack' => 'required',
'static_image' => 'required',
'base_directory' => 'required',
'publish_directory' => 'nullable',
'ports_exposes' => 'required',
'ports_mappings' => 'nullable',
'custom_network_aliases' => 'nullable',
'gitRepository' => 'required',
'gitBranch' => 'required',
'gitCommitSha' => 'nullable',
'installCommand' => 'nullable',
'buildCommand' => 'nullable',
'startCommand' => 'nullable',
'buildPack' => 'required',
'staticImage' => 'required',
'baseDirectory' => 'required',
'publishDirectory' => 'nullable',
'portsExposes' => 'required',
'portsMappings' => 'nullable',
'customNetworkAliases' => 'nullable',
'dockerfile' => 'nullable',
'docker_registry_image_name' => 'nullable',
'docker_registry_image_tag' => 'nullable',
'dockerfile_location' => 'nullable',
'docker_compose_location' => 'nullable',
'docker_compose' => 'nullable',
'docker_compose_raw' => 'nullable',
'dockerfile_target_build' => 'nullable',
'docker_compose_custom_start_command' => 'nullable',
'docker_compose_custom_build_command' => 'nullable',
'custom_labels' => 'nullable',
'custom_docker_run_options' => 'nullable',
'pre_deployment_command' => 'nullable',
'pre_deployment_command_container' => 'nullable',
'post_deployment_command' => 'nullable',
'post_deployment_command_container' => 'nullable',
'custom_nginx_configuration' => 'nullable',
'is_static' => 'boolean|required',
'is_spa' => 'boolean|required',
'is_build_server_enabled' => 'boolean|required',
'is_container_label_escape_enabled' => 'boolean|required',
'is_container_label_readonly_enabled' => 'boolean|required',
'is_preserve_repository_enabled' => 'boolean|required',
'is_http_basic_auth_enabled' => 'boolean|required',
'http_basic_auth_username' => 'string|nullable',
'http_basic_auth_password' => 'string|nullable',
'watch_paths' => 'nullable',
'dockerRegistryImageName' => 'nullable',
'dockerRegistryImageTag' => 'nullable',
'dockerfileLocation' => 'nullable',
'dockerComposeLocation' => 'nullable',
'dockerCompose' => 'nullable',
'dockerComposeRaw' => 'nullable',
'dockerfileTargetBuild' => 'nullable',
'dockerComposeCustomStartCommand' => 'nullable',
'dockerComposeCustomBuildCommand' => 'nullable',
'customLabels' => 'nullable',
'customDockerRunOptions' => 'nullable',
'preDeploymentCommand' => 'nullable',
'preDeploymentCommandContainer' => 'nullable',
'postDeploymentCommand' => 'nullable',
'postDeploymentCommandContainer' => 'nullable',
'customNginxConfiguration' => 'nullable',
'isStatic' => 'boolean|required',
'isSpa' => 'boolean|required',
'isBuildServerEnabled' => 'boolean|required',
'isContainerLabelEscapeEnabled' => 'boolean|required',
'isContainerLabelReadonlyEnabled' => 'boolean|required',
'isPreserveRepositoryEnabled' => 'boolean|required',
'isHttpBasicAuthEnabled' => 'boolean|required',
'httpBasicAuthUsername' => 'string|nullable',
'httpBasicAuthPassword' => 'string|nullable',
'watchPaths' => 'nullable',
'redirect' => 'string|required',
];
}
@@ -193,26 +189,26 @@ class General extends Component
'name.required' => 'The Name field is required.',
'name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
'description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
'git_repository.required' => 'The Git Repository field is required.',
'git_branch.required' => 'The Git Branch field is required.',
'build_pack.required' => 'The Build Pack field is required.',
'static_image.required' => 'The Static Image field is required.',
'base_directory.required' => 'The Base Directory field is required.',
'ports_exposes.required' => 'The Exposed Ports field is required.',
'is_static.required' => 'The Static setting is required.',
'is_static.boolean' => 'The Static setting must be true or false.',
'is_spa.required' => 'The SPA setting is required.',
'is_spa.boolean' => 'The SPA setting must be true or false.',
'is_build_server_enabled.required' => 'The Build Server setting is required.',
'is_build_server_enabled.boolean' => 'The Build Server setting must be true or false.',
'is_container_label_escape_enabled.required' => 'The Container Label Escape setting is required.',
'is_container_label_escape_enabled.boolean' => 'The Container Label Escape setting must be true or false.',
'is_container_label_readonly_enabled.required' => 'The Container Label Readonly setting is required.',
'is_container_label_readonly_enabled.boolean' => 'The Container Label Readonly setting must be true or false.',
'is_preserve_repository_enabled.required' => 'The Preserve Repository setting is required.',
'is_preserve_repository_enabled.boolean' => 'The Preserve Repository setting must be true or false.',
'is_http_basic_auth_enabled.required' => 'The HTTP Basic Auth setting is required.',
'is_http_basic_auth_enabled.boolean' => 'The HTTP Basic Auth setting must be true or false.',
'gitRepository.required' => 'The Git Repository field is required.',
'gitBranch.required' => 'The Git Branch field is required.',
'buildPack.required' => 'The Build Pack field is required.',
'staticImage.required' => 'The Static Image field is required.',
'baseDirectory.required' => 'The Base Directory field is required.',
'portsExposes.required' => 'The Exposed Ports field is required.',
'isStatic.required' => 'The Static setting is required.',
'isStatic.boolean' => 'The Static setting must be true or false.',
'isSpa.required' => 'The SPA setting is required.',
'isSpa.boolean' => 'The SPA setting must be true or false.',
'isBuildServerEnabled.required' => 'The Build Server setting is required.',
'isBuildServerEnabled.boolean' => 'The Build Server setting must be true or false.',
'isContainerLabelEscapeEnabled.required' => 'The Container Label Escape setting is required.',
'isContainerLabelEscapeEnabled.boolean' => 'The Container Label Escape setting must be true or false.',
'isContainerLabelReadonlyEnabled.required' => 'The Container Label Readonly setting is required.',
'isContainerLabelReadonlyEnabled.boolean' => 'The Container Label Readonly setting must be true or false.',
'isPreserveRepositoryEnabled.required' => 'The Preserve Repository setting is required.',
'isPreserveRepositoryEnabled.boolean' => 'The Preserve Repository setting must be true or false.',
'isHttpBasicAuthEnabled.required' => 'The HTTP Basic Auth setting is required.',
'isHttpBasicAuthEnabled.boolean' => 'The HTTP Basic Auth setting must be true or false.',
'redirect.required' => 'The Redirect setting is required.',
'redirect.string' => 'The Redirect setting must be a string.',
]
@@ -220,43 +216,43 @@ class General extends Component
}
protected $validationAttributes = [
'application.name' => 'name',
'application.description' => 'description',
'application.fqdn' => 'FQDN',
'application.git_repository' => 'Git repository',
'application.git_branch' => 'Git branch',
'application.git_commit_sha' => 'Git commit SHA',
'application.install_command' => 'Install command',
'application.build_command' => 'Build command',
'application.start_command' => 'Start command',
'application.build_pack' => 'Build pack',
'application.static_image' => 'Static image',
'application.base_directory' => 'Base directory',
'application.publish_directory' => 'Publish directory',
'application.ports_exposes' => 'Ports exposes',
'application.ports_mappings' => 'Ports mappings',
'application.dockerfile' => 'Dockerfile',
'application.docker_registry_image_name' => 'Docker registry image name',
'application.docker_registry_image_tag' => 'Docker registry image tag',
'application.dockerfile_location' => 'Dockerfile location',
'application.docker_compose_location' => 'Docker compose location',
'application.docker_compose' => 'Docker compose',
'application.docker_compose_raw' => 'Docker compose raw',
'application.custom_labels' => 'Custom labels',
'application.dockerfile_target_build' => 'Dockerfile target build',
'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_build_command' => 'Docker compose custom build command',
'application.custom_nginx_configuration' => 'Custom Nginx configuration',
'application.settings.is_static' => 'Is static',
'application.settings.is_spa' => 'Is SPA',
'application.settings.is_build_server_enabled' => 'Is build server enabled',
'application.settings.is_container_label_escape_enabled' => 'Is container label escape enabled',
'application.settings.is_container_label_readonly_enabled' => 'Is container label readonly',
'application.settings.is_preserve_repository_enabled' => 'Is preserve repository enabled',
'application.watch_paths' => 'Watch paths',
'application.redirect' => 'Redirect',
'name' => 'name',
'description' => 'description',
'fqdn' => 'FQDN',
'gitRepository' => 'Git repository',
'gitBranch' => 'Git branch',
'gitCommitSha' => 'Git commit SHA',
'installCommand' => 'Install command',
'buildCommand' => 'Build command',
'startCommand' => 'Start command',
'buildPack' => 'Build pack',
'staticImage' => 'Static image',
'baseDirectory' => 'Base directory',
'publishDirectory' => 'Publish directory',
'portsExposes' => 'Ports exposes',
'portsMappings' => 'Ports mappings',
'dockerfile' => 'Dockerfile',
'dockerRegistryImageName' => 'Docker registry image name',
'dockerRegistryImageTag' => 'Docker registry image tag',
'dockerfileLocation' => 'Dockerfile location',
'dockerComposeLocation' => 'Docker compose location',
'dockerCompose' => 'Docker compose',
'dockerComposeRaw' => 'Docker compose raw',
'customLabels' => 'Custom labels',
'dockerfileTargetBuild' => 'Dockerfile target build',
'customDockerRunOptions' => 'Custom docker run commands',
'customNetworkAliases' => 'Custom docker network aliases',
'dockerComposeCustomStartCommand' => 'Docker compose custom start command',
'dockerComposeCustomBuildCommand' => 'Docker compose custom build command',
'customNginxConfiguration' => 'Custom Nginx configuration',
'isStatic' => 'Is static',
'isSpa' => 'Is SPA',
'isBuildServerEnabled' => 'Is build server enabled',
'isContainerLabelEscapeEnabled' => 'Is container label escape enabled',
'isContainerLabelReadonlyEnabled' => 'Is container label readonly',
'isPreserveRepositoryEnabled' => 'Is preserve repository enabled',
'watchPaths' => 'Watch paths',
'redirect' => 'Redirect',
];
public function mount()
@@ -266,14 +262,14 @@ class General extends Component
if (is_null($this->parsedServices) || empty($this->parsedServices)) {
$this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.');
// Still sync data even if parse fails, so form fields are populated
$this->syncFromModel();
$this->syncData();
return;
}
} catch (\Throwable $e) {
$this->dispatch('error', $e->getMessage());
// Still sync data even on error, so form fields are populated
$this->syncFromModel();
$this->syncData();
}
if ($this->application->build_pack === 'dockercompose') {
// Only update if user has permission
@@ -325,57 +321,112 @@ class General extends Component
// Sync data from model to properties at the END, after all business logic
// This ensures any modifications to $this->application during mount() are reflected in properties
$this->syncFromModel();
$this->syncData();
}
protected function getModelBindings(): array
public function syncData(bool $toModel = false): void
{
return [
'name' => 'application.name',
'description' => 'application.description',
'fqdn' => 'application.fqdn',
'git_repository' => 'application.git_repository',
'git_branch' => 'application.git_branch',
'git_commit_sha' => 'application.git_commit_sha',
'install_command' => 'application.install_command',
'build_command' => 'application.build_command',
'start_command' => 'application.start_command',
'build_pack' => 'application.build_pack',
'static_image' => 'application.static_image',
'base_directory' => 'application.base_directory',
'publish_directory' => 'application.publish_directory',
'ports_exposes' => 'application.ports_exposes',
'ports_mappings' => 'application.ports_mappings',
'custom_network_aliases' => 'application.custom_network_aliases',
'dockerfile' => 'application.dockerfile',
'dockerfile_location' => 'application.dockerfile_location',
'dockerfile_target_build' => 'application.dockerfile_target_build',
'docker_registry_image_name' => 'application.docker_registry_image_name',
'docker_registry_image_tag' => 'application.docker_registry_image_tag',
'docker_compose_location' => 'application.docker_compose_location',
'docker_compose' => 'application.docker_compose',
'docker_compose_raw' => 'application.docker_compose_raw',
'docker_compose_custom_start_command' => 'application.docker_compose_custom_start_command',
'docker_compose_custom_build_command' => 'application.docker_compose_custom_build_command',
'custom_labels' => 'application.custom_labels',
'custom_docker_run_options' => 'application.custom_docker_run_options',
'pre_deployment_command' => 'application.pre_deployment_command',
'pre_deployment_command_container' => 'application.pre_deployment_command_container',
'post_deployment_command' => 'application.post_deployment_command',
'post_deployment_command_container' => 'application.post_deployment_command_container',
'custom_nginx_configuration' => 'application.custom_nginx_configuration',
'is_static' => 'application.settings.is_static',
'is_spa' => 'application.settings.is_spa',
'is_build_server_enabled' => 'application.settings.is_build_server_enabled',
'is_preserve_repository_enabled' => 'application.settings.is_preserve_repository_enabled',
'is_container_label_escape_enabled' => 'application.settings.is_container_label_escape_enabled',
'is_container_label_readonly_enabled' => 'application.settings.is_container_label_readonly_enabled',
'is_http_basic_auth_enabled' => 'application.is_http_basic_auth_enabled',
'http_basic_auth_username' => 'application.http_basic_auth_username',
'http_basic_auth_password' => 'application.http_basic_auth_password',
'watch_paths' => 'application.watch_paths',
'redirect' => 'application.redirect',
];
if ($toModel) {
$this->validate();
// Application properties
$this->application->name = $this->name;
$this->application->description = $this->description;
$this->application->fqdn = $this->fqdn;
$this->application->git_repository = $this->gitRepository;
$this->application->git_branch = $this->gitBranch;
$this->application->git_commit_sha = $this->gitCommitSha;
$this->application->install_command = $this->installCommand;
$this->application->build_command = $this->buildCommand;
$this->application->start_command = $this->startCommand;
$this->application->build_pack = $this->buildPack;
$this->application->static_image = $this->staticImage;
$this->application->base_directory = $this->baseDirectory;
$this->application->publish_directory = $this->publishDirectory;
$this->application->ports_exposes = $this->portsExposes;
$this->application->ports_mappings = $this->portsMappings;
$this->application->custom_network_aliases = $this->customNetworkAliases;
$this->application->dockerfile = $this->dockerfile;
$this->application->dockerfile_location = $this->dockerfileLocation;
$this->application->dockerfile_target_build = $this->dockerfileTargetBuild;
$this->application->docker_registry_image_name = $this->dockerRegistryImageName;
$this->application->docker_registry_image_tag = $this->dockerRegistryImageTag;
$this->application->docker_compose_location = $this->dockerComposeLocation;
$this->application->docker_compose = $this->dockerCompose;
$this->application->docker_compose_raw = $this->dockerComposeRaw;
$this->application->docker_compose_custom_start_command = $this->dockerComposeCustomStartCommand;
$this->application->docker_compose_custom_build_command = $this->dockerComposeCustomBuildCommand;
$this->application->custom_labels = base64_encode($this->customLabels);
$this->application->custom_docker_run_options = $this->customDockerRunOptions;
$this->application->pre_deployment_command = $this->preDeploymentCommand;
$this->application->pre_deployment_command_container = $this->preDeploymentCommandContainer;
$this->application->post_deployment_command = $this->postDeploymentCommand;
$this->application->post_deployment_command_container = $this->postDeploymentCommandContainer;
$this->application->custom_nginx_configuration = $this->customNginxConfiguration;
$this->application->is_http_basic_auth_enabled = $this->isHttpBasicAuthEnabled;
$this->application->http_basic_auth_username = $this->httpBasicAuthUsername;
$this->application->http_basic_auth_password = $this->httpBasicAuthPassword;
$this->application->watch_paths = $this->watchPaths;
$this->application->redirect = $this->redirect;
// Application settings properties
$this->application->settings->is_static = $this->isStatic;
$this->application->settings->is_spa = $this->isSpa;
$this->application->settings->is_build_server_enabled = $this->isBuildServerEnabled;
$this->application->settings->is_preserve_repository_enabled = $this->isPreserveRepositoryEnabled;
$this->application->settings->is_container_label_escape_enabled = $this->isContainerLabelEscapeEnabled;
$this->application->settings->is_container_label_readonly_enabled = $this->isContainerLabelReadonlyEnabled;
$this->application->settings->save();
} else {
// From model to properties
$this->name = $this->application->name;
$this->description = $this->application->description;
$this->fqdn = $this->application->fqdn;
$this->gitRepository = $this->application->git_repository;
$this->gitBranch = $this->application->git_branch;
$this->gitCommitSha = $this->application->git_commit_sha;
$this->installCommand = $this->application->install_command;
$this->buildCommand = $this->application->build_command;
$this->startCommand = $this->application->start_command;
$this->buildPack = $this->application->build_pack;
$this->staticImage = $this->application->static_image;
$this->baseDirectory = $this->application->base_directory;
$this->publishDirectory = $this->application->publish_directory;
$this->portsExposes = $this->application->ports_exposes;
$this->portsMappings = $this->application->ports_mappings;
$this->customNetworkAliases = $this->application->custom_network_aliases;
$this->dockerfile = $this->application->dockerfile;
$this->dockerfileLocation = $this->application->dockerfile_location;
$this->dockerfileTargetBuild = $this->application->dockerfile_target_build;
$this->dockerRegistryImageName = $this->application->docker_registry_image_name;
$this->dockerRegistryImageTag = $this->application->docker_registry_image_tag;
$this->dockerComposeLocation = $this->application->docker_compose_location;
$this->dockerCompose = $this->application->docker_compose;
$this->dockerComposeRaw = $this->application->docker_compose_raw;
$this->dockerComposeCustomStartCommand = $this->application->docker_compose_custom_start_command;
$this->dockerComposeCustomBuildCommand = $this->application->docker_compose_custom_build_command;
$this->customLabels = $this->application->parseContainerLabels();
$this->customDockerRunOptions = $this->application->custom_docker_run_options;
$this->preDeploymentCommand = $this->application->pre_deployment_command;
$this->preDeploymentCommandContainer = $this->application->pre_deployment_command_container;
$this->postDeploymentCommand = $this->application->post_deployment_command;
$this->postDeploymentCommandContainer = $this->application->post_deployment_command_container;
$this->customNginxConfiguration = $this->application->custom_nginx_configuration;
$this->isHttpBasicAuthEnabled = $this->application->is_http_basic_auth_enabled;
$this->httpBasicAuthUsername = $this->application->http_basic_auth_username;
$this->httpBasicAuthPassword = $this->application->http_basic_auth_password;
$this->watchPaths = $this->application->watch_paths;
$this->redirect = $this->application->redirect;
// Application settings properties
$this->isStatic = $this->application->settings->is_static;
$this->isSpa = $this->application->settings->is_spa;
$this->isBuildServerEnabled = $this->application->settings->is_build_server_enabled;
$this->isPreserveRepositoryEnabled = $this->application->settings->is_preserve_repository_enabled;
$this->isContainerLabelEscapeEnabled = $this->application->settings->is_container_label_escape_enabled;
$this->isContainerLabelReadonlyEnabled = $this->application->settings->is_container_label_readonly_enabled;
}
}
public function instantSave()
@@ -387,7 +438,7 @@ class General extends Component
$oldIsContainerLabelEscapeEnabled = $this->application->settings->is_container_label_escape_enabled;
$oldIsPreserveRepositoryEnabled = $this->application->settings->is_preserve_repository_enabled;
$this->syncToModel();
$this->syncData(toModel: true);
if ($this->application->settings->isDirty('is_spa')) {
$this->generateNginxConfiguration($this->application->settings->is_spa ? 'spa' : 'static');
@@ -395,24 +446,27 @@ class General extends Component
if ($this->application->isDirty('is_http_basic_auth_enabled')) {
$this->application->save();
}
$this->application->settings->save();
$this->dispatch('success', 'Settings saved.');
$this->application->refresh();
$this->syncFromModel();
$this->syncData();
// If port_exposes changed, reset default labels
if ($oldPortsExposes !== $this->ports_exposes || $oldIsContainerLabelEscapeEnabled !== $this->is_container_label_escape_enabled) {
if ($oldPortsExposes !== $this->portsExposes || $oldIsContainerLabelEscapeEnabled !== $this->isContainerLabelEscapeEnabled) {
$this->resetDefaultLabels(false);
}
if ($oldIsPreserveRepositoryEnabled !== $this->is_preserve_repository_enabled) {
if ($this->is_preserve_repository_enabled === false) {
if ($oldIsPreserveRepositoryEnabled !== $this->isPreserveRepositoryEnabled) {
if ($this->isPreserveRepositoryEnabled === false) {
$this->application->fileStorages->each(function ($storage) {
$storage->is_based_on_git = $this->is_preserve_repository_enabled;
$storage->is_based_on_git = $this->isPreserveRepositoryEnabled;
$storage->save();
});
}
}
if ($this->is_container_label_readonly_enabled) {
if ($this->isContainerLabelReadonlyEnabled) {
$this->resetDefaultLabels(false);
}
} catch (\Throwable $e) {
@@ -441,7 +495,7 @@ class General extends Component
// Sync the docker_compose_raw from the model to the component property
// This ensures the Monaco editor displays the loaded compose file
$this->syncFromModel();
$this->syncData();
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
// Convert service names with dots and dashes to use underscores for HTML form binding
@@ -507,7 +561,7 @@ class General extends Component
public function updatedBaseDirectory()
{
if ($this->build_pack === 'dockercompose') {
if ($this->buildPack === 'dockercompose') {
$this->loadComposeFile();
}
}
@@ -527,24 +581,24 @@ class General extends Component
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
// User doesn't have permission, revert the change and return
$this->application->refresh();
$this->syncFromModel();
$this->syncData();
return;
}
// Sync property to model before checking/modifying
$this->syncToModel();
$this->syncData(toModel: true);
if ($this->build_pack !== 'nixpacks') {
$this->is_static = false;
if ($this->buildPack !== 'nixpacks') {
$this->isStatic = false;
$this->application->settings->is_static = false;
$this->application->settings->save();
} else {
$this->ports_exposes = 3000;
$this->application->ports_exposes = 3000;
$this->portsExposes = '3000';
$this->application->ports_exposes = '3000';
$this->resetDefaultLabels(false);
}
if ($this->build_pack === 'dockercompose') {
if ($this->buildPack === 'dockercompose') {
// Only update if user has permission
try {
$this->authorize('update', $this->application);
@@ -567,9 +621,9 @@ class General extends Component
$this->application->environment_variables_preview()->where('key', 'LIKE', 'SERVICE_URL_%')->delete();
}
}
if ($this->build_pack === 'static') {
$this->ports_exposes = 80;
$this->application->ports_exposes = 80;
if ($this->buildPack === 'static') {
$this->portsExposes = '80';
$this->application->ports_exposes = '80';
$this->resetDefaultLabels(false);
$this->generateNginxConfiguration();
}
@@ -586,10 +640,10 @@ class General extends Component
if ($server) {
$fqdn = generateUrl(server: $server, random: $this->application->uuid);
$this->fqdn = $fqdn;
$this->syncToModel();
$this->syncData(toModel: true);
$this->application->save();
$this->application->refresh();
$this->syncFromModel();
$this->syncData();
$this->resetDefaultLabels();
$this->dispatch('success', 'Wildcard domain generated.');
}
@@ -603,11 +657,11 @@ class General extends Component
try {
$this->authorize('update', $this->application);
$this->custom_nginx_configuration = defaultNginxConfiguration($type);
$this->syncToModel();
$this->customNginxConfiguration = defaultNginxConfiguration($type);
$this->syncData(toModel: true);
$this->application->save();
$this->application->refresh();
$this->syncFromModel();
$this->syncData();
$this->dispatch('success', 'Nginx configuration generated.');
} catch (\Throwable $e) {
return handleError($e, $this);
@@ -617,16 +671,15 @@ class General extends Component
public function resetDefaultLabels($manualReset = false)
{
try {
if (! $this->is_container_label_readonly_enabled && ! $manualReset) {
if (! $this->isContainerLabelReadonlyEnabled && ! $manualReset) {
return;
}
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
$this->custom_labels = base64_encode($this->customLabels);
$this->syncToModel();
$this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save();
$this->application->refresh();
$this->syncFromModel();
if ($this->build_pack === 'dockercompose') {
$this->syncData();
if ($this->buildPack === 'dockercompose') {
$this->loadComposeFile(showToast: false);
}
$this->dispatch('configurationChanged');
@@ -722,7 +775,7 @@ class General extends Component
$this->dispatch('warning', __('warning.sslipdomain'));
}
$this->syncToModel();
$this->syncData(toModel: true);
if ($this->application->isDirty('redirect')) {
$this->setRedirect();
@@ -742,42 +795,42 @@ class General extends Component
$this->application->save();
}
if ($this->build_pack === 'dockercompose' && $oldDockerComposeLocation !== $this->docker_compose_location) {
if ($this->buildPack === 'dockercompose' && $oldDockerComposeLocation !== $this->dockerComposeLocation) {
$compose_return = $this->loadComposeFile(showToast: false);
if ($compose_return instanceof \Livewire\Features\SupportEvents\Event) {
return;
}
}
if ($oldPortsExposes !== $this->ports_exposes || $oldIsContainerLabelEscapeEnabled !== $this->is_container_label_escape_enabled) {
if ($oldPortsExposes !== $this->portsExposes || $oldIsContainerLabelEscapeEnabled !== $this->isContainerLabelEscapeEnabled) {
$this->resetDefaultLabels();
}
if ($this->build_pack === 'dockerimage') {
if ($this->buildPack === 'dockerimage') {
$this->validate([
'docker_registry_image_name' => 'required',
'dockerRegistryImageName' => 'required',
]);
}
if ($this->custom_docker_run_options) {
$this->custom_docker_run_options = str($this->custom_docker_run_options)->trim()->toString();
$this->application->custom_docker_run_options = $this->custom_docker_run_options;
if ($this->customDockerRunOptions) {
$this->customDockerRunOptions = str($this->customDockerRunOptions)->trim()->toString();
$this->application->custom_docker_run_options = $this->customDockerRunOptions;
}
if ($this->dockerfile) {
$port = get_port_from_dockerfile($this->dockerfile);
if ($port && ! $this->ports_exposes) {
$this->ports_exposes = $port;
if ($port && ! $this->portsExposes) {
$this->portsExposes = $port;
$this->application->ports_exposes = $port;
}
}
if ($this->base_directory && $this->base_directory !== '/') {
$this->base_directory = rtrim($this->base_directory, '/');
$this->application->base_directory = $this->base_directory;
if ($this->baseDirectory && $this->baseDirectory !== '/') {
$this->baseDirectory = rtrim($this->baseDirectory, '/');
$this->application->base_directory = $this->baseDirectory;
}
if ($this->publish_directory && $this->publish_directory !== '/') {
$this->publish_directory = rtrim($this->publish_directory, '/');
$this->application->publish_directory = $this->publish_directory;
if ($this->publishDirectory && $this->publishDirectory !== '/') {
$this->publishDirectory = rtrim($this->publishDirectory, '/');
$this->application->publish_directory = $this->publishDirectory;
}
if ($this->build_pack === 'dockercompose') {
if ($this->buildPack === 'dockercompose') {
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
if ($this->application->isDirty('docker_compose_domains')) {
foreach ($this->parsedServiceDomains as $service) {
@@ -809,11 +862,11 @@ class General extends Component
$this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save();
$this->application->refresh();
$this->syncFromModel();
$this->syncData();
$showToaster && ! $warning && $this->dispatch('success', 'Application settings updated!');
} catch (\Throwable $e) {
$this->application->refresh();
$this->syncFromModel();
$this->syncData();
return handleError($e, $this);
} finally {