Complete Livewire legacy model binding migration (25+ components)

This completes the migration from Livewire's legacy `id="model.property"`
pattern to explicit properties with manual synchronization. This allows
disabling the `legacy_model_binding` feature flag.

**Components Migrated (Final Session - 9 components):**
- Server/Proxy.php (1 field)
- Service/EditDomain.php (1 field) - Fixed Collection/string bug & parent sync
- Application/Previews.php (2 fields - array handling)
- Service/EditCompose.php (4 fields)
- Service/FileStorage.php (6 fields)
- Service/Database.php (7 fields)
- Service/ServiceApplicationView.php (10 fields)
- Application/General.php (53 fields) - LARGEST migration
- Application/PreviewsCompose.php (1 field)

**Total Migration Summary:**
- 25+ components migrated across all phases
- 150+ explicit properties added
- 0 legacy bindings remaining (verified via grep)
- All wire:model, id, @entangle bindings updated
- All updater hooks renamed (updatedApplicationX → updatedX)

**Technical Changes:**
- Added explicit public properties (camelCase)
- Implemented syncData(bool $toModel) bidirectional sync
- Updated validation rules (removed model. prefix)
- Updated all action methods (mount, submit, instantSave)
- Fixed updater hooks: updatedBuildPack, updatedBaseDirectory, updatedIsStatic
- Updated Blade views (id & wire:model bindings)
- Applied Collection/string confusion fixes
- Added model refresh + re-sync pattern

**Critical Fixes:**
- EditDomain.php Collection/string confusion (use intermediate variables)
- EditDomain.php parent component sync (refresh + re-sync after save)
- General.php domain field empty (syncData at end of mount)
- General.php wire:model bindings (application.* → property)
- General.php updater hooks (wrong naming convention)

**Files Modified:** 34 files
- 17 PHP Livewire components
- 17 Blade view templates
- 1 MIGRATION_REPORT.md (documentation)

**Ready to disable legacy_model_binding flag in config/livewire.php**

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Andras Bacsai
2025-10-13 15:38:59 +02:00
parent bb9ddd089a
commit f77ad4cbd9
35 changed files with 1597 additions and 501 deletions

View File

@@ -23,6 +23,8 @@ class General extends Component
public string $name;
public ?string $description = null;
public ?string $fqdn = null;
public string $git_repository;
@@ -31,14 +33,82 @@ class General extends Component
public ?string $git_commit_sha = null;
public ?string $install_command = null;
public ?string $build_command = null;
public ?string $start_command = null;
public string $build_pack;
public string $static_image;
public string $base_directory;
public ?string $publish_directory = null;
public ?string $ports_exposes = null;
public ?string $ports_mappings = null;
public ?string $custom_network_aliases = null;
public ?string $dockerfile = null;
public ?string $dockerfile_location = null;
public ?string $dockerfile_target_build = null;
public ?string $docker_registry_image_name = null;
public ?string $docker_registry_image_tag = null;
public ?string $docker_compose_location = null;
public ?string $docker_compose = null;
public ?string $docker_compose_raw = null;
public ?string $docker_compose_custom_start_command = null;
public ?string $docker_compose_custom_build_command = null;
public ?string $custom_labels = null;
public ?string $custom_docker_run_options = null;
public ?string $pre_deployment_command = null;
public ?string $pre_deployment_command_container = null;
public ?string $post_deployment_command = null;
public ?string $post_deployment_command_container = null;
public ?string $custom_nginx_configuration = null;
public bool $is_static = false;
public bool $is_spa = false;
public bool $is_build_server_enabled = false;
public bool $is_preserve_repository_enabled = false;
public bool $is_container_label_escape_enabled = true;
public bool $is_container_label_readonly_enabled = false;
public bool $is_http_basic_auth_enabled = false;
public ?string $http_basic_auth_username = null;
public ?string $http_basic_auth_password = null;
public ?string $watch_paths = null;
public string $redirect;
public $customLabels;
public bool $labelsChanged = false;
@@ -66,50 +136,50 @@ class General extends Component
protected function rules(): array
{
return [
'application.name' => ValidationPatterns::nameRules(),
'application.description' => ValidationPatterns::descriptionRules(),
'application.fqdn' => 'nullable',
'application.git_repository' => 'required',
'application.git_branch' => 'required',
'application.git_commit_sha' => 'nullable',
'application.install_command' => 'nullable',
'application.build_command' => 'nullable',
'application.start_command' => 'nullable',
'application.build_pack' => 'required',
'application.static_image' => 'required',
'application.base_directory' => 'required',
'application.publish_directory' => 'nullable',
'application.ports_exposes' => 'required',
'application.ports_mappings' => 'nullable',
'application.custom_network_aliases' => 'nullable',
'application.dockerfile' => 'nullable',
'application.docker_registry_image_name' => 'nullable',
'application.docker_registry_image_tag' => 'nullable',
'application.dockerfile_location' => 'nullable',
'application.docker_compose_location' => 'nullable',
'application.docker_compose' => 'nullable',
'application.docker_compose_raw' => 'nullable',
'application.dockerfile_target_build' => 'nullable',
'application.docker_compose_custom_start_command' => 'nullable',
'application.docker_compose_custom_build_command' => 'nullable',
'application.custom_labels' => 'nullable',
'application.custom_docker_run_options' => 'nullable',
'application.pre_deployment_command' => 'nullable',
'application.pre_deployment_command_container' => 'nullable',
'application.post_deployment_command' => 'nullable',
'application.post_deployment_command_container' => 'nullable',
'application.custom_nginx_configuration' => 'nullable',
'application.settings.is_static' => 'boolean|required',
'application.settings.is_spa' => 'boolean|required',
'application.settings.is_build_server_enabled' => 'boolean|required',
'application.settings.is_container_label_escape_enabled' => 'boolean|required',
'application.settings.is_container_label_readonly_enabled' => 'boolean|required',
'application.settings.is_preserve_repository_enabled' => 'boolean|required',
'application.is_http_basic_auth_enabled' => 'boolean|required',
'application.http_basic_auth_username' => 'string|nullable',
'application.http_basic_auth_password' => 'string|nullable',
'application.watch_paths' => 'nullable',
'application.redirect' => 'string|required',
'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',
'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',
'redirect' => 'string|required',
];
}
@@ -118,31 +188,31 @@ class General extends Component
return array_merge(
ValidationPatterns::combinedMessages(),
[
'application.name.required' => 'The Name field is required.',
'application.name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
'application.description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
'application.git_repository.required' => 'The Git Repository field is required.',
'application.git_branch.required' => 'The Git Branch field is required.',
'application.build_pack.required' => 'The Build Pack field is required.',
'application.static_image.required' => 'The Static Image field is required.',
'application.base_directory.required' => 'The Base Directory field is required.',
'application.ports_exposes.required' => 'The Exposed Ports field is required.',
'application.settings.is_static.required' => 'The Static setting is required.',
'application.settings.is_static.boolean' => 'The Static setting must be true or false.',
'application.settings.is_spa.required' => 'The SPA setting is required.',
'application.settings.is_spa.boolean' => 'The SPA setting must be true or false.',
'application.settings.is_build_server_enabled.required' => 'The Build Server setting is required.',
'application.settings.is_build_server_enabled.boolean' => 'The Build Server setting must be true or false.',
'application.settings.is_container_label_escape_enabled.required' => 'The Container Label Escape setting is required.',
'application.settings.is_container_label_escape_enabled.boolean' => 'The Container Label Escape setting must be true or false.',
'application.settings.is_container_label_readonly_enabled.required' => 'The Container Label Readonly setting is required.',
'application.settings.is_container_label_readonly_enabled.boolean' => 'The Container Label Readonly setting must be true or false.',
'application.settings.is_preserve_repository_enabled.required' => 'The Preserve Repository setting is required.',
'application.settings.is_preserve_repository_enabled.boolean' => 'The Preserve Repository setting must be true or false.',
'application.is_http_basic_auth_enabled.required' => 'The HTTP Basic Auth setting is required.',
'application.is_http_basic_auth_enabled.boolean' => 'The HTTP Basic Auth setting must be true or false.',
'application.redirect.required' => 'The Redirect setting is required.',
'application.redirect.string' => 'The Redirect setting must be a string.',
'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.',
'redirect.required' => 'The Redirect setting is required.',
'redirect.string' => 'The Redirect setting must be a string.',
]
);
}
@@ -193,11 +263,15 @@ class General extends Component
$this->parsedServices = $this->application->parse();
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->syncData(false);
return;
}
} catch (\Throwable $e) {
$this->dispatch('error', $e->getMessage());
// Still sync data even on error, so form fields are populated
$this->syncData(false);
}
if ($this->application->build_pack === 'dockercompose') {
// Only update if user has permission
@@ -218,9 +292,6 @@ class General extends Component
}
$this->parsedServiceDomains = $sanitizedDomains;
$this->ports_exposes = $this->application->ports_exposes;
$this->is_preserve_repository_enabled = $this->application->settings->is_preserve_repository_enabled;
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
$this->customLabels = $this->application->parseContainerLabels();
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && $this->application->settings->is_container_label_readonly_enabled === true) {
// Only update custom labels if user has permission
@@ -249,6 +320,105 @@ class General extends Component
if (str($this->application->status)->startsWith('running') && is_null($this->application->config_hash)) {
$this->dispatch('configurationChanged');
}
// 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->syncData(false);
}
private function syncData(bool $toModel = false): void
{
if ($toModel) {
$this->application->name = $this->name;
$this->application->description = $this->description;
$this->application->fqdn = $this->fqdn;
$this->application->git_repository = $this->git_repository;
$this->application->git_branch = $this->git_branch;
$this->application->git_commit_sha = $this->git_commit_sha;
$this->application->install_command = $this->install_command;
$this->application->build_command = $this->build_command;
$this->application->start_command = $this->start_command;
$this->application->build_pack = $this->build_pack;
$this->application->static_image = $this->static_image;
$this->application->base_directory = $this->base_directory;
$this->application->publish_directory = $this->publish_directory;
$this->application->ports_exposes = $this->ports_exposes;
$this->application->ports_mappings = $this->ports_mappings;
$this->application->custom_network_aliases = $this->custom_network_aliases;
$this->application->dockerfile = $this->dockerfile;
$this->application->dockerfile_location = $this->dockerfile_location;
$this->application->dockerfile_target_build = $this->dockerfile_target_build;
$this->application->docker_registry_image_name = $this->docker_registry_image_name;
$this->application->docker_registry_image_tag = $this->docker_registry_image_tag;
$this->application->docker_compose_location = $this->docker_compose_location;
$this->application->docker_compose = $this->docker_compose;
$this->application->docker_compose_raw = $this->docker_compose_raw;
$this->application->docker_compose_custom_start_command = $this->docker_compose_custom_start_command;
$this->application->docker_compose_custom_build_command = $this->docker_compose_custom_build_command;
$this->application->custom_labels = $this->custom_labels;
$this->application->custom_docker_run_options = $this->custom_docker_run_options;
$this->application->pre_deployment_command = $this->pre_deployment_command;
$this->application->pre_deployment_command_container = $this->pre_deployment_command_container;
$this->application->post_deployment_command = $this->post_deployment_command;
$this->application->post_deployment_command_container = $this->post_deployment_command_container;
$this->application->custom_nginx_configuration = $this->custom_nginx_configuration;
$this->application->settings->is_static = $this->is_static;
$this->application->settings->is_spa = $this->is_spa;
$this->application->settings->is_build_server_enabled = $this->is_build_server_enabled;
$this->application->settings->is_preserve_repository_enabled = $this->is_preserve_repository_enabled;
$this->application->settings->is_container_label_escape_enabled = $this->is_container_label_escape_enabled;
$this->application->settings->is_container_label_readonly_enabled = $this->is_container_label_readonly_enabled;
$this->application->is_http_basic_auth_enabled = $this->is_http_basic_auth_enabled;
$this->application->http_basic_auth_username = $this->http_basic_auth_username;
$this->application->http_basic_auth_password = $this->http_basic_auth_password;
$this->application->watch_paths = $this->watch_paths;
$this->application->redirect = $this->redirect;
} else {
$this->name = $this->application->name;
$this->description = $this->application->description;
$this->fqdn = $this->application->fqdn;
$this->git_repository = $this->application->git_repository;
$this->git_branch = $this->application->git_branch;
$this->git_commit_sha = $this->application->git_commit_sha;
$this->install_command = $this->application->install_command;
$this->build_command = $this->application->build_command;
$this->start_command = $this->application->start_command;
$this->build_pack = $this->application->build_pack;
$this->static_image = $this->application->static_image;
$this->base_directory = $this->application->base_directory;
$this->publish_directory = $this->application->publish_directory;
$this->ports_exposes = $this->application->ports_exposes;
$this->ports_mappings = $this->application->ports_mappings;
$this->custom_network_aliases = $this->application->custom_network_aliases;
$this->dockerfile = $this->application->dockerfile;
$this->dockerfile_location = $this->application->dockerfile_location;
$this->dockerfile_target_build = $this->application->dockerfile_target_build;
$this->docker_registry_image_name = $this->application->docker_registry_image_name;
$this->docker_registry_image_tag = $this->application->docker_registry_image_tag;
$this->docker_compose_location = $this->application->docker_compose_location;
$this->docker_compose = $this->application->docker_compose;
$this->docker_compose_raw = $this->application->docker_compose_raw;
$this->docker_compose_custom_start_command = $this->application->docker_compose_custom_start_command;
$this->docker_compose_custom_build_command = $this->application->docker_compose_custom_build_command;
$this->custom_labels = $this->application->custom_labels;
$this->custom_docker_run_options = $this->application->custom_docker_run_options;
$this->pre_deployment_command = $this->application->pre_deployment_command;
$this->pre_deployment_command_container = $this->application->pre_deployment_command_container;
$this->post_deployment_command = $this->application->post_deployment_command;
$this->post_deployment_command_container = $this->application->post_deployment_command_container;
$this->custom_nginx_configuration = $this->application->custom_nginx_configuration;
$this->is_static = $this->application->settings->is_static ?? false;
$this->is_spa = $this->application->settings->is_spa ?? false;
$this->is_build_server_enabled = $this->application->settings->is_build_server_enabled ?? false;
$this->is_preserve_repository_enabled = $this->application->settings->is_preserve_repository_enabled ?? false;
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled ?? true;
$this->is_container_label_readonly_enabled = $this->application->settings->is_container_label_readonly_enabled ?? false;
$this->is_http_basic_auth_enabled = $this->application->is_http_basic_auth_enabled ?? false;
$this->http_basic_auth_username = $this->application->http_basic_auth_username;
$this->http_basic_auth_password = $this->application->http_basic_auth_password;
$this->watch_paths = $this->application->watch_paths;
$this->redirect = $this->application->redirect;
}
}
public function instantSave()
@@ -256,6 +426,12 @@ class General extends Component
try {
$this->authorize('update', $this->application);
$oldPortsExposes = $this->application->ports_exposes;
$oldIsContainerLabelEscapeEnabled = $this->application->settings->is_container_label_escape_enabled;
$oldIsPreserveRepositoryEnabled = $this->application->settings->is_preserve_repository_enabled;
$this->syncData(true);
if ($this->application->settings->isDirty('is_spa')) {
$this->generateNginxConfiguration($this->application->settings->is_spa ? 'spa' : 'static');
}
@@ -265,20 +441,21 @@ class General extends Component
$this->application->settings->save();
$this->dispatch('success', 'Settings saved.');
$this->application->refresh();
$this->syncData(false);
// If port_exposes changed, reset default labels
if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) {
if ($oldPortsExposes !== $this->ports_exposes || $oldIsContainerLabelEscapeEnabled !== $this->is_container_label_escape_enabled) {
$this->resetDefaultLabels(false);
}
if ($this->is_preserve_repository_enabled !== $this->application->settings->is_preserve_repository_enabled) {
if ($this->application->settings->is_preserve_repository_enabled === false) {
if ($oldIsPreserveRepositoryEnabled !== $this->is_preserve_repository_enabled) {
if ($this->is_preserve_repository_enabled === false) {
$this->application->fileStorages->each(function ($storage) {
$storage->is_based_on_git = $this->application->settings->is_preserve_repository_enabled;
$storage->is_based_on_git = $this->is_preserve_repository_enabled;
$storage->save();
});
}
}
if ($this->application->settings->is_container_label_readonly_enabled) {
if ($this->is_container_label_readonly_enabled) {
$this->resetDefaultLabels(false);
}
} catch (\Throwable $e) {
@@ -366,21 +543,21 @@ class General extends Component
}
}
public function updatedApplicationBaseDirectory()
public function updatedBaseDirectory()
{
if ($this->application->build_pack === 'dockercompose') {
if ($this->build_pack === 'dockercompose') {
$this->loadComposeFile();
}
}
public function updatedApplicationSettingsIsStatic($value)
public function updatedIsStatic($value)
{
if ($value) {
$this->generateNginxConfiguration();
}
}
public function updatedApplicationBuildPack()
public function updatedBuildPack()
{
// Check if user has permission to update
try {
@@ -388,21 +565,28 @@ class General extends Component
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
// User doesn't have permission, revert the change and return
$this->application->refresh();
$this->syncData(false);
return;
}
if ($this->application->build_pack !== 'nixpacks') {
// Sync property to model before checking/modifying
$this->syncData(true);
if ($this->build_pack !== 'nixpacks') {
$this->is_static = false;
$this->application->settings->is_static = false;
$this->application->settings->save();
} else {
$this->application->ports_exposes = $this->ports_exposes = 3000;
$this->ports_exposes = 3000;
$this->application->ports_exposes = 3000;
$this->resetDefaultLabels(false);
}
if ($this->application->build_pack === 'dockercompose') {
if ($this->build_pack === 'dockercompose') {
// Only update if user has permission
try {
$this->authorize('update', $this->application);
$this->fqdn = null;
$this->application->fqdn = null;
$this->application->settings->save();
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
@@ -421,8 +605,9 @@ class General extends Component
$this->application->environment_variables_preview()->where('key', 'LIKE', 'SERVICE_URL_%')->delete();
}
}
if ($this->application->build_pack === 'static') {
$this->application->ports_exposes = $this->ports_exposes = 80;
if ($this->build_pack === 'static') {
$this->ports_exposes = 80;
$this->application->ports_exposes = 80;
$this->resetDefaultLabels(false);
$this->generateNginxConfiguration();
}
@@ -438,8 +623,11 @@ class General extends Component
$server = data_get($this->application, 'destination.server');
if ($server) {
$fqdn = generateUrl(server: $server, random: $this->application->uuid);
$this->application->fqdn = $fqdn;
$this->fqdn = $fqdn;
$this->syncData(true);
$this->application->save();
$this->application->refresh();
$this->syncData(false);
$this->resetDefaultLabels();
$this->dispatch('success', 'Wildcard domain generated.');
}
@@ -453,8 +641,11 @@ class General extends Component
try {
$this->authorize('update', $this->application);
$this->application->custom_nginx_configuration = defaultNginxConfiguration($type);
$this->custom_nginx_configuration = defaultNginxConfiguration($type);
$this->syncData(true);
$this->application->save();
$this->application->refresh();
$this->syncData(false);
$this->dispatch('success', 'Nginx configuration generated.');
} catch (\Throwable $e) {
return handleError($e, $this);
@@ -464,15 +655,16 @@ class General extends Component
public function resetDefaultLabels($manualReset = false)
{
try {
if (! $this->application->settings->is_container_label_readonly_enabled && ! $manualReset) {
if (! $this->is_container_label_readonly_enabled && ! $manualReset) {
return;
}
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
$this->ports_exposes = $this->application->ports_exposes;
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
$this->application->custom_labels = base64_encode($this->customLabels);
$this->custom_labels = base64_encode($this->customLabels);
$this->syncData(true);
$this->application->save();
if ($this->application->build_pack === 'dockercompose') {
$this->application->refresh();
$this->syncData(false);
if ($this->build_pack === 'dockercompose') {
$this->loadComposeFile(showToast: false);
}
$this->dispatch('configurationChanged');
@@ -483,8 +675,8 @@ class General extends Component
public function checkFqdns($showToaster = true)
{
if (data_get($this->application, 'fqdn')) {
$domains = str($this->application->fqdn)->trim()->explode(',');
if ($this->fqdn) {
$domains = str($this->fqdn)->trim()->explode(',');
if ($this->application->additional_servers->count() === 0) {
foreach ($domains as $domain) {
if (! validateDNSEntry($domain, $this->application->destination->server)) {
@@ -507,7 +699,8 @@ class General extends Component
$this->forceSaveDomains = false;
}
$this->application->fqdn = $domains->implode(',');
$this->fqdn = $domains->implode(',');
$this->application->fqdn = $this->fqdn;
$this->resetDefaultLabels(false);
}
@@ -547,21 +740,27 @@ class General extends Component
$this->validate();
$this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
$this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
$this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
$oldPortsExposes = $this->application->ports_exposes;
$oldIsContainerLabelEscapeEnabled = $this->application->settings->is_container_label_escape_enabled;
$oldDockerComposeLocation = $this->initialDockerComposeLocation;
// Process FQDN with intermediate variable to avoid Collection/string confusion
$this->fqdn = str($this->fqdn)->replaceEnd(',', '')->trim()->toString();
$this->fqdn = str($this->fqdn)->replaceStart(',', '')->trim()->toString();
$domains = str($this->fqdn)->trim()->explode(',')->map(function ($domain) {
$domain = trim($domain);
Url::fromString($domain, ['http', 'https']);
return str($domain)->lower();
});
$this->application->fqdn = $this->application->fqdn->unique()->implode(',');
$warning = sslipDomainWarning($this->application->fqdn);
$this->fqdn = $domains->unique()->implode(',');
$warning = sslipDomainWarning($this->fqdn);
if ($warning) {
$this->dispatch('warning', __('warning.sslipdomain'));
}
// $this->resetDefaultLabels();
$this->syncData(true);
if ($this->application->isDirty('redirect')) {
$this->setRedirect();
@@ -581,38 +780,42 @@ class General extends Component
$this->application->save();
}
if ($this->application->build_pack === 'dockercompose' && $this->initialDockerComposeLocation !== $this->application->docker_compose_location) {
if ($this->build_pack === 'dockercompose' && $oldDockerComposeLocation !== $this->docker_compose_location) {
$compose_return = $this->loadComposeFile(showToast: false);
if ($compose_return instanceof \Livewire\Features\SupportEvents\Event) {
return;
}
}
if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) {
if ($oldPortsExposes !== $this->ports_exposes || $oldIsContainerLabelEscapeEnabled !== $this->is_container_label_escape_enabled) {
$this->resetDefaultLabels();
}
if (data_get($this->application, 'build_pack') === 'dockerimage') {
if ($this->build_pack === 'dockerimage') {
$this->validate([
'application.docker_registry_image_name' => 'required',
'docker_registry_image_name' => 'required',
]);
}
if (data_get($this->application, 'custom_docker_run_options')) {
$this->application->custom_docker_run_options = str($this->application->custom_docker_run_options)->trim();
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 (data_get($this->application, 'dockerfile')) {
$port = get_port_from_dockerfile($this->application->dockerfile);
if ($port && ! $this->application->ports_exposes) {
if ($this->dockerfile) {
$port = get_port_from_dockerfile($this->dockerfile);
if ($port && ! $this->ports_exposes) {
$this->ports_exposes = $port;
$this->application->ports_exposes = $port;
}
}
if ($this->application->base_directory && $this->application->base_directory !== '/') {
$this->application->base_directory = rtrim($this->application->base_directory, '/');
if ($this->base_directory && $this->base_directory !== '/') {
$this->base_directory = rtrim($this->base_directory, '/');
$this->application->base_directory = $this->base_directory;
}
if ($this->application->publish_directory && $this->application->publish_directory !== '/') {
$this->application->publish_directory = rtrim($this->application->publish_directory, '/');
if ($this->publish_directory && $this->publish_directory !== '/') {
$this->publish_directory = rtrim($this->publish_directory, '/');
$this->application->publish_directory = $this->publish_directory;
}
if ($this->application->build_pack === 'dockercompose') {
if ($this->build_pack === 'dockercompose') {
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
if ($this->application->isDirty('docker_compose_domains')) {
foreach ($this->parsedServiceDomains as $service) {
@@ -643,12 +846,12 @@ class General extends Component
}
$this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save();
$this->application->refresh();
$this->syncData(false);
$showToaster && ! $warning && $this->dispatch('success', 'Application settings updated!');
} catch (\Throwable $e) {
$originalFqdn = $this->application->getOriginal('fqdn');
if ($originalFqdn !== $this->application->fqdn) {
$this->application->fqdn = $originalFqdn;
}
$this->application->refresh();
$this->syncData(false);
return handleError($e, $this);
} finally {