From 78ef80f80052af5d44ad58ece30f7255ecba1a4f Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 23 Apr 2025 13:22:01 +0200 Subject: [PATCH] refactor --- .../Api/ApplicationsController.php | 58 ++++++++++++--- app/Livewire/Project/Application/General.php | 9 ++- app/Models/Application.php | 7 +- bootstrap/helpers/docker.php | 53 +++++++------- ...add_application_http_basic_auth_fields.php | 4 +- openapi.json | 72 ++++++++++++++++++- openapi.yaml | 57 ++++++++++++++- .../project/application/general.blade.php | 18 ++--- tests/Feature/DockerCustomCommandsTest.php | 32 ++++++++- 9 files changed, 257 insertions(+), 53 deletions(-) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index f25f63b11..3e0627b21 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -45,6 +45,7 @@ class ApplicationsController extends Controller 'private_key_id', 'value', 'real_value', + 'http_basic_auth_password', ]); } @@ -183,6 +184,9 @@ class ApplicationsController extends Controller 'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'], 'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'], 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'], + 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], + 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], + 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'], ], ) ), @@ -299,6 +303,9 @@ class ApplicationsController extends Controller 'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'], 'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'], 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'], + 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], + 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], + 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'], ], ) ), @@ -415,6 +422,9 @@ class ApplicationsController extends Controller 'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'], 'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'], 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'], + 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], + 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], + 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'], ], ) ), @@ -515,6 +525,9 @@ class ApplicationsController extends Controller 'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']], 'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'], 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'], + 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], + 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], + 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'], ], ) ), @@ -612,6 +625,9 @@ class ApplicationsController extends Controller 'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']], 'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'], 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'], + 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], + 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], + 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'], ], ) ), @@ -711,7 +727,6 @@ class ApplicationsController extends Controller private function create_application(Request $request, $type) { - $allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image', 'custom_nginx_configuration']; $teamId = getTeamIdFromToken(); if (is_null($teamId)) { return invalidTokenResponse(); @@ -721,6 +736,8 @@ class ApplicationsController extends Controller if ($return instanceof \Illuminate\Http\JsonResponse) { return $return; } + $allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password']; + $validator = customApiValidator($request->all(), [ 'name' => 'string|max:255', 'description' => 'string|nullable', @@ -729,6 +746,9 @@ class ApplicationsController extends Controller 'environment_uuid' => 'string|nullable', 'server_uuid' => 'string|required', 'destination_uuid' => 'string', + 'is_http_basic_auth_enabled' => 'boolean', + 'http_basic_auth_username' => 'string|nullable', + 'http_basic_auth_password' => 'string|nullable', ]); $extraFields = array_diff(array_keys($request->all()), $allowedFields); @@ -1758,25 +1778,19 @@ class ApplicationsController extends Controller if (is_null($teamId)) { return invalidTokenResponse(); } - - if ($request->collect()->count() == 0) { - return response()->json([ - 'message' => 'Invalid request.', - ], 400); - } $return = validateIncomingRequest($request); if ($return instanceof \Illuminate\Http\JsonResponse) { return $return; } - $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first(); + $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first(); if (! $application) { return response()->json([ 'message' => 'Application not found', ], 404); } $server = $application->destination->server; - $allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'custom_nginx_configuration']; + $allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password']; $validationRules = [ 'name' => 'string|max:255', @@ -1789,6 +1803,9 @@ class ApplicationsController extends Controller 'docker_compose_custom_start_command' => 'string|nullable', 'docker_compose_custom_build_command' => 'string|nullable', 'custom_nginx_configuration' => 'string|nullable', + 'is_http_basic_auth_enabled' => 'boolean|nullable', + 'http_basic_auth_username' => 'string', + 'http_basic_auth_password' => 'string', ]; $validationRules = array_merge(sharedDataApplications(), $validationRules); $validator = customApiValidator($request->all(), $validationRules); @@ -1844,6 +1861,29 @@ class ApplicationsController extends Controller 'errors' => $errors, ], 422); } + + if ($request->has('is_http_basic_auth_enabled') && $request->is_http_basic_auth_enabled === true) { + if (blank($application->http_basic_auth_username) || blank($application->http_basic_auth_password)) { + $validationErrors = []; + if (blank($request->http_basic_auth_username)) { + $validationErrors['http_basic_auth_username'] = 'The http_basic_auth_username is required.'; + } + if (blank($request->http_basic_auth_password)) { + $validationErrors['http_basic_auth_password'] = 'The http_basic_auth_password is required.'; + } + if (count($validationErrors) > 0) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validationErrors, + ], 422); + } + } + } + if ($request->has('is_http_basic_auth_enabled') && $application->is_container_label_readonly_enabled === false) { + $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); + $application->save(); + } + $domains = $request->domains; $requestHasDomains = $request->has('domains'); if ($requestHasDomains && $server->isProxyShouldRun()) { diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index b7cb693b6..74f47232c 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -92,11 +92,11 @@ class General extends Component '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', - 'application.http_basic_auth_enabled' => 'boolean|required', - 'application.http_basic_auth_username' => 'nullable', - 'application.http_basic_auth_password' => 'nullable', ]; protected $validationAttributes = [ @@ -181,6 +181,9 @@ class General extends Component if ($this->application->settings->isDirty('is_spa')) { $this->generateNginxConfiguration($this->application->settings->is_spa ? 'spa' : 'static'); } + if ($this->application->isDirty('is_http_basic_auth_enabled')) { + $this->application->save(); + } $this->application->settings->save(); $this->dispatch('success', 'Settings saved.'); $this->application->refresh(); diff --git a/app/Models/Application.php b/app/Models/Application.php index 3306510d1..b84b7cd37 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -103,7 +103,7 @@ use Visus\Cuid2\Cuid2; 'deleted_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true, 'description' => 'The date and time when the application was deleted.'], 'compose_parsing_version' => ['type' => 'string', 'description' => 'How Coolify parse the compose file.'], 'custom_nginx_configuration' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom Nginx configuration base64 encoded.'], - 'http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], + 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'], 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'], 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'], ] @@ -119,7 +119,10 @@ class Application extends BaseModel protected $appends = ['server_status']; - protected $casts = ['custom_network_aliases' => 'array']; + protected $casts = [ + 'custom_network_aliases' => 'array', + 'http_basic_auth_password' => 'encrypted', + ]; public function customNetworkAliases(): Attribute { diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 7801ed00a..9363e4bf0 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -297,7 +297,7 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource) return $payload; } -function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, ?string $image = null, string $redirect_direction = 'both', ?string $predefinedPort = null, bool $http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null) +function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, ?string $image = null, string $redirect_direction = 'both', ?string $predefinedPort = null, bool $is_http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null) { $labels = collect([]); if ($serviceLabels) { @@ -306,7 +306,8 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, $labels->push("caddy_ingress_network={$network}"); } - $http_basic_auth_enabled = $http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null; + $is_http_basic_auth_enabled = $is_http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null; + $hashedPassword = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]); foreach ($domains as $loop => $domain) { $url = Url::fromString($domain); @@ -344,28 +345,27 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) { $labels->push("caddy_{$loop}.redir={$schema}://{$host_without_www}{uri}"); } - if ($http_basic_auth_enabled) { - $http_basic_auth_password = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]); - $labels->push("caddy_{$loop}.basicauth.{$http_basic_auth_username}=\"{$http_basic_auth_password}\""); + if ($is_http_basic_auth_enabled) { + $labels->push("caddy_{$loop}.basicauth.{$http_basic_auth_username}=\"{$hashedPassword}\""); } } return $labels->sort(); } -function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, bool $generate_unique_uuid = false, ?string $image = null, string $redirect_direction = 'both', bool $http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null) +function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, bool $generate_unique_uuid = false, ?string $image = null, string $redirect_direction = 'both', bool $is_http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null) { $labels = collect([]); $labels->push('traefik.enable=true'); $labels->push('traefik.http.middlewares.gzip.compress=true'); $labels->push('traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https'); - $http_basic_auth_enabled = $http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null; + $is_http_basic_auth_enabled = $is_http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null; $http_basic_auth_label = "http-basic-auth-{$uuid}"; + $hashedPassword = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]); - if ($http_basic_auth_enabled) { - $http_basic_auth_password = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]); - $labels->push("traefik.http.middlewares.{$http_basic_auth_label}.basicauth.users={$http_basic_auth_username}:{$http_basic_auth_password}"); + if ($is_http_basic_auth_enabled) { + $labels->push("traefik.http.middlewares.{$http_basic_auth_label}.basicauth.users={$http_basic_auth_username}:{$hashedPassword}"); } $middlewares_from_labels = collect([]); @@ -453,7 +453,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $labels = $labels->merge($redirect_to_www); $middlewares->push($to_www_name); } - if ($http_basic_auth_enabled) { + if ($is_http_basic_auth_enabled) { $middlewares->push($http_basic_auth_label); } $middlewares_from_labels->each(function ($middleware_name) use ($middlewares) { @@ -479,7 +479,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $labels = $labels->merge($redirect_to_www); $middlewares->push($to_www_name); } - if ($http_basic_auth_enabled) { + if ($is_http_basic_auth_enabled) { $middlewares->push($http_basic_auth_label); } $middlewares_from_labels->each(function ($middleware_name) use ($middlewares) { @@ -531,7 +531,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $labels = $labels->merge($redirect_to_www); $middlewares->push($to_www_name); } - if ($http_basic_auth_enabled) { + if ($is_http_basic_auth_enabled) { $middlewares->push($http_basic_auth_label); } $middlewares_from_labels->each(function ($middleware_name) use ($middlewares) { @@ -557,7 +557,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $labels = $labels->merge($redirect_to_www); $middlewares->push($to_www_name); } - if ($http_basic_auth_enabled) { + if ($is_http_basic_auth_enabled) { $middlewares->push($http_basic_auth_label); } $middlewares_from_labels->each(function ($middleware_name) use ($middlewares) { @@ -604,7 +604,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), redirect_direction: $application->redirect, - http_basic_auth_enabled: $application->http_basic_auth_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, )); @@ -619,7 +619,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), redirect_direction: $application->redirect, - http_basic_auth_enabled: $application->http_basic_auth_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, )); @@ -634,7 +634,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), redirect_direction: $application->redirect, - http_basic_auth_enabled: $application->http_basic_auth_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, )); @@ -647,7 +647,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), redirect_direction: $application->redirect, - http_basic_auth_enabled: $application->http_basic_auth_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, )); @@ -670,7 +670,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - http_basic_auth_enabled: $application->http_basic_auth_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, )); @@ -684,7 +684,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - http_basic_auth_enabled: $application->http_basic_auth_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, )); @@ -698,7 +698,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - http_basic_auth_enabled: $application->http_basic_auth_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, )); @@ -710,7 +710,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled(), - http_basic_auth_enabled: $application->http_basic_auth_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, )); @@ -779,11 +779,14 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null) $options[$option] = array_unique($options[$option]); } if ($option === '--hostname') { - $regexForParsingHostname = '/hostname=([^\s]+)/'; + // Match --hostname=value or --hostname value + $regexForParsingHostname = '/--hostname(?:=|\s+)([^\s]+)/'; preg_match($regexForParsingHostname, $custom_docker_run_options, $hostname_matches); $value = $hostname_matches[1] ?? null; - $options[$option][] = $value; - $options[$option] = array_unique($options[$option]); + if ($value) { + $options[$option][] = $value; + $options[$option] = array_unique($options[$option]); + } } if (isset($match[2]) && $match[2] !== '') { $value = $match[2]; diff --git a/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php b/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php index 247300abd..4b8e11bc8 100644 --- a/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php +++ b/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php @@ -12,7 +12,7 @@ return new class extends Migration public function up(): void { Schema::table('applications', function (Blueprint $table) { - $table->boolean('http_basic_auth_enabled')->default(false); + $table->boolean('is_http_basic_auth_enabled')->default(false); $table->string('http_basic_auth_username')->nullable(true)->default(null); $table->string('http_basic_auth_password')->nullable(true)->default(null); }); @@ -24,7 +24,7 @@ return new class extends Migration public function down(): void { Schema::table('applications', function (Blueprint $table) { - $table->dropColumn('http_basic_auth_enabled'); + $table->dropColumn('is_http_basic_auth_enabled'); $table->dropColumn('http_basic_auth_username'); $table->dropColumn('http_basic_auth_password'); }); diff --git a/openapi.json b/openapi.json index ccd7e2d83..40f663e51 100644 --- a/openapi.json +++ b/openapi.json @@ -339,6 +339,20 @@ "type": "boolean", "nullable": true, "description": "Use build server." + }, + "is_http_basic_auth_enabled": { + "type": "boolean", + "description": "HTTP Basic Authentication enabled." + }, + "http_basic_auth_username": { + "type": "string", + "nullable": true, + "description": "Username for HTTP Basic Authentication" + }, + "http_basic_auth_password": { + "type": "string", + "nullable": true, + "description": "Password for HTTP Basic Authentication" } }, "type": "object" @@ -673,6 +687,20 @@ "type": "boolean", "nullable": true, "description": "Use build server." + }, + "is_http_basic_auth_enabled": { + "type": "boolean", + "description": "HTTP Basic Authentication enabled." + }, + "http_basic_auth_username": { + "type": "string", + "nullable": true, + "description": "Username for HTTP Basic Authentication" + }, + "http_basic_auth_password": { + "type": "string", + "nullable": true, + "description": "Password for HTTP Basic Authentication" } }, "type": "object" @@ -1007,6 +1035,20 @@ "type": "boolean", "nullable": true, "description": "Use build server." + }, + "is_http_basic_auth_enabled": { + "type": "boolean", + "description": "HTTP Basic Authentication enabled." + }, + "http_basic_auth_username": { + "type": "string", + "nullable": true, + "description": "Username for HTTP Basic Authentication" + }, + "http_basic_auth_password": { + "type": "string", + "nullable": true, + "description": "Password for HTTP Basic Authentication" } }, "type": "object" @@ -1270,6 +1312,20 @@ "type": "boolean", "nullable": true, "description": "Use build server." + }, + "is_http_basic_auth_enabled": { + "type": "boolean", + "description": "HTTP Basic Authentication enabled." + }, + "http_basic_auth_username": { + "type": "string", + "nullable": true, + "description": "Username for HTTP Basic Authentication" + }, + "http_basic_auth_password": { + "type": "string", + "nullable": true, + "description": "Password for HTTP Basic Authentication" } }, "type": "object" @@ -1516,6 +1572,20 @@ "type": "boolean", "nullable": true, "description": "Use build server." + }, + "is_http_basic_auth_enabled": { + "type": "boolean", + "description": "HTTP Basic Authentication enabled." + }, + "http_basic_auth_username": { + "type": "string", + "nullable": true, + "description": "Username for HTTP Basic Authentication" + }, + "http_basic_auth_password": { + "type": "string", + "nullable": true, + "description": "Password for HTTP Basic Authentication" } }, "type": "object" @@ -7554,7 +7624,7 @@ "nullable": true, "description": "Custom Nginx configuration base64 encoded." }, - "http_basic_auth_enabled": { + "is_http_basic_auth_enabled": { "type": "boolean", "description": "HTTP Basic Authentication enabled." }, diff --git a/openapi.yaml b/openapi.yaml index 41b1efdae..c5113d9f7 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -248,6 +248,17 @@ paths: type: boolean nullable: true description: 'Use build server.' + is_http_basic_auth_enabled: + type: boolean + description: 'HTTP Basic Authentication enabled.' + http_basic_auth_username: + type: string + nullable: true + description: 'Username for HTTP Basic Authentication' + http_basic_auth_password: + type: string + nullable: true + description: 'Password for HTTP Basic Authentication' type: object responses: '201': @@ -487,6 +498,17 @@ paths: type: boolean nullable: true description: 'Use build server.' + is_http_basic_auth_enabled: + type: boolean + description: 'HTTP Basic Authentication enabled.' + http_basic_auth_username: + type: string + nullable: true + description: 'Username for HTTP Basic Authentication' + http_basic_auth_password: + type: string + nullable: true + description: 'Password for HTTP Basic Authentication' type: object responses: '201': @@ -726,6 +748,17 @@ paths: type: boolean nullable: true description: 'Use build server.' + is_http_basic_auth_enabled: + type: boolean + description: 'HTTP Basic Authentication enabled.' + http_basic_auth_username: + type: string + nullable: true + description: 'Username for HTTP Basic Authentication' + http_basic_auth_password: + type: string + nullable: true + description: 'Password for HTTP Basic Authentication' type: object responses: '201': @@ -912,6 +945,17 @@ paths: type: boolean nullable: true description: 'Use build server.' + is_http_basic_auth_enabled: + type: boolean + description: 'HTTP Basic Authentication enabled.' + http_basic_auth_username: + type: string + nullable: true + description: 'Username for HTTP Basic Authentication' + http_basic_auth_password: + type: string + nullable: true + description: 'Password for HTTP Basic Authentication' type: object responses: '201': @@ -1089,6 +1133,17 @@ paths: type: boolean nullable: true description: 'Use build server.' + is_http_basic_auth_enabled: + type: boolean + description: 'HTTP Basic Authentication enabled.' + http_basic_auth_username: + type: string + nullable: true + description: 'Username for HTTP Basic Authentication' + http_basic_auth_password: + type: string + nullable: true + description: 'Password for HTTP Basic Authentication' type: object responses: '201': @@ -5042,7 +5097,7 @@ components: type: string nullable: true description: 'Custom Nginx configuration base64 encoded.' - http_basic_auth_enabled: + is_http_basic_auth_enabled: type: boolean description: 'HTTP Basic Authentication enabled.' http_basic_auth_username: diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index 994d95bb8..d1b052603 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -350,16 +350,18 @@