diff --git a/app/Actions/Database/StartPostgresql.php b/app/Actions/Database/StartPostgresql.php index 7faa232c3..ebfe61f82 100644 --- a/app/Actions/Database/StartPostgresql.php +++ b/app/Actions/Database/StartPostgresql.php @@ -23,6 +23,9 @@ class StartPostgresql $this->database = $database; $container_name = $this->database->uuid; $this->configuration_dir = database_configuration_dir().'/'.$container_name; + if (isDev()) { + $this->configuration_dir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/databases/'.$container_name; + } $this->commands = [ "echo 'Starting database.'", @@ -78,7 +81,7 @@ class StartPostgresql ], ], ]; - if (! is_null($this->database->limits_cpuset)) { + if (filled($this->database->limits_cpuset)) { data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); } if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { @@ -108,7 +111,7 @@ class StartPostgresql ]; } } - if (! is_null($this->database->postgres_conf) && ! empty($this->database->postgres_conf)) { + if (filled($this->database->postgres_conf)) { $docker_compose['services'][$container_name]['volumes'][] = [ 'type' => 'bind', 'source' => $this->configuration_dir.'/custom-postgres.conf', @@ -199,9 +202,12 @@ class StartPostgresql private function generate_init_scripts() { - if (is_null($this->database->init_scripts) || count($this->database->init_scripts) === 0) { + $this->commands[] = "rm -rf $this->configuration_dir/docker-entrypoint-initdb.d/*"; + + if (blank($this->database->init_scripts) || count($this->database->init_scripts) === 0) { return; } + foreach ($this->database->init_scripts as $init_script) { $filename = data_get($init_script, 'filename'); $content = data_get($init_script, 'content'); @@ -213,10 +219,15 @@ class StartPostgresql private function add_custom_conf() { - if (is_null($this->database->postgres_conf) || empty($this->database->postgres_conf)) { + $filename = 'custom-postgres.conf'; + $config_file_path = "$this->configuration_dir/$filename"; + + if (blank($this->database->postgres_conf)) { + $this->commands[] = "rm -f $config_file_path"; + return; } - $filename = 'custom-postgres.conf'; + $content = $this->database->postgres_conf; if (! str($content)->contains('listen_addresses')) { $content .= "\nlisten_addresses = '*'"; @@ -224,6 +235,6 @@ class StartPostgresql $this->database->save(); } $content_base64 = base64_encode($content); - $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null"; + $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $config_file_path > /dev/null"; } } diff --git a/app/Console/Commands/CloudCleanupSubscriptions.php b/app/Console/Commands/CloudCleanupSubscriptions.php index 9198b003e..9dc6e24f5 100644 --- a/app/Console/Commands/CloudCleanupSubscriptions.php +++ b/app/Console/Commands/CloudCleanupSubscriptions.php @@ -2,6 +2,7 @@ namespace App\Console\Commands; +use App\Events\ServerReachabilityChanged; use App\Models\Team; use Illuminate\Console\Command; @@ -92,6 +93,8 @@ class CloudCleanupSubscriptions extends Command $server->update([ 'ip' => '1.2.3.4', ]); + + ServerReachabilityChanged::dispatch($server); } } } diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 2ed3ee454..c58ef2187 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -133,14 +133,14 @@ class Kernel extends ConsoleKernel foreach ($servers as $server) { $serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone); + if (validate_timezone($serverTimezone) === false) { + $serverTimezone = config('app.timezone'); + } // Sentinel check $lastSentinelUpdate = $server->sentinel_updated_at; if (Carbon::parse($lastSentinelUpdate)->isBefore(now()->subSeconds($server->waitBeforeDoingSshCheck()))) { // Check container status every minute if Sentinel does not activated - if (validate_timezone($serverTimezone) === false) { - $serverTimezone = config('app.timezone'); - } if (isCloud()) { $this->scheduleInstance->job(new ServerCheckJob($server))->timezone($serverTimezone)->everyFiveMinutes()->onOneServer(); } else { @@ -148,15 +148,11 @@ class Kernel extends ConsoleKernel } // $this->scheduleInstance->job(new \App\Jobs\ServerCheckNewJob($server))->everyFiveMinutes()->onOneServer(); - // Check storage usage every 10 minutes if Sentinel does not activated - $this->scheduleInstance->job(new ServerStorageCheckJob($server))->everyTenMinutes()->onOneServer(); - } - if ($server->settings->force_docker_cleanup) { - $this->scheduleInstance->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer(); - } else { - $this->scheduleInstance->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer(); + $this->scheduleInstance->job(new ServerStorageCheckJob($server))->cron($server->settings->server_disk_usage_check_frequency)->timezone($serverTimezone)->onOneServer(); } + $this->scheduleInstance->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer(); + // Cleanup multiplexed connections every hour // $this->scheduleInstance->job(new ServerCleanupMux($server))->hourly()->onOneServer(); diff --git a/app/Events/ServerReachabilityChanged.php b/app/Events/ServerReachabilityChanged.php new file mode 100644 index 000000000..fb4680146 --- /dev/null +++ b/app/Events/ServerReachabilityChanged.php @@ -0,0 +1,17 @@ +server->isReachableChanged(); + } +} diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 782a23a89..6fae33ece 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -27,6 +27,9 @@ class ApplicationsController extends Controller { $application->makeHidden([ 'id', + 'resourceable', + 'resourceable_id', + 'resourceable_type', ]); if (request()->attributes->get('can_read_sensitive', false) === false) { $application->makeHidden([ @@ -114,11 +117,12 @@ class ApplicationsController extends Controller mediaType: 'application/json', schema: new OA\Schema( type: 'object', - required: ['project_uuid', 'server_uuid', 'environment_name', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'], + required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'], properties: [ 'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'], 'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'], - 'environment_name' => ['type' => 'string', 'description' => 'The environment name.'], + 'environment_name' => ['type' => 'string', 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.'], + 'environment_uuid' => ['type' => 'string', 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.'], 'git_repository' => ['type' => 'string', 'description' => 'The git repository URL.'], 'git_branch' => ['type' => 'string', 'description' => 'The git branch.'], 'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'], @@ -229,11 +233,12 @@ class ApplicationsController extends Controller mediaType: 'application/json', schema: new OA\Schema( type: 'object', - required: ['project_uuid', 'server_uuid', 'environment_name', 'github_app_uuid', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'], + required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'github_app_uuid', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'], properties: [ 'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'], 'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'], - 'environment_name' => ['type' => 'string', 'description' => 'The environment name.'], + 'environment_name' => ['type' => 'string', 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.'], + 'environment_uuid' => ['type' => 'string', 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.'], 'github_app_uuid' => ['type' => 'string', 'description' => 'The Github App UUID.'], 'git_repository' => ['type' => 'string', 'description' => 'The git repository URL.'], 'git_branch' => ['type' => 'string', 'description' => 'The git branch.'], @@ -344,11 +349,12 @@ class ApplicationsController extends Controller mediaType: 'application/json', schema: new OA\Schema( type: 'object', - required: ['project_uuid', 'server_uuid', 'environment_name', 'private_key_uuid', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'], + required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'private_key_uuid', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'], properties: [ 'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'], 'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'], - 'environment_name' => ['type' => 'string', 'description' => 'The environment name.'], + 'environment_name' => ['type' => 'string', 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.'], + 'environment_uuid' => ['type' => 'string', 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.'], 'private_key_uuid' => ['type' => 'string', 'description' => 'The private key UUID.'], 'git_repository' => ['type' => 'string', 'description' => 'The git repository URL.'], 'git_branch' => ['type' => 'string', 'description' => 'The git branch.'], @@ -459,11 +465,12 @@ class ApplicationsController extends Controller mediaType: 'application/json', schema: new OA\Schema( type: 'object', - required: ['project_uuid', 'server_uuid', 'environment_name', 'dockerfile'], + required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'dockerfile'], properties: [ 'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'], 'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'], - 'environment_name' => ['type' => 'string', 'description' => 'The environment name.'], + 'environment_name' => ['type' => 'string', 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.'], + 'environment_uuid' => ['type' => 'string', 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.'], 'dockerfile' => ['type' => 'string', 'description' => 'The Dockerfile content.'], 'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'], 'ports_exposes' => ['type' => 'string', 'description' => 'The ports to expose.'], @@ -558,11 +565,12 @@ class ApplicationsController extends Controller mediaType: 'application/json', schema: new OA\Schema( type: 'object', - required: ['project_uuid', 'server_uuid', 'environment_name', 'docker_registry_image_name', 'ports_exposes'], + required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'docker_registry_image_name', 'ports_exposes'], properties: [ 'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'], 'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'], - 'environment_name' => ['type' => 'string', 'description' => 'The environment name.'], + 'environment_name' => ['type' => 'string', 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.'], + 'environment_uuid' => ['type' => 'string', 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.'], 'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'], 'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'], 'ports_exposes' => ['type' => 'string', 'description' => 'The ports to expose.'], @@ -654,11 +662,12 @@ class ApplicationsController extends Controller mediaType: 'application/json', schema: new OA\Schema( type: 'object', - required: ['project_uuid', 'server_uuid', 'environment_name', 'docker_compose_raw'], + required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'docker_compose_raw'], properties: [ 'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'], 'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'], - 'environment_name' => ['type' => 'string', 'description' => 'The environment name.'], + 'environment_name' => ['type' => 'string', 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.'], + 'environment_uuid' => ['type' => 'string', 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.'], 'docker_compose_raw' => ['type' => 'string', 'description' => 'The Docker Compose raw content.'], 'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID if the server has more than one destinations.'], 'name' => ['type' => 'string', 'description' => 'The application name.'], @@ -701,7 +710,7 @@ class ApplicationsController extends Controller private function create_application(Request $request, $type) { - $allowedFields = ['project_uuid', 'environment_name', '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']; + $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(); @@ -715,7 +724,8 @@ class ApplicationsController extends Controller 'name' => 'string|max:255', 'description' => 'string|nullable', 'project_uuid' => 'string|required', - 'environment_name' => 'string|required', + 'environment_name' => 'string|nullable', + 'environment_uuid' => 'string|nullable', 'server_uuid' => 'string|required', 'destination_uuid' => 'string', ]); @@ -735,6 +745,11 @@ class ApplicationsController extends Controller ], 422); } + $environmentUuid = $request->environment_uuid; + $environmentName = $request->environment_name; + if (blank($environmentUuid) && blank($environmentName)) { + return response()->json(['message' => 'You need to provide at least one of environment_name or environment_uuid.'], 422); + } $serverUuid = $request->server_uuid; $fqdn = $request->domains; $instantDeploy = $request->instant_deploy; @@ -767,7 +782,10 @@ class ApplicationsController extends Controller if (! $project) { return response()->json(['message' => 'Project not found.'], 404); } - $environment = $project->environments()->where('name', $request->environment_name)->first(); + $environment = $project->environments()->where('name', $environmentName)->first(); + if (! $environment) { + $environment = $project->environments()->where('uuid', $environmentUuid)->first(); + } if (! $environment) { return response()->json(['message' => 'Environment not found.'], 404); } @@ -784,12 +802,6 @@ class ApplicationsController extends Controller } $destination = $destinations->first(); if ($type === 'public') { - if (! $request->has('name')) { - $request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch)); - } - if ($request->build_pack === 'dockercompose') { - $request->offsetSet('ports_exposes', '80'); - } $validationRules = [ 'git_repository' => 'string|required', 'git_branch' => 'string|required', @@ -799,7 +811,7 @@ class ApplicationsController extends Controller 'docker_compose_raw' => 'string|nullable', 'docker_compose_domains' => 'array|nullable', ]; - $validationRules = array_merge($validationRules, sharedDataApplications()); + $validationRules = array_merge(sharedDataApplications(), $validationRules); $validator = customApiValidator($request->all(), $validationRules); if ($validator->fails()) { return response()->json([ @@ -807,6 +819,12 @@ class ApplicationsController extends Controller 'errors' => $validator->errors(), ], 422); } + if (! $request->has('name')) { + $request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch)); + } + if ($request->build_pack === 'dockercompose') { + $request->offsetSet('ports_exposes', '80'); + } $return = $this->validateDataApplications($request, $server); if ($return instanceof \Illuminate\Http\JsonResponse) { @@ -871,12 +889,6 @@ class ApplicationsController extends Controller 'domains' => data_get($application, 'domains'), ]))->setStatusCode(201); } elseif ($type === 'private-gh-app') { - if (! $request->has('name')) { - $request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch)); - } - if ($request->build_pack === 'dockercompose') { - $request->offsetSet('ports_exposes', '80'); - } $validationRules = [ 'git_repository' => 'string|required', 'git_branch' => 'string|required', @@ -887,7 +899,7 @@ class ApplicationsController extends Controller 'docker_compose_location' => 'string', 'docker_compose_raw' => 'string|nullable', ]; - $validationRules = array_merge($validationRules, sharedDataApplications()); + $validationRules = array_merge(sharedDataApplications(), $validationRules); $validator = customApiValidator($request->all(), $validationRules); if ($validator->fails()) { @@ -896,6 +908,14 @@ class ApplicationsController extends Controller 'errors' => $validator->errors(), ], 422); } + + if (! $request->has('name')) { + $request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch)); + } + if ($request->build_pack === 'dockercompose') { + $request->offsetSet('ports_exposes', '80'); + } + $return = $this->validateDataApplications($request, $server); if ($return instanceof \Illuminate\Http\JsonResponse) { return $return; @@ -970,12 +990,6 @@ class ApplicationsController extends Controller 'domains' => data_get($application, 'domains'), ]))->setStatusCode(201); } elseif ($type === 'private-deploy-key') { - if (! $request->has('name')) { - $request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch)); - } - if ($request->build_pack === 'dockercompose') { - $request->offsetSet('ports_exposes', '80'); - } $validationRules = [ 'git_repository' => 'string|required', @@ -988,7 +1002,7 @@ class ApplicationsController extends Controller 'docker_compose_raw' => 'string|nullable', ]; - $validationRules = array_merge($validationRules, sharedDataApplications()); + $validationRules = array_merge(sharedDataApplications(), $validationRules); $validator = customApiValidator($request->all(), $validationRules); if ($validator->fails()) { @@ -997,6 +1011,13 @@ class ApplicationsController extends Controller 'errors' => $validator->errors(), ], 422); } + if (! $request->has('name')) { + $request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch)); + } + if ($request->build_pack === 'dockercompose') { + $request->offsetSet('ports_exposes', '80'); + } + $return = $this->validateDataApplications($request, $server); if ($return instanceof \Illuminate\Http\JsonResponse) { return $return; @@ -1066,14 +1087,10 @@ class ApplicationsController extends Controller 'domains' => data_get($application, 'domains'), ]))->setStatusCode(201); } elseif ($type === 'dockerfile') { - if (! $request->has('name')) { - $request->offsetSet('name', 'dockerfile-'.new Cuid2); - } - $validationRules = [ 'dockerfile' => 'string|required', ]; - $validationRules = array_merge($validationRules, sharedDataApplications()); + $validationRules = array_merge(sharedDataApplications(), $validationRules); $validator = customApiValidator($request->all(), $validationRules); if ($validator->fails()) { @@ -1082,6 +1099,10 @@ class ApplicationsController extends Controller 'errors' => $validator->errors(), ], 422); } + if (! $request->has('name')) { + $request->offsetSet('name', 'dockerfile-'.new Cuid2); + } + $return = $this->validateDataApplications($request, $server); if ($return instanceof \Illuminate\Http\JsonResponse) { return $return; @@ -1152,15 +1173,12 @@ class ApplicationsController extends Controller 'domains' => data_get($application, 'domains'), ]))->setStatusCode(201); } elseif ($type === 'dockerimage') { - if (! $request->has('name')) { - $request->offsetSet('name', 'docker-image-'.new Cuid2); - } $validationRules = [ 'docker_registry_image_name' => 'string|required', 'docker_registry_image_tag' => 'string', 'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required', ]; - $validationRules = array_merge($validationRules, sharedDataApplications()); + $validationRules = array_merge(sharedDataApplications(), $validationRules); $validator = customApiValidator($request->all(), $validationRules); if ($validator->fails()) { @@ -1169,6 +1187,9 @@ class ApplicationsController extends Controller 'errors' => $validator->errors(), ], 422); } + if (! $request->has('name')) { + $request->offsetSet('name', 'docker-image-'.new Cuid2); + } $return = $this->validateDataApplications($request, $server); if ($return instanceof \Illuminate\Http\JsonResponse) { return $return; @@ -1216,7 +1237,7 @@ class ApplicationsController extends Controller 'domains' => data_get($application, 'domains'), ]))->setStatusCode(201); } elseif ($type === 'dockercompose') { - $allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'instant_deploy', 'docker_compose_raw']; + $allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'instant_deploy', 'docker_compose_raw']; $extraFields = array_diff(array_keys($request->all()), $allowedFields); if ($validator->fails() || ! empty($extraFields)) { @@ -1238,7 +1259,7 @@ class ApplicationsController extends Controller $validationRules = [ 'docker_compose_raw' => 'string|required', ]; - $validationRules = array_merge($validationRules, sharedDataApplications()); + $validationRules = array_merge(sharedDataApplications(), $validationRules); $validator = customApiValidator($request->all(), $validationRules); if ($validator->fails()) { @@ -1606,7 +1627,7 @@ class ApplicationsController extends Controller 'docker_compose_custom_build_command' => 'string|nullable', 'custom_nginx_configuration' => 'string|nullable', ]; - $validationRules = array_merge($validationRules, sharedDataApplications()); + $validationRules = array_merge(sharedDataApplications(), $validationRules); $validator = customApiValidator($request->all(), $validationRules); // Validate ports_exposes @@ -1948,8 +1969,9 @@ class ApplicationsController extends Controller $is_preview = $request->is_preview ?? false; $is_build_time = $request->is_build_time ?? false; $is_literal = $request->is_literal ?? false; + $key = str($request->key)->trim()->replace(' ', '_')->value; if ($is_preview) { - $env = $application->environment_variables_preview->where('key', $request->key)->first(); + $env = $application->environment_variables_preview->where('key', $key)->first(); if ($env) { $env->value = $request->value; if ($env->is_build_time != $is_build_time) { @@ -1976,7 +1998,7 @@ class ApplicationsController extends Controller ], 404); } } else { - $env = $application->environment_variables->where('key', $request->key)->first(); + $env = $application->environment_variables->where('key', $key)->first(); if ($env) { $env->value = $request->value; if ($env->is_build_time != $is_build_time) { @@ -2119,6 +2141,7 @@ class ApplicationsController extends Controller $bulk_data = collect($bulk_data)->map(function ($item) { return collect($item)->only(['key', 'value', 'is_preview', 'is_build_time', 'is_literal']); }); + $returnedEnvs = collect(); foreach ($bulk_data as $item) { $validator = customApiValidator($item, [ 'key' => 'string|required', @@ -2140,8 +2163,9 @@ class ApplicationsController extends Controller $is_literal = $item->get('is_literal') ?? false; $is_multi_line = $item->get('is_multiline') ?? false; $is_shown_once = $item->get('is_shown_once') ?? false; + $key = str($item->get('key'))->trim()->replace(' ', '_')->value; if ($is_preview) { - $env = $application->environment_variables_preview->where('key', $item->get('key'))->first(); + $env = $application->environment_variables_preview->where('key', $key)->first(); if ($env) { $env->value = $item->get('value'); if ($env->is_build_time != $is_build_time) { @@ -2166,10 +2190,12 @@ class ApplicationsController extends Controller 'is_literal' => $is_literal, 'is_multiline' => $is_multi_line, 'is_shown_once' => $is_shown_once, + 'resourceable_type' => get_class($application), + 'resourceable_id' => $application->id, ]); } } else { - $env = $application->environment_variables->where('key', $item->get('key'))->first(); + $env = $application->environment_variables->where('key', $key)->first(); if ($env) { $env->value = $item->get('value'); if ($env->is_build_time != $is_build_time) { @@ -2194,12 +2220,15 @@ class ApplicationsController extends Controller 'is_literal' => $is_literal, 'is_multiline' => $is_multi_line, 'is_shown_once' => $is_shown_once, + 'resourceable_type' => get_class($application), + 'resourceable_id' => $application->id, ]); } } + $returnedEnvs->push($this->removeSensitiveData($env)); } - return response()->json($this->removeSensitiveData($env))->setStatusCode(201); + return response()->json($returnedEnvs)->setStatusCode(201); } #[OA\Post( @@ -2312,8 +2341,10 @@ class ApplicationsController extends Controller ], 422); } $is_preview = $request->is_preview ?? false; + $key = str($request->key)->trim()->replace(' ', '_')->value; + if ($is_preview) { - $env = $application->environment_variables_preview->where('key', $request->key)->first(); + $env = $application->environment_variables_preview->where('key', $key)->first(); if ($env) { return response()->json([ 'message' => 'Environment variable already exists. Use PATCH request to update it.', @@ -2327,6 +2358,8 @@ class ApplicationsController extends Controller 'is_literal' => $request->is_literal ?? false, 'is_multiline' => $request->is_multiline ?? false, 'is_shown_once' => $request->is_shown_once ?? false, + 'resourceable_type' => get_class($application), + 'resourceable_id' => $application->id, ]); return response()->json([ @@ -2334,7 +2367,7 @@ class ApplicationsController extends Controller ])->setStatusCode(201); } } else { - $env = $application->environment_variables->where('key', $request->key)->first(); + $env = $application->environment_variables->where('key', $key)->first(); if ($env) { return response()->json([ 'message' => 'Environment variable already exists. Use PATCH request to update it.', @@ -2348,6 +2381,8 @@ class ApplicationsController extends Controller 'is_literal' => $request->is_literal ?? false, 'is_multiline' => $request->is_multiline ?? false, 'is_shown_once' => $request->is_shown_once ?? false, + 'resourceable_type' => get_class($application), + 'resourceable_id' => $application->id, ]); return response()->json([ @@ -2435,7 +2470,10 @@ class ApplicationsController extends Controller 'message' => 'Application not found.', ], 404); } - $found_env = EnvironmentVariable::where('uuid', $request->env_uuid)->where('application_id', $application->id)->first(); + $found_env = EnvironmentVariable::where('uuid', $request->env_uuid) + ->where('resourceable_type', Application::class) + ->where('resourceable_id', $application->id) + ->first(); if (! $found_env) { return response()->json([ 'message' => 'Environment variable not found.', diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php index 917171e5c..504665f6a 100644 --- a/app/Http/Controllers/Api/DatabasesController.php +++ b/app/Http/Controllers/Api/DatabasesController.php @@ -523,11 +523,12 @@ class DatabasesController extends Controller mediaType: 'application/json', schema: new OA\Schema( type: 'object', - required: ['server_uuid', 'project_uuid', 'environment_name'], + required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'], properties: [ 'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], 'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], - 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'], + 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'], + 'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'], 'postgres_user' => ['type' => 'string', 'description' => 'PostgreSQL user'], 'postgres_password' => ['type' => 'string', 'description' => 'PostgreSQL password'], 'postgres_db' => ['type' => 'string', 'description' => 'PostgreSQL database'], @@ -589,11 +590,12 @@ class DatabasesController extends Controller mediaType: 'application/json', schema: new OA\Schema( type: 'object', - required: ['server_uuid', 'project_uuid', 'environment_name'], + required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'], properties: [ 'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], 'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], - 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'], + 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'], + 'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'], 'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'], 'clickhouse_admin_user' => ['type' => 'string', 'description' => 'Clickhouse admin user'], 'clickhouse_admin_password' => ['type' => 'string', 'description' => 'Clickhouse admin password'], @@ -651,11 +653,12 @@ class DatabasesController extends Controller mediaType: 'application/json', schema: new OA\Schema( type: 'object', - required: ['server_uuid', 'project_uuid', 'environment_name'], + required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'], properties: [ 'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], 'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], - 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'], + 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'], + 'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'], 'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'], 'dragonfly_password' => ['type' => 'string', 'description' => 'DragonFly password'], 'name' => ['type' => 'string', 'description' => 'Name of the database'], @@ -712,11 +715,12 @@ class DatabasesController extends Controller mediaType: 'application/json', schema: new OA\Schema( type: 'object', - required: ['server_uuid', 'project_uuid', 'environment_name'], + required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'], properties: [ 'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], 'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], - 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'], + 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'], + 'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'], 'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'], 'redis_password' => ['type' => 'string', 'description' => 'Redis password'], 'redis_conf' => ['type' => 'string', 'description' => 'Redis conf'], @@ -774,11 +778,12 @@ class DatabasesController extends Controller mediaType: 'application/json', schema: new OA\Schema( type: 'object', - required: ['server_uuid', 'project_uuid', 'environment_name'], + required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'], properties: [ 'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], 'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], - 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'], + 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'], + 'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'], 'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'], 'keydb_password' => ['type' => 'string', 'description' => 'KeyDB password'], 'keydb_conf' => ['type' => 'string', 'description' => 'KeyDB conf'], @@ -836,11 +841,12 @@ class DatabasesController extends Controller mediaType: 'application/json', schema: new OA\Schema( type: 'object', - required: ['server_uuid', 'project_uuid', 'environment_name'], + required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'], properties: [ 'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], 'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], - 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'], + 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'], + 'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'], 'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'], 'mariadb_conf' => ['type' => 'string', 'description' => 'MariaDB conf'], 'mariadb_root_password' => ['type' => 'string', 'description' => 'MariaDB root password'], @@ -901,11 +907,12 @@ class DatabasesController extends Controller mediaType: 'application/json', schema: new OA\Schema( type: 'object', - required: ['server_uuid', 'project_uuid', 'environment_name'], + required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'], properties: [ 'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], 'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], - 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'], + 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'], + 'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'], 'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'], 'mysql_root_password' => ['type' => 'string', 'description' => 'MySQL root password'], 'mysql_password' => ['type' => 'string', 'description' => 'MySQL password'], @@ -966,11 +973,12 @@ class DatabasesController extends Controller mediaType: 'application/json', schema: new OA\Schema( type: 'object', - required: ['server_uuid', 'project_uuid', 'environment_name'], + required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'], properties: [ 'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], 'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], - 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'], + 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'], + 'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'], 'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'], 'mongo_conf' => ['type' => 'string', 'description' => 'MongoDB conf'], 'mongo_initdb_root_username' => ['type' => 'string', 'description' => 'MongoDB initdb root username'], @@ -1013,7 +1021,7 @@ class DatabasesController extends Controller public function create_database(Request $request, NewDatabaseTypes $type) { - $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf']; + $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf']; $teamId = getTeamIdFromToken(); if (is_null($teamId)) { @@ -1039,6 +1047,11 @@ class DatabasesController extends Controller 'errors' => $errors, ], 422); } + $environmentUuid = $request->environment_uuid; + $environmentName = $request->environment_name; + if (blank($environmentUuid) && blank($environmentName)) { + return response()->json(['message' => 'You need to provide at least one of environment_name or environment_uuid.'], 422); + } $serverUuid = $request->server_uuid; $instantDeploy = $request->instant_deploy ?? false; if ($request->is_public && ! $request->public_port) { @@ -1048,9 +1061,12 @@ class DatabasesController extends Controller if (! $project) { return response()->json(['message' => 'Project not found.'], 404); } - $environment = $project->environments()->where('name', $request->environment_name)->first(); + $environment = $project->environments()->where('name', $environmentName)->first(); if (! $environment) { - return response()->json(['message' => 'Environment not found.'], 404); + $environment = $project->environments()->where('uuid', $environmentUuid)->first(); + } + if (! $environment) { + return response()->json(['message' => 'You need to provide a valid environment_name or environment_uuid.'], 422); } $server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first(); if (! $server) { @@ -1074,7 +1090,8 @@ class DatabasesController extends Controller 'description' => 'string|nullable', 'image' => 'string', 'project_uuid' => 'string|required', - 'environment_name' => 'string|required', + 'environment_name' => 'string|nullable', + 'environment_uuid' => 'string|nullable', 'server_uuid' => 'string|required', 'destination_uuid' => 'string', 'is_public' => 'boolean', @@ -1105,7 +1122,7 @@ class DatabasesController extends Controller } } if ($type === NewDatabaseTypes::POSTGRESQL) { - $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf']; + $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf']; $validator = customApiValidator($request->all(), [ 'postgres_user' => 'string', 'postgres_password' => 'string', @@ -1164,7 +1181,7 @@ class DatabasesController extends Controller return response()->json(serializeApiResponse($payload))->setStatusCode(201); } elseif ($type === NewDatabaseTypes::MARIADB) { - $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database']; + $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database']; $validator = customApiValidator($request->all(), [ 'clickhouse_admin_user' => 'string', 'clickhouse_admin_password' => 'string', @@ -1220,7 +1237,7 @@ class DatabasesController extends Controller return response()->json(serializeApiResponse($payload))->setStatusCode(201); } elseif ($type === NewDatabaseTypes::MYSQL) { - $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf']; + $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf']; $validator = customApiValidator($request->all(), [ 'mysql_root_password' => 'string', 'mysql_password' => 'string', @@ -1279,7 +1296,7 @@ class DatabasesController extends Controller return response()->json(serializeApiResponse($payload))->setStatusCode(201); } elseif ($type === NewDatabaseTypes::REDIS) { - $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'redis_password', 'redis_conf']; + $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'redis_password', 'redis_conf']; $validator = customApiValidator($request->all(), [ 'redis_password' => 'string', 'redis_conf' => 'string', @@ -1335,7 +1352,7 @@ class DatabasesController extends Controller return response()->json(serializeApiResponse($payload))->setStatusCode(201); } elseif ($type === NewDatabaseTypes::DRAGONFLY) { - $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'dragonfly_password']; + $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'dragonfly_password']; $validator = customApiValidator($request->all(), [ 'dragonfly_password' => 'string', ]); @@ -1365,7 +1382,7 @@ class DatabasesController extends Controller 'uuid' => $database->uuid, ]))->setStatusCode(201); } elseif ($type === NewDatabaseTypes::KEYDB) { - $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'keydb_password', 'keydb_conf']; + $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'keydb_password', 'keydb_conf']; $validator = customApiValidator($request->all(), [ 'keydb_password' => 'string', 'keydb_conf' => 'string', @@ -1421,7 +1438,7 @@ class DatabasesController extends Controller return response()->json(serializeApiResponse($payload))->setStatusCode(201); } elseif ($type === NewDatabaseTypes::CLICKHOUSE) { - $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'clickhouse_admin_user', 'clickhouse_admin_password']; + $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'clickhouse_admin_user', 'clickhouse_admin_password']; $validator = customApiValidator($request->all(), [ 'clickhouse_admin_user' => 'string', 'clickhouse_admin_password' => 'string', @@ -1457,7 +1474,7 @@ class DatabasesController extends Controller return response()->json(serializeApiResponse($payload))->setStatusCode(201); } elseif ($type === NewDatabaseTypes::MONGODB) { - $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database']; + $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database']; $validator = customApiValidator($request->all(), [ 'mongo_conf' => 'string', 'mongo_initdb_root_username' => 'string', diff --git a/app/Http/Controllers/Api/ProjectController.php b/app/Http/Controllers/Api/ProjectController.php index e7b0af785..de008c84e 100644 --- a/app/Http/Controllers/Api/ProjectController.php +++ b/app/Http/Controllers/Api/ProjectController.php @@ -103,16 +103,16 @@ class ProjectController extends Controller #[OA\Get( summary: 'Environment', - description: 'Get environment by name.', - path: '/projects/{uuid}/{environment_name}', - operationId: 'get-environment-by-name', + description: 'Get environment by name or UUID.', + path: '/projects/{uuid}/{environment_name_or_uuid}', + operationId: 'get-environment-by-name-or-uuid', security: [ ['bearerAuth' => []], ], tags: ['Projects'], parameters: [ new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'string')), - new OA\Parameter(name: 'environment_name', in: 'path', required: true, description: 'Environment name', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'environment_name_or_uuid', in: 'path', required: true, description: 'Environment name or UUID', schema: new OA\Schema(type: 'string')), ], responses: [ new OA\Response( @@ -142,14 +142,17 @@ class ProjectController extends Controller if (! $request->uuid) { return response()->json(['message' => 'UUID is required.'], 422); } - if (! $request->environment_name) { - return response()->json(['message' => 'Environment name is required.'], 422); + if (! $request->environment_name_or_uuid) { + return response()->json(['message' => 'Environment name or UUID is required.'], 422); } $project = Project::whereTeamId($teamId)->whereUuid($request->uuid)->first(); if (! $project) { return response()->json(['message' => 'Project not found.'], 404); } - $environment = $project->environments()->whereName($request->environment_name)->first(); + $environment = $project->environments()->whereName($request->environment_name_or_uuid)->first(); + if (! $environment) { + $environment = $project->environments()->whereUuid($request->environment_name_or_uuid)->first(); + } if (! $environment) { return response()->json(['message' => 'Environment not found.'], 404); } diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index bcaba7107..03d9d209c 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -20,6 +20,9 @@ class ServicesController extends Controller { $service->makeHidden([ 'id', + 'resourceable', + 'resourceable_id', + 'resourceable_type', ]); if (request()->attributes->get('can_read_sensitive', false) === false) { $service->makeHidden([ @@ -99,7 +102,7 @@ class ServicesController extends Controller mediaType: 'application/json', schema: new OA\Schema( type: 'object', - required: ['server_uuid', 'project_uuid', 'environment_name', 'type'], + required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid', 'type'], properties: [ 'type' => [ 'description' => 'The one-click service type', @@ -196,7 +199,8 @@ class ServicesController extends Controller 'name' => ['type' => 'string', 'maxLength' => 255, 'description' => 'Name of the service.'], 'description' => ['type' => 'string', 'nullable' => true, 'description' => 'Description of the service.'], 'project_uuid' => ['type' => 'string', 'description' => 'Project UUID.'], - 'environment_name' => ['type' => 'string', 'description' => 'Environment name.'], + 'environment_name' => ['type' => 'string', 'description' => 'Environment name. You need to provide at least one of environment_name or environment_uuid.'], + 'environment_uuid' => ['type' => 'string', 'description' => 'Environment UUID. You need to provide at least one of environment_name or environment_uuid.'], 'server_uuid' => ['type' => 'string', 'description' => 'Server UUID.'], 'destination_uuid' => ['type' => 'string', 'description' => 'Destination UUID. Required if server has multiple destinations.'], 'instant_deploy' => ['type' => 'boolean', 'default' => false, 'description' => 'Start the service immediately after creation.'], @@ -233,7 +237,7 @@ class ServicesController extends Controller )] public function create_service(Request $request) { - $allowedFields = ['type', 'name', 'description', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy']; + $allowedFields = ['type', 'name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy']; $teamId = getTeamIdFromToken(); if (is_null($teamId)) { @@ -247,7 +251,8 @@ class ServicesController extends Controller $validator = customApiValidator($request->all(), [ 'type' => 'string|required', 'project_uuid' => 'string|required', - 'environment_name' => 'string|required', + 'environment_name' => 'string|nullable', + 'environment_uuid' => 'string|nullable', 'server_uuid' => 'string|required', 'destination_uuid' => 'string', 'name' => 'string|max:255', @@ -269,6 +274,11 @@ class ServicesController extends Controller 'errors' => $errors, ], 422); } + $environmentUuid = $request->environment_uuid; + $environmentName = $request->environment_name; + if (blank($environmentUuid) && blank($environmentName)) { + return response()->json(['message' => 'You need to provide at least one of environment_name or environment_uuid.'], 422); + } $serverUuid = $request->server_uuid; $instantDeploy = $request->instant_deploy ?? false; if ($request->is_public && ! $request->public_port) { @@ -278,7 +288,10 @@ class ServicesController extends Controller if (! $project) { return response()->json(['message' => 'Project not found.'], 404); } - $environment = $project->environments()->where('name', $request->environment_name)->first(); + $environment = $project->environments()->where('name', $environmentName)->first(); + if (! $environment) { + $environment = $project->environments()->where('uuid', $environmentUuid)->first(); + } if (! $environment) { return response()->json(['message' => 'Environment not found.'], 404); } @@ -333,7 +346,8 @@ class ServicesController extends Controller EnvironmentVariable::create([ 'key' => $key, 'value' => $generatedValue, - 'service_id' => $service->id, + 'resourceable_id' => $service->id, + 'resourceable_type' => $service->getMorphClass(), 'is_build_time' => false, 'is_preview' => false, ]); @@ -345,7 +359,11 @@ class ServicesController extends Controller } $domains = $service->applications()->get()->pluck('fqdn')->sort(); $domains = $domains->map(function ($domain) { - return str($domain)->beforeLast(':')->value(); + if (count(explode(':', $domain)) > 2) { + return str($domain)->beforeLast(':')->value(); + } + + return $domain; }); return response()->json([ @@ -673,7 +691,8 @@ class ServicesController extends Controller ], 422); } - $env = $service->environment_variables()->where('key', $request->key)->first(); + $key = str($request->key)->trim()->replace(' ', '_')->value; + $env = $service->environment_variables()->where('key', $key)->first(); if (! $env) { return response()->json(['message' => 'Environment variable not found.'], 404); } @@ -799,9 +818,9 @@ class ServicesController extends Controller 'errors' => $validator->errors(), ], 422); } - + $key = str($item['key'])->trim()->replace(' ', '_')->value; $env = $service->environment_variables()->updateOrCreate( - ['key' => $item['key']], + ['key' => $key], $item ); @@ -909,7 +928,8 @@ class ServicesController extends Controller ], 422); } - $existingEnv = $service->environment_variables()->where('key', $request->key)->first(); + $key = str($request->key)->trim()->replace(' ', '_')->value; + $existingEnv = $service->environment_variables()->where('key', $key)->first(); if ($existingEnv) { return response()->json([ 'message' => 'Environment variable already exists. Use PATCH request to update it.', @@ -995,7 +1015,8 @@ class ServicesController extends Controller } $env = EnvironmentVariable::where('uuid', $request->env_uuid) - ->where('service_id', $service->id) + ->where('resourceable_type', Service::class) + ->where('resourceable_id', $service->id) ->first(); if (! $env) { diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 6b677fa0e..7e4155338 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -1405,7 +1405,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue 'project_uuid' => data_get($this->application, 'environment.project.uuid'), 'application_uuid' => data_get($this->application, 'uuid'), 'deployment_uuid' => $deployment_uuid, - 'environment_name' => data_get($this->application, 'environment.name'), + 'environment_uuid' => data_get($this->application, 'environment.uuid'), ])); } } diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php index 06aec5e49..6730dceb7 100644 --- a/app/Jobs/DatabaseBackupJob.php +++ b/app/Jobs/DatabaseBackupJob.php @@ -32,8 +32,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue public Server $server; - public ScheduledDatabaseBackup $backup; - public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database; public ?string $container_name = null; @@ -58,10 +56,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue public ?S3Storage $s3 = null; - public function __construct($backup) + public function __construct(public ScheduledDatabaseBackup $backup) { $this->onQueue('high'); - $this->backup = $backup; } public function handle(): void diff --git a/app/Jobs/ServerCheckJob.php b/app/Jobs/ServerCheckJob.php index 49d8dfe08..9818d5c6a 100644 --- a/app/Jobs/ServerCheckJob.php +++ b/app/Jobs/ServerCheckJob.php @@ -31,12 +31,7 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue return [(new WithoutOverlapping($this->server->uuid))->dontRelease()]; } - public function __construct(public Server $server) - { - if (isDev()) { - $this->handle(); - } - } + public function __construct(public Server $server) {} public function handle() { diff --git a/app/Livewire/Admin/Index.php b/app/Livewire/Admin/Index.php index 359db6329..b5f6d2929 100644 --- a/app/Livewire/Admin/Index.php +++ b/app/Livewire/Admin/Index.php @@ -21,16 +21,28 @@ class Index extends Component public function mount() { - if (! isCloud()) { + if (! isCloud() && ! isDev()) { return redirect()->route('dashboard'); } - - if (Auth::id() !== 0) { + if (Auth::id() !== 0 && ! session('impersonating')) { return redirect()->route('dashboard'); } $this->getSubscribers(); } + public function back() + { + if (session('impersonating')) { + session()->forget('impersonating'); + $user = User::find(0); + $team_to_switch_to = $user->teams->first(); + Auth::login($user); + refreshSession($team_to_switch_to); + + return redirect(request()->header('Referer')); + } + } + public function submitSearch() { if ($this->search !== '') { @@ -52,9 +64,10 @@ class Index extends Component if (Auth::id() !== 0) { return redirect()->route('dashboard'); } + session(['impersonating' => true]); $user = User::find($user_id); $team_to_switch_to = $user->teams->first(); - Cache::forget("team:{$user->id}"); + // Cache::forget("team:{$user->id}"); Auth::login($user); refreshSession($team_to_switch_to); diff --git a/app/Livewire/Boarding/Index.php b/app/Livewire/Boarding/Index.php index eadabba7c..15eabfec5 100644 --- a/app/Livewire/Boarding/Index.php +++ b/app/Livewire/Boarding/Index.php @@ -9,6 +9,7 @@ use App\Models\Server; use App\Models\Team; use Illuminate\Support\Collection; use Livewire\Component; +use Visus\Cuid2\Cuid2; class Index extends Component { @@ -334,6 +335,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== $this->createdProject = Project::create([ 'name' => 'My first project', 'team_id' => currentTeam()->id, + 'uuid' => (string) new Cuid2, ]); $this->currentState = 'create-resource'; } @@ -346,7 +348,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== 'project.resource.create', [ 'project_uuid' => $this->createdProject->uuid, - 'environment_name' => 'production', + 'environment_uuid' => $this->createdProject->environments->first()->uuid, 'server' => $this->createdServer->id, ] ); diff --git a/app/Livewire/Dashboard.php b/app/Livewire/Dashboard.php index 69ba19e40..c3cb797bf 100644 --- a/app/Livewire/Dashboard.php +++ b/app/Livewire/Dashboard.php @@ -8,6 +8,7 @@ use App\Models\Project; use App\Models\Server; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Artisan; +use Illuminate\Support\Facades\Redirect; use Livewire\Component; class Dashboard extends Component @@ -49,6 +50,20 @@ class Dashboard extends Component ])->sortBy('id')->groupBy('server_name')->toArray(); } + public function navigateToProject($projectUuid) + { + $project = Project::where('uuid', $projectUuid)->first(); + + if ($project && $project->environments->count() === 1) { + return Redirect::route('project.resource.index', [ + 'project_uuid' => $projectUuid, + 'environment_uuid' => $project->environments->first()->uuid, + ]); + } + + return Redirect::route('project.show', ['project_uuid' => $projectUuid]); + } + public function render() { return view('livewire.dashboard'); diff --git a/app/Livewire/Project/AddEmpty.php b/app/Livewire/Project/AddEmpty.php index fd976548a..07873c059 100644 --- a/app/Livewire/Project/AddEmpty.php +++ b/app/Livewire/Project/AddEmpty.php @@ -5,6 +5,7 @@ namespace App\Livewire\Project; use App\Models\Project; use Livewire\Attributes\Validate; use Livewire\Component; +use Visus\Cuid2\Cuid2; class AddEmpty extends Component { @@ -22,6 +23,7 @@ class AddEmpty extends Component 'name' => $this->name, 'description' => $this->description, 'team_id' => currentTeam()->id, + 'uuid' => (string) new Cuid2, ]); return redirect()->route('project.show', $project->uuid); diff --git a/app/Livewire/Project/Application/Configuration.php b/app/Livewire/Project/Application/Configuration.php index 5261a0800..56e0caf75 100644 --- a/app/Livewire/Project/Application/Configuration.php +++ b/app/Livewire/Project/Application/Configuration.php @@ -3,43 +3,42 @@ namespace App\Livewire\Project\Application; use App\Models\Application; -use App\Models\Server; use Livewire\Component; class Configuration extends Component { + public $currentRoute; + public Application $application; + public $project; + + public $environment; + public $servers; protected $listeners = ['buildPackUpdated' => '$refresh']; public function mount() { + $this->currentRoute = request()->route()->getName(); $project = currentTeam() ->projects() ->select('id', 'uuid', 'team_id') ->where('uuid', request()->route('project_uuid')) ->firstOrFail(); $environment = $project->environments() - ->select('id', 'name', 'project_id') - ->where('name', request()->route('environment_name')) + ->select('id', 'uuid', 'name', 'project_id') + ->where('uuid', request()->route('environment_uuid')) ->firstOrFail(); $application = $environment->applications() ->with(['destination']) ->where('uuid', request()->route('application_uuid')) ->firstOrFail(); + $this->project = $project; + $this->environment = $environment; $this->application = $application; - if ($application->destination && $application->destination->server) { - $mainServer = $application->destination->server; - $this->servers = Server::ownedByCurrentTeam() - ->select('id', 'name') - ->where('id', '!=', $mainServer->id) - ->get(); - } else { - $this->servers = collect(); - } } public function render() diff --git a/app/Livewire/Project/Application/Deployment/Index.php b/app/Livewire/Project/Application/Deployment/Index.php index 4f761c2cf..b847c40ef 100644 --- a/app/Livewire/Project/Application/Deployment/Index.php +++ b/app/Livewire/Project/Application/Deployment/Index.php @@ -34,7 +34,7 @@ class Index extends Component if (! $project) { return redirect()->route('dashboard'); } - $environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']); + $environment = $project->load(['environments'])->environments->where('uuid', request()->route('environment_uuid'))->first()->load(['applications']); if (! $environment) { return redirect()->route('dashboard'); } diff --git a/app/Livewire/Project/Application/Deployment/Show.php b/app/Livewire/Project/Application/Deployment/Show.php index 04170fa28..4e86b5c7e 100644 --- a/app/Livewire/Project/Application/Deployment/Show.php +++ b/app/Livewire/Project/Application/Deployment/Show.php @@ -26,7 +26,7 @@ class Show extends Component if (! $project) { return redirect()->route('dashboard'); } - $environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']); + $environment = $project->load(['environments'])->environments->where('uuid', request()->route('environment_uuid'))->first()->load(['applications']); if (! $environment) { return redirect()->route('dashboard'); } @@ -34,19 +34,11 @@ class Show extends Component if (! $application) { return redirect()->route('dashboard'); } - // $activity = Activity::where('properties->type_uuid', '=', $deploymentUuid)->first(); - // if (!$activity) { - // return redirect()->route('project.application.deployment.index', [ - // 'project_uuid' => $project->uuid, - // 'environment_name' => $environment->name, - // 'application_uuid' => $application->uuid, - // ]); - // } $application_deployment_queue = ApplicationDeploymentQueue::where('deployment_uuid', $deploymentUuid)->first(); if (! $application_deployment_queue) { return redirect()->route('project.application.deployment.index', [ 'project_uuid' => $project->uuid, - 'environment_name' => $environment->name, + 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid, ]); } diff --git a/app/Livewire/Project/Application/DeploymentNavbar.php b/app/Livewire/Project/Application/DeploymentNavbar.php index 6a6fa2482..87b40d4dc 100644 --- a/app/Livewire/Project/Application/DeploymentNavbar.php +++ b/app/Livewire/Project/Application/DeploymentNavbar.php @@ -23,7 +23,7 @@ class DeploymentNavbar extends Component public function mount() { - $this->application = Application::find($this->application_deployment_queue->application_id); + $this->application = Application::ownedByCurrentTeam()->find($this->application_deployment_queue->application_id); $this->server = $this->application->destination->server; $this->is_debug_enabled = $this->application->settings->is_debug_enabled; } diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index ff29b74e9..d1cc3476c 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -327,7 +327,7 @@ class General extends Component } } - public function set_redirect() + public function setRedirect() { try { $has_www = collect($this->application->fqdns)->filter(fn ($fqdn) => str($fqdn)->contains('www.'))->count(); @@ -360,10 +360,10 @@ class General extends Component if ($warning) { $this->dispatch('warning', __('warning.sslipdomain')); } - $this->resetDefaultLabels(); + // $this->resetDefaultLabels(); if ($this->application->isDirty('redirect')) { - $this->set_redirect(); + $this->setRedirect(); } $this->checkFqdns(); diff --git a/app/Livewire/Project/Application/Heading.php b/app/Livewire/Project/Application/Heading.php index 19a6145b7..0afc9123a 100644 --- a/app/Livewire/Project/Application/Heading.php +++ b/app/Livewire/Project/Application/Heading.php @@ -38,7 +38,7 @@ class Heading extends Component { $this->parameters = [ 'project_uuid' => $this->application->project()->uuid, - 'environment_name' => $this->application->environment->name, + 'environment_uuid' => $this->application->environment->uuid, 'application_uuid' => $this->application->uuid, ]; $lastDeployment = $this->application->get_last_successful_deployment(); @@ -94,7 +94,7 @@ class Heading extends Component 'project_uuid' => $this->parameters['project_uuid'], 'application_uuid' => $this->parameters['application_uuid'], 'deployment_uuid' => $this->deploymentUuid, - 'environment_name' => $this->parameters['environment_name'], + 'environment_uuid' => $this->parameters['environment_uuid'], ]); } @@ -136,7 +136,7 @@ class Heading extends Component 'project_uuid' => $this->parameters['project_uuid'], 'application_uuid' => $this->parameters['application_uuid'], 'deployment_uuid' => $this->deploymentUuid, - 'environment_name' => $this->parameters['environment_name'], + 'environment_uuid' => $this->parameters['environment_uuid'], ]); } diff --git a/app/Livewire/Project/Application/Previews.php b/app/Livewire/Project/Application/Previews.php index d42bf03d7..bdf62706c 100644 --- a/app/Livewire/Project/Application/Previews.php +++ b/app/Livewire/Project/Application/Previews.php @@ -171,7 +171,7 @@ class Previews extends Component 'project_uuid' => $this->parameters['project_uuid'], 'application_uuid' => $this->parameters['application_uuid'], 'deployment_uuid' => $this->deployment_uuid, - 'environment_name' => $this->parameters['environment_name'], + 'environment_uuid' => $this->parameters['environment_uuid'], ]); } catch (\Throwable $e) { return handleError($e, $this); diff --git a/app/Livewire/Project/Application/Rollback.php b/app/Livewire/Project/Application/Rollback.php index 1e58a1458..ff5db1e08 100644 --- a/app/Livewire/Project/Application/Rollback.php +++ b/app/Livewire/Project/Application/Rollback.php @@ -37,7 +37,7 @@ class Rollback extends Component 'project_uuid' => $this->parameters['project_uuid'], 'application_uuid' => $this->parameters['application_uuid'], 'deployment_uuid' => $deployment_uuid, - 'environment_name' => $this->parameters['environment_name'], + 'environment_uuid' => $this->parameters['environment_uuid'], ]); } diff --git a/app/Livewire/Project/CloneMe.php b/app/Livewire/Project/CloneMe.php index 4d2bc6589..593355c44 100644 --- a/app/Livewire/Project/CloneMe.php +++ b/app/Livewire/Project/CloneMe.php @@ -12,7 +12,7 @@ class CloneMe extends Component { public string $project_uuid; - public string $environment_name; + public string $environment_uuid; public int $project_id; @@ -44,7 +44,7 @@ class CloneMe extends Component { $this->project_uuid = $project_uuid; $this->project = Project::where('uuid', $project_uuid)->firstOrFail(); - $this->environment = $this->project->environments->where('name', $this->environment_name)->first(); + $this->environment = $this->project->environments->where('uuid', $this->environment_uuid)->first(); $this->project_id = $this->project->id; $this->servers = currentTeam()->servers; $this->newName = str($this->project->name.'-clone-'.(string) new Cuid2)->slug(); @@ -89,6 +89,7 @@ class CloneMe extends Component if ($this->environment->name !== 'production') { $project->environments()->create([ 'name' => $this->environment->name, + 'uuid' => (string) new Cuid2, ]); } $environment = $project->environments->where('name', $this->environment->name)->first(); @@ -100,6 +101,7 @@ class CloneMe extends Component $project = $this->project; $environment = $this->project->environments()->create([ 'name' => $this->newName, + 'uuid' => (string) new Cuid2, ]); } $applications = $this->environment->applications; @@ -119,7 +121,7 @@ class CloneMe extends Component $environmentVaribles = $application->environment_variables()->get(); foreach ($environmentVaribles as $environmentVarible) { $newEnvironmentVariable = $environmentVarible->replicate()->fill([ - 'application_id' => $newApplication->id, + 'resourceable_id' => $newApplication->id, ]); $newEnvironmentVariable->save(); } @@ -145,17 +147,8 @@ class CloneMe extends Component $environmentVaribles = $database->environment_variables()->get(); foreach ($environmentVaribles as $environmentVarible) { $payload = []; - if ($database->type() === 'standalone-postgresql') { - $payload['standalone_postgresql_id'] = $newDatabase->id; - } elseif ($database->type() === 'standalone-redis') { - $payload['standalone_redis_id'] = $newDatabase->id; - } elseif ($database->type() === 'standalone-mongodb') { - $payload['standalone_mongodb_id'] = $newDatabase->id; - } elseif ($database->type() === 'standalone-mysql') { - $payload['standalone_mysql_id'] = $newDatabase->id; - } elseif ($database->type() === 'standalone-mariadb') { - $payload['standalone_mariadb_id'] = $newDatabase->id; - } + $payload['resourceable_id'] = $newDatabase->id; + $payload['resourceable_type'] = $newDatabase->getMorphClass(); $newEnvironmentVariable = $environmentVarible->replicate()->fill($payload); $newEnvironmentVariable->save(); } @@ -183,7 +176,7 @@ class CloneMe extends Component return redirect()->route('project.resource.index', [ 'project_uuid' => $project->uuid, - 'environment_name' => $environment->name, + 'environment_uuid' => $environment->uuid, ]); } catch (\Exception $e) { return handleError($e, $this); diff --git a/app/Livewire/Project/Database/Backup/Execution.php b/app/Livewire/Project/Database/Backup/Execution.php index 564091659..4ac3b2e2c 100644 --- a/app/Livewire/Project/Database/Backup/Execution.php +++ b/app/Livewire/Project/Database/Backup/Execution.php @@ -22,7 +22,7 @@ class Execution extends Component if (! $project) { return redirect()->route('dashboard'); } - $environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']); + $environment = $project->load(['environments'])->environments->where('uuid', request()->route('environment_uuid'))->first()->load(['applications']); if (! $environment) { return redirect()->route('dashboard'); } diff --git a/app/Livewire/Project/Database/Backup/Index.php b/app/Livewire/Project/Database/Backup/Index.php index 9ff2f48d5..2df32ec7b 100644 --- a/app/Livewire/Project/Database/Backup/Index.php +++ b/app/Livewire/Project/Database/Backup/Index.php @@ -14,7 +14,7 @@ class Index extends Component if (! $project) { return redirect()->route('dashboard'); } - $environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']); + $environment = $project->load(['environments'])->environments->where('uuid', request()->route('environment_uuid'))->first()->load(['applications']); if (! $environment) { return redirect()->route('dashboard'); } @@ -31,7 +31,7 @@ class Index extends Component ) { return redirect()->route('project.database.configuration', [ 'project_uuid' => $project->uuid, - 'environment_name' => $environment->name, + 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid, ]); } diff --git a/app/Livewire/Project/Database/BackupNow.php b/app/Livewire/Project/Database/BackupNow.php index 9c9c175e2..3cd360562 100644 --- a/app/Livewire/Project/Database/BackupNow.php +++ b/app/Livewire/Project/Database/BackupNow.php @@ -9,11 +9,9 @@ class BackupNow extends Component { public $backup; - public function backup_now() + public function backupNow() { - dispatch(new DatabaseBackupJob( - backup: $this->backup - )); + DatabaseBackupJob::dispatch($this->backup); $this->dispatch('success', 'Backup queued. It will be available in a few minutes.'); } } diff --git a/app/Livewire/Project/Database/Configuration.php b/app/Livewire/Project/Database/Configuration.php index e14b27cf6..938abba54 100644 --- a/app/Livewire/Project/Database/Configuration.php +++ b/app/Livewire/Project/Database/Configuration.php @@ -6,23 +6,34 @@ use Livewire\Component; class Configuration extends Component { + public $currentRoute; + public $database; + public $project; + + public $environment; + public function mount() { - $project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); - if (! $project) { - return redirect()->route('dashboard'); - } - $environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']); - if (! $environment) { - return redirect()->route('dashboard'); - } - $database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first(); - if (! $database) { - return redirect()->route('dashboard'); - } + $this->currentRoute = request()->route()->getName(); + + $project = currentTeam() + ->projects() + ->select('id', 'uuid', 'team_id') + ->where('uuid', request()->route('project_uuid')) + ->firstOrFail(); + $environment = $project->environments() + ->select('id', 'name', 'project_id', 'uuid') + ->where('uuid', request()->route('environment_uuid')) + ->firstOrFail(); + $database = $environment->databases() + ->where('uuid', request()->route('database_uuid')) + ->firstOrFail(); + $this->database = $database; + $this->project = $project; + $this->environment = $environment; if (str($this->database->status)->startsWith('running') && is_null($this->database->config_hash)) { $this->database->isConfigurationChanged(true); $this->dispatch('configurationChanged'); diff --git a/app/Livewire/Project/Database/Postgresql/General.php b/app/Livewire/Project/Database/Postgresql/General.php index c12fa49f3..88dd5c1a8 100644 --- a/app/Livewire/Project/Database/Postgresql/General.php +++ b/app/Livewire/Project/Database/Postgresql/General.php @@ -9,8 +9,6 @@ use App\Models\StandalonePostgresql; use Exception; use Livewire\Component; -use function Aws\filter; - class General extends Component { public StandalonePostgresql $database; @@ -126,10 +124,52 @@ class General extends Component public function save_init_script($script) { - $this->database->init_scripts = filter($this->database->init_scripts, fn ($s) => $s['filename'] !== $script['filename']); - $this->database->init_scripts = array_merge($this->database->init_scripts, [$script]); + $initScripts = collect($this->database->init_scripts ?? []); + + $existingScript = $initScripts->firstWhere('filename', $script['filename']); + $oldScript = $initScripts->firstWhere('index', $script['index']); + + if ($existingScript && $existingScript['index'] !== $script['index']) { + $this->dispatch('error', 'A script with this filename already exists.'); + + return; + } + + $container_name = $this->database->uuid; + $configuration_dir = database_configuration_dir().'/'.$container_name; + + if ($oldScript && $oldScript['filename'] !== $script['filename']) { + $old_file_path = "$configuration_dir/docker-entrypoint-initdb.d/{$oldScript['filename']}"; + $delete_command = "rm -f $old_file_path"; + try { + instant_remote_process([$delete_command], $this->server); + } catch (\Exception $e) { + $this->dispatch('error', 'Failed to remove old init script from server: '.$e->getMessage()); + + return; + } + } + + $index = $initScripts->search(function ($item) use ($script) { + return $item['index'] === $script['index']; + }); + + if ($index !== false) { + $initScripts[$index] = $script; + } else { + $initScripts->push($script); + } + + $this->database->init_scripts = $initScripts->values() + ->map(function ($item, $index) { + $item['index'] = $index; + + return $item; + }) + ->all(); + $this->database->save(); - $this->dispatch('success', 'Init script saved.'); + $this->dispatch('success', 'Init script saved and updated.'); } public function delete_init_script($script) @@ -137,12 +177,32 @@ class General extends Component $collection = collect($this->database->init_scripts); $found = $collection->firstWhere('filename', $script['filename']); if ($found) { - $this->database->init_scripts = $collection->filter(fn ($s) => $s['filename'] !== $script['filename'])->toArray(); + $container_name = $this->database->uuid; + $configuration_dir = database_configuration_dir().'/'.$container_name; + $file_path = "$configuration_dir/docker-entrypoint-initdb.d/{$script['filename']}"; + + $command = "rm -f $file_path"; + try { + instant_remote_process([$command], $this->server); + } catch (\Exception $e) { + $this->dispatch('error', 'Failed to remove init script from server: '.$e->getMessage()); + + return; + } + + $updatedScripts = $collection->filter(fn ($s) => $s['filename'] !== $script['filename']) + ->values() + ->map(function ($item, $index) { + $item['index'] = $index; + + return $item; + }) + ->all(); + + $this->database->init_scripts = $updatedScripts; $this->database->save(); $this->refresh(); - $this->dispatch('success', 'Init script deleted.'); - - return; + $this->dispatch('success', 'Init script deleted from the database and the server.'); } } diff --git a/app/Livewire/Project/EnvironmentEdit.php b/app/Livewire/Project/EnvironmentEdit.php index f48220b3d..e98b088ec 100644 --- a/app/Livewire/Project/EnvironmentEdit.php +++ b/app/Livewire/Project/EnvironmentEdit.php @@ -23,11 +23,11 @@ class EnvironmentEdit extends Component #[Validate(['nullable', 'string', 'max:255'])] public ?string $description = null; - public function mount(string $project_uuid, string $environment_name) + public function mount(string $project_uuid, string $environment_uuid) { try { $this->project = Project::ownedByCurrentTeam()->where('uuid', $project_uuid)->firstOrFail(); - $this->environment = $this->project->environments()->where('name', $environment_name)->firstOrFail(); + $this->environment = $this->project->environments()->where('uuid', $environment_uuid)->firstOrFail(); $this->syncData(); } catch (\Throwable $e) { return handleError($e, $this); @@ -52,7 +52,10 @@ class EnvironmentEdit extends Component { try { $this->syncData(true); - $this->redirectRoute('project.environment.edit', ['environment_name' => $this->environment->name, 'project_uuid' => $this->project->uuid]); + $this->redirectRoute('project.environment.edit', [ + 'environment_uuid' => $this->environment->uuid, + 'project_uuid' => $this->project->uuid, + ]); } catch (\Throwable $e) { return handleError($e, $this); } diff --git a/app/Livewire/Project/Index.php b/app/Livewire/Project/Index.php index f8eb838be..06bf88219 100644 --- a/app/Livewire/Project/Index.php +++ b/app/Livewire/Project/Index.php @@ -5,6 +5,7 @@ namespace App\Livewire\Project; use App\Models\PrivateKey; use App\Models\Project; use App\Models\Server; +use Illuminate\Support\Facades\Redirect; use Livewire\Component; class Index extends Component @@ -30,4 +31,18 @@ class Index extends Component { return view('livewire.project.index'); } + + public function navigateToProject($projectUuid) + { + $project = Project::where('uuid', $projectUuid)->first(); + + if ($project && $project->environments->count() === 1) { + return Redirect::route('project.resource.index', [ + 'project_uuid' => $projectUuid, + 'environment_uuid' => $project->environments->first()->uuid, + ]); + } + + return Redirect::route('project.show', ['project_uuid' => $projectUuid]); + } } diff --git a/app/Livewire/Project/New/DockerCompose.php b/app/Livewire/Project/New/DockerCompose.php index 199a20cf6..27975eaa2 100644 --- a/app/Livewire/Project/New/DockerCompose.php +++ b/app/Livewire/Project/New/DockerCompose.php @@ -59,7 +59,7 @@ class DockerCompose extends Component } $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); - $environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first(); + $environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first(); $destination_uuid = $this->query['destination']; $destination = StandaloneDocker::where('uuid', $destination_uuid)->first(); @@ -87,7 +87,8 @@ class DockerCompose extends Component 'value' => $variable, 'is_build_time' => false, 'is_preview' => false, - 'service_id' => $service->id, + 'resourceable_id' => $service->id, + 'resourceable_type' => $service->getMorphClass(), ]); } $service->name = "service-$service->uuid"; @@ -96,7 +97,7 @@ class DockerCompose extends Component return redirect()->route('project.service.configuration', [ 'service_uuid' => $service->uuid, - 'environment_name' => $environment->name, + 'environment_uuid' => $environment->uuid, 'project_uuid' => $project->uuid, ]); } catch (\Throwable $e) { diff --git a/app/Livewire/Project/New/DockerImage.php b/app/Livewire/Project/New/DockerImage.php index 417fb2ea0..942924437 100644 --- a/app/Livewire/Project/New/DockerImage.php +++ b/app/Livewire/Project/New/DockerImage.php @@ -45,7 +45,7 @@ class DockerImage extends Component $destination_class = $destination->getMorphClass(); $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); - $environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first(); + $environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first(); $application = Application::create([ 'name' => 'docker-image-'.new Cuid2, 'repository_project_id' => 0, @@ -69,7 +69,7 @@ class DockerImage extends Component return redirect()->route('project.application.configuration', [ 'application_uuid' => $application->uuid, - 'environment_name' => $environment->name, + 'environment_uuid' => $environment->uuid, 'project_uuid' => $project->uuid, ]); } diff --git a/app/Livewire/Project/New/EmptyProject.php b/app/Livewire/Project/New/EmptyProject.php index 28249b442..54cfc4b4d 100644 --- a/app/Livewire/Project/New/EmptyProject.php +++ b/app/Livewire/Project/New/EmptyProject.php @@ -4,6 +4,7 @@ namespace App\Livewire\Project\New; use App\Models\Project; use Livewire\Component; +use Visus\Cuid2\Cuid2; class EmptyProject extends Component { @@ -12,8 +13,9 @@ class EmptyProject extends Component $project = Project::create([ 'name' => generate_random_name(), 'team_id' => currentTeam()->id, + 'uuid' => (string) new Cuid2, ]); - return redirect()->route('project.show', ['project_uuid' => $project->uuid, 'environment_name' => 'production']); + return redirect()->route('project.show', ['project_uuid' => $project->uuid, 'environment_uuid' => $project->environments->first()->uuid]); } } diff --git a/app/Livewire/Project/New/GithubPrivateRepository.php b/app/Livewire/Project/New/GithubPrivateRepository.php index 2f4f5a25c..370d00555 100644 --- a/app/Livewire/Project/New/GithubPrivateRepository.php +++ b/app/Livewire/Project/New/GithubPrivateRepository.php @@ -177,7 +177,7 @@ class GithubPrivateRepository extends Component $destination_class = $destination->getMorphClass(); $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); - $environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first(); + $environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first(); $application = Application::create([ 'name' => generate_application_name($this->selected_repository_owner.'/'.$this->selected_repository_repo, $this->selected_branch_name), @@ -211,7 +211,7 @@ class GithubPrivateRepository extends Component return redirect()->route('project.application.configuration', [ 'application_uuid' => $application->uuid, - 'environment_name' => $environment->name, + 'environment_uuid' => $environment->uuid, 'project_uuid' => $project->uuid, ]); } catch (\Throwable $e) { diff --git a/app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php b/app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php index b46c4a794..01b0c9ae8 100644 --- a/app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php +++ b/app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php @@ -136,7 +136,7 @@ class GithubPrivateRepositoryDeployKey extends Component $this->get_git_source(); $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); - $environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first(); + $environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first(); if ($this->git_source === 'other') { $application_init = [ 'name' => generate_random_name(), @@ -184,7 +184,7 @@ class GithubPrivateRepositoryDeployKey extends Component return redirect()->route('project.application.configuration', [ 'application_uuid' => $application->uuid, - 'environment_name' => $environment->name, + 'environment_uuid' => $environment->uuid, 'project_uuid' => $project->uuid, ]); } catch (\Throwable $e) { diff --git a/app/Livewire/Project/New/PublicGitRepository.php b/app/Livewire/Project/New/PublicGitRepository.php index bd35dccef..2f2331fc0 100644 --- a/app/Livewire/Project/New/PublicGitRepository.php +++ b/app/Livewire/Project/New/PublicGitRepository.php @@ -225,7 +225,7 @@ class PublicGitRepository extends Component $this->validate(); $destination_uuid = $this->query['destination']; $project_uuid = $this->parameters['project_uuid']; - $environment_name = $this->parameters['environment_name']; + $environment_uuid = $this->parameters['environment_uuid']; $destination = StandaloneDocker::where('uuid', $destination_uuid)->first(); if (! $destination) { @@ -237,7 +237,7 @@ class PublicGitRepository extends Component $destination_class = $destination->getMorphClass(); $project = Project::where('uuid', $project_uuid)->first(); - $environment = $project->load(['environments'])->environments->where('name', $environment_name)->first(); + $environment = $project->load(['environments'])->environments->where('uuid', $environment_uuid)->first(); if ($this->build_pack === 'dockercompose' && isDev() && $this->new_compose_services) { $server = $destination->server; @@ -260,7 +260,7 @@ class PublicGitRepository extends Component return redirect()->route('project.service.configuration', [ 'service_uuid' => $service->uuid, - 'environment_name' => $environment->name, + 'environment_uuid' => $environment->uuid, 'project_uuid' => $project->uuid, ]); @@ -319,7 +319,7 @@ class PublicGitRepository extends Component return redirect()->route('project.application.configuration', [ 'application_uuid' => $application->uuid, - 'environment_name' => $environment->name, + 'environment_uuid' => $environment->uuid, 'project_uuid' => $project->uuid, ]); } catch (\Throwable $e) { diff --git a/app/Livewire/Project/New/Select.php b/app/Livewire/Project/New/Select.php index 0b6d075a4..2c098d6bc 100644 --- a/app/Livewire/Project/New/Select.php +++ b/app/Livewire/Project/New/Select.php @@ -61,7 +61,7 @@ class Select extends Component } $projectUuid = data_get($this->parameters, 'project_uuid'); $this->environments = Project::whereUuid($projectUuid)->first()->environments; - $this->selectedEnvironment = data_get($this->parameters, 'environment_name'); + $this->selectedEnvironment = data_get($this->parameters, 'environment_uuid'); } public function render() @@ -73,20 +73,10 @@ class Select extends Component { return redirect()->route('project.resource.create', [ 'project_uuid' => $this->parameters['project_uuid'], - 'environment_name' => $this->selectedEnvironment, + 'environment_uuid' => $this->selectedEnvironment, ]); } - // public function addExistingPostgresql() - // { - // try { - // instantCommand("psql {$this->existingPostgresqlUrl} -c 'SELECT 1'"); - // $this->dispatch('success', 'Successfully connected to the database.'); - // } catch (\Throwable $e) { - // return handleError($e, $this); - // } - // } - public function loadServices() { $services = get_service_templates(true); @@ -308,7 +298,7 @@ class Select extends Component return redirect()->route('project.resource.create', [ 'project_uuid' => $this->parameters['project_uuid'], - 'environment_name' => $this->parameters['environment_name'], + 'environment_uuid' => $this->parameters['environment_uuid'], 'type' => $this->type, 'destination' => $this->destination_uuid, 'server_id' => $this->server_id, @@ -323,7 +313,7 @@ class Select extends Component } else { return redirect()->route('project.resource.create', [ 'project_uuid' => $this->parameters['project_uuid'], - 'environment_name' => $this->parameters['environment_name'], + 'environment_uuid' => $this->parameters['environment_uuid'], 'type' => $this->type, 'destination' => $this->destination_uuid, 'server_id' => $this->server_id, diff --git a/app/Livewire/Project/New/SimpleDockerfile.php b/app/Livewire/Project/New/SimpleDockerfile.php index 3c7f42329..c3ed6039a 100644 --- a/app/Livewire/Project/New/SimpleDockerfile.php +++ b/app/Livewire/Project/New/SimpleDockerfile.php @@ -46,7 +46,7 @@ CMD ["nginx", "-g", "daemon off;"] $destination_class = $destination->getMorphClass(); $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); - $environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first(); + $environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first(); $port = get_port_from_dockerfile($this->dockerfile); if (! $port) { @@ -78,7 +78,7 @@ CMD ["nginx", "-g", "daemon off;"] return redirect()->route('project.application.configuration', [ 'application_uuid' => $application->uuid, - 'environment_name' => $environment->name, + 'environment_uuid' => $environment->uuid, 'project_uuid' => $project->uuid, ]); } diff --git a/app/Livewire/Project/Resource/Create.php b/app/Livewire/Project/Resource/Create.php index 9266a57fc..0faf0b8da 100644 --- a/app/Livewire/Project/Resource/Create.php +++ b/app/Livewire/Project/Resource/Create.php @@ -25,7 +25,7 @@ class Create extends Component return redirect()->route('dashboard'); } $this->project = $project; - $environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first(); + $environment = $project->load(['environments'])->environments->where('uuid', request()->route('environment_uuid'))->first(); if (! $environment) { return redirect()->route('dashboard'); } @@ -57,7 +57,7 @@ class Create extends Component return redirect()->route('project.database.configuration', [ 'project_uuid' => $project->uuid, - 'environment_name' => $environment->name, + 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid, ]); } @@ -95,7 +95,8 @@ class Create extends Component EnvironmentVariable::create([ 'key' => $key, 'value' => $value, - 'service_id' => $service->id, + 'resourceable_id' => $service->id, + 'resourceable_type' => $service->getMorphClass(), 'is_build_time' => false, 'is_preview' => false, ]); @@ -106,7 +107,7 @@ class Create extends Component return redirect()->route('project.service.configuration', [ 'service_uuid' => $service->uuid, - 'environment_name' => $environment->name, + 'environment_uuid' => $environment->uuid, 'project_uuid' => $project->uuid, ]); } diff --git a/app/Livewire/Project/Resource/EnvironmentSelect.php b/app/Livewire/Project/Resource/EnvironmentSelect.php index efb1b6ca2..a38d750da 100644 --- a/app/Livewire/Project/Resource/EnvironmentSelect.php +++ b/app/Livewire/Project/Resource/EnvironmentSelect.php @@ -15,7 +15,7 @@ class EnvironmentSelect extends Component public function mount() { - $this->selectedEnvironment = request()->route('environment_name'); + $this->selectedEnvironment = request()->route('environment_uuid'); $this->project_uuid = request()->route('project_uuid'); } @@ -28,7 +28,7 @@ class EnvironmentSelect extends Component } else { return redirect()->route('project.resource.index', [ 'project_uuid' => $this->project_uuid, - 'environment_name' => $value, + 'environment_uuid' => $value, ]); } } diff --git a/app/Livewire/Project/Resource/Index.php b/app/Livewire/Project/Resource/Index.php index 0c2ea802a..2b199dcfd 100644 --- a/app/Livewire/Project/Resource/Index.php +++ b/app/Livewire/Project/Resource/Index.php @@ -4,6 +4,7 @@ namespace App\Livewire\Project\Resource; use App\Models\Environment; use App\Models\Project; +use Illuminate\Support\Collection; use Livewire\Component; class Index extends Component @@ -12,39 +13,42 @@ class Index extends Component public Environment $environment; - public $applications = []; + public Collection $applications; - public $postgresqls = []; + public Collection $postgresqls; - public $redis = []; + public Collection $redis; - public $mongodbs = []; + public Collection $mongodbs; - public $mysqls = []; + public Collection $mysqls; - public $mariadbs = []; + public Collection $mariadbs; - public $keydbs = []; + public Collection $keydbs; - public $dragonflies = []; + public Collection $dragonflies; - public $clickhouses = []; + public Collection $clickhouses; - public $services = []; + public Collection $services; public array $parameters; public function mount() { + $this->applications = $this->postgresqls = $this->redis = $this->mongodbs = $this->mysqls = $this->mariadbs = $this->keydbs = $this->dragonflies = $this->clickhouses = $this->services = collect(); $this->parameters = get_route_parameters(); - $project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); - if (! $project) { - return redirect()->route('dashboard'); - } - $environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first(); - if (! $environment) { - return redirect()->route('dashboard'); - } + $project = currentTeam() + ->projects() + ->select('id', 'uuid', 'team_id', 'name') + ->where('uuid', request()->route('project_uuid')) + ->firstOrFail(); + $environment = $project->environments() + ->select('id', 'uuid', 'name', 'project_id') + ->where('uuid', request()->route('environment_uuid')) + ->firstOrFail(); + $this->project = $project; $this->environment = $environment->loadCount([ 'applications', @@ -69,9 +73,9 @@ class Index extends Component ])->get()->sortBy('name'); $this->applications = $this->applications->map(function ($application) { $application->hrefLink = route('project.application.configuration', [ - 'project_uuid' => $this->project->uuid, - 'application_uuid' => $application->uuid, - 'environment_name' => $this->environment->name, + 'project_uuid' => data_get($application, 'environment.project.uuid'), + 'environment_uuid' => data_get($application, 'environment.uuid'), + 'application_uuid' => data_get($application, 'uuid'), ]); return $application; @@ -89,14 +93,6 @@ class Index extends Component 'clickhouses' => 'clickhouses', ]; - // Load all server-related data first to prevent duplicate queries - $serverData = $this->environment->applications() - ->with(['destination.server.settings']) - ->get() - ->pluck('destination.server') - ->filter() - ->unique('id'); - foreach ($databaseTypes as $property => $relation) { $this->{$property} = $this->environment->{$relation}()->with([ 'tags', @@ -106,7 +102,7 @@ class Index extends Component $db->hrefLink = route('project.database.configuration', [ 'project_uuid' => $this->project->uuid, 'database_uuid' => $db->uuid, - 'environment_name' => $this->environment->name, + 'environment_uuid' => data_get($this->environment, 'uuid'), ]); return $db; @@ -120,9 +116,9 @@ class Index extends Component ])->get()->sortBy('name'); $this->services = $this->services->map(function ($service) { $service->hrefLink = route('project.service.configuration', [ - 'project_uuid' => $this->project->uuid, - 'service_uuid' => $service->uuid, - 'environment_name' => $this->environment->name, + 'project_uuid' => data_get($service, 'environment.project.uuid'), + 'environment_uuid' => data_get($service, 'environment.uuid'), + 'service_uuid' => data_get($service, 'uuid'), ]); return $service; diff --git a/app/Livewire/Project/Service/Configuration.php b/app/Livewire/Project/Service/Configuration.php index 319ead361..4a5e8627f 100644 --- a/app/Livewire/Project/Service/Configuration.php +++ b/app/Livewire/Project/Service/Configuration.php @@ -9,16 +9,22 @@ use Livewire\Component; class Configuration extends Component { + public $currentRoute; + + public $project; + + public $environment; + public ?Service $service = null; public $applications; public $databases; - public array $parameters; - public array $query; + public array $parameters; + public function getListeners() { $userId = Auth::id(); @@ -38,11 +44,21 @@ class Configuration extends Component public function mount() { $this->parameters = get_route_parameters(); + $this->currentRoute = request()->route()->getName(); $this->query = request()->query(); - $this->service = Service::whereUuid($this->parameters['service_uuid'])->first(); - if (! $this->service) { - return redirect()->route('dashboard'); - } + $project = currentTeam() + ->projects() + ->select('id', 'uuid', 'team_id') + ->where('uuid', request()->route('project_uuid')) + ->firstOrFail(); + $environment = $project->environments() + ->select('id', 'uuid', 'name', 'project_id') + ->where('uuid', request()->route('environment_uuid')) + ->firstOrFail(); + $this->service = $environment->services()->whereUuid(request()->route('service_uuid'))->firstOrFail(); + + $this->project = $project; + $this->environment = $environment; $this->applications = $this->service->applications->sort(); $this->databases = $this->service->databases->sort(); } diff --git a/app/Livewire/Project/Shared/Danger.php b/app/Livewire/Project/Shared/Danger.php index a0b4ac2c4..7da48f9fb 100644 --- a/app/Livewire/Project/Shared/Danger.php +++ b/app/Livewire/Project/Shared/Danger.php @@ -20,7 +20,7 @@ class Danger extends Component public $projectUuid; - public $environmentName; + public $environmentUuid; public bool $delete_configurations = true; @@ -39,7 +39,7 @@ class Danger extends Component $parameters = get_route_parameters(); $this->modalId = new Cuid2; $this->projectUuid = data_get($parameters, 'project_uuid'); - $this->environmentName = data_get($parameters, 'environment_name'); + $this->environmentUuid = data_get($parameters, 'environment_uuid'); if ($this->resource === null) { if (isset($parameters['service_uuid'])) { @@ -107,7 +107,7 @@ class Danger extends Component return redirect()->route('project.resource.index', [ 'project_uuid' => $this->projectUuid, - 'environment_name' => $this->environmentName, + 'environment_uuid' => $this->environmentUuid, ]); } catch (\Throwable $e) { return handleError($e, $this); diff --git a/app/Livewire/Project/Shared/Destination.php b/app/Livewire/Project/Shared/Destination.php index c305e817c..1759fe08a 100644 --- a/app/Livewire/Project/Shared/Destination.php +++ b/app/Livewire/Project/Shared/Destination.php @@ -8,6 +8,7 @@ use App\Events\ApplicationStatusChanged; use App\Models\InstanceSettings; use App\Models\Server; use App\Models\StandaloneDocker; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; use Livewire\Component; @@ -17,7 +18,7 @@ class Destination extends Component { public $resource; - public $networks = []; + public Collection $networks; public function getListeners() { @@ -30,6 +31,7 @@ class Destination extends Component public function mount() { + $this->networks = collect([]); $this->loadData(); } @@ -55,38 +57,46 @@ class Destination extends Component } } - public function stop(int $server_id) + public function stop($serverId) { - $server = Server::find($server_id); - StopApplicationOneServer::run($this->resource, $server); - $this->refreshServers(); + try { + $server = Server::ownedByCurrentTeam()->findOrFail($serverId); + StopApplicationOneServer::run($this->resource, $server); + $this->refreshServers(); + } catch (\Exception $e) { + return handleError($e, $this); + } } public function redeploy(int $network_id, int $server_id) { - if ($this->resource->additional_servers->count() > 0 && str($this->resource->docker_registry_image_name)->isEmpty()) { - $this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.
More information here: documentation'); + try { + if ($this->resource->additional_servers->count() > 0 && str($this->resource->docker_registry_image_name)->isEmpty()) { + $this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.
More information here: documentation'); - return; + return; + } + $deployment_uuid = new Cuid2; + $server = Server::ownedByCurrentTeam()->findOrFail($server_id); + $destination = $server->standaloneDockers->where('id', $network_id)->firstOrFail(); + queue_application_deployment( + deployment_uuid: $deployment_uuid, + application: $this->resource, + server: $server, + destination: $destination, + only_this_server: true, + no_questions_asked: true, + ); + + return redirect()->route('project.application.deployment.show', [ + 'project_uuid' => data_get($this->resource, 'environment.project.uuid'), + 'application_uuid' => data_get($this->resource, 'uuid'), + 'deployment_uuid' => $deployment_uuid, + 'environment_uuid' => data_get($this->resource, 'environment.uuid'), + ]); + } catch (\Exception $e) { + return handleError($e, $this); } - $deployment_uuid = new Cuid2; - $server = Server::find($server_id); - $destination = StandaloneDocker::find($network_id); - queue_application_deployment( - deployment_uuid: $deployment_uuid, - application: $this->resource, - server: $server, - destination: $destination, - only_this_server: true, - no_questions_asked: true, - ); - - return redirect()->route('project.application.deployment.show', [ - 'project_uuid' => data_get($this->resource, 'environment.project.uuid'), - 'application_uuid' => data_get($this->resource, 'uuid'), - 'deployment_uuid' => $deployment_uuid, - 'environment_name' => data_get($this->resource, 'environment.name'), - ]); } public function promote(int $network_id, int $server_id) @@ -119,23 +129,27 @@ class Destination extends Component public function removeServer(int $network_id, int $server_id, $password) { - if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) { - if (! Hash::check($password, Auth::user()->password)) { - $this->addError('password', 'The provided password is incorrect.'); + try { + if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) { + if (! Hash::check($password, Auth::user()->password)) { + $this->addError('password', 'The provided password is incorrect.'); + + return; + } + } + + if ($this->resource->destination->server->id == $server_id && $this->resource->destination->id == $network_id) { + $this->dispatch('error', 'You cannot remove this destination server.', 'You are trying to remove the main server.'); return; } + $server = Server::ownedByCurrentTeam()->findOrFail($server_id); + StopApplicationOneServer::run($this->resource, $server); + $this->resource->additional_networks()->detach($network_id, ['server_id' => $server_id]); + $this->loadData(); + ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id')); + } catch (\Exception $e) { + return handleError($e, $this); } - - if ($this->resource->destination->server->id == $server_id && $this->resource->destination->id == $network_id) { - $this->dispatch('error', 'You cannot remove this destination server.', 'You are trying to remove the main server.'); - - return; - } - $server = Server::find($server_id); - StopApplicationOneServer::run($this->resource, $server); - $this->resource->additional_networks()->detach($network_id, ['server_id' => $server_id]); - $this->loadData(); - ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id')); } } diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php index 787d33a69..80156bf65 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -4,7 +4,6 @@ namespace App\Livewire\Project\Shared\EnvironmentVariable; use App\Models\EnvironmentVariable; use Livewire\Component; -use Visus\Cuid2\Cuid2; class All extends Component { @@ -14,38 +13,35 @@ class All extends Component public bool $showPreview = false; - public ?string $modalId = null; - public ?string $variables = null; public ?string $variablesPreview = null; public string $view = 'normal'; + public bool $is_env_sorting_enabled = false; + protected $listeners = [ 'saveKey' => 'submit', 'refreshEnvs', 'environmentVariableDeleted' => 'refreshEnvs', ]; - protected $rules = [ - 'resource.settings.is_env_sorting_enabled' => 'required|boolean', - ]; - public function mount() { + $this->is_env_sorting_enabled = data_get($this->resource, 'settings.is_env_sorting_enabled', false); $this->resourceClass = get_class($this->resource); $resourceWithPreviews = [\App\Models\Application::class]; - $simpleDockerfile = ! is_null(data_get($this->resource, 'dockerfile')); + $simpleDockerfile = filled(data_get($this->resource, 'dockerfile')); if (str($this->resourceClass)->contains($resourceWithPreviews) && ! $simpleDockerfile) { $this->showPreview = true; } - $this->modalId = new Cuid2; $this->sortEnvironmentVariables(); } public function instantSave() { + $this->resource->settings->is_env_sorting_enabled = $this->is_env_sorting_enabled; $this->resource->settings->save(); $this->sortEnvironmentVariables(); $this->dispatch('success', 'Environment variable settings updated.'); @@ -53,7 +49,7 @@ class All extends Component public function sortEnvironmentVariables() { - if (! data_get($this->resource, 'settings.is_env_sorting_enabled')) { + if ($this->is_env_sorting_enabled === false) { if ($this->resource->environment_variables) { $this->resource->environment_variables = $this->resource->environment_variables->sortBy('order')->values(); } @@ -178,35 +174,12 @@ class All extends Component $environment->is_multiline = $data['is_multiline'] ?? false; $environment->is_literal = $data['is_literal'] ?? false; $environment->is_preview = $data['is_preview'] ?? false; - - $resourceType = $this->resource->type(); - $resourceIdField = $this->getResourceIdField($resourceType); - - if ($resourceIdField) { - $environment->$resourceIdField = $this->resource->id; - } + $environment->resourceable_id = $this->resource->id; + $environment->resourceable_type = $this->resource->getMorphClass(); return $environment; } - private function getResourceIdField($resourceType) - { - $resourceTypes = [ - 'application' => 'application_id', - 'standalone-postgresql' => 'standalone_postgresql_id', - 'standalone-redis' => 'standalone_redis_id', - 'standalone-mongodb' => 'standalone_mongodb_id', - 'standalone-mysql' => 'standalone_mysql_id', - 'standalone-mariadb' => 'standalone_mariadb_id', - 'standalone-keydb' => 'standalone_keydb_id', - 'standalone-dragonfly' => 'standalone_dragonfly_id', - 'standalone-clickhouse' => 'standalone_clickhouse_id', - 'service' => 'service_id', - ]; - - return $resourceTypes[$resourceType] ?? null; - } - private function deleteRemovedVariables($isPreview, $variables) { $method = $isPreview ? 'environment_variables_preview' : 'environment_variables'; @@ -231,34 +204,14 @@ class All extends Component $environment->is_build_time = false; $environment->is_multiline = false; $environment->is_preview = $isPreview; + $environment->resourceable_id = $this->resource->id; + $environment->resourceable_type = $this->resource->getMorphClass(); - $this->setEnvironmentResourceId($environment); $environment->save(); } } } - private function setEnvironmentResourceId($environment) - { - $resourceTypes = [ - 'application' => 'application_id', - 'standalone-postgresql' => 'standalone_postgresql_id', - 'standalone-redis' => 'standalone_redis_id', - 'standalone-mongodb' => 'standalone_mongodb_id', - 'standalone-mysql' => 'standalone_mysql_id', - 'standalone-mariadb' => 'standalone_mariadb_id', - 'standalone-keydb' => 'standalone_keydb_id', - 'standalone-dragonfly' => 'standalone_dragonfly_id', - 'standalone-clickhouse' => 'standalone_clickhouse_id', - 'service' => 'service_id', - ]; - - $resourceType = $this->resource->type(); - if (isset($resourceTypes[$resourceType])) { - $environment->{$resourceTypes[$resourceType]} = $this->resource->id; - } - } - public function refreshEnvs() { $this->resource->refresh(); diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php index 6294d97c6..4b66bfdcb 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php @@ -5,7 +5,6 @@ namespace App\Livewire\Project\Shared\EnvironmentVariable; use App\Models\EnvironmentVariable as ModelsEnvironmentVariable; use App\Models\SharedEnvironmentVariable; use Livewire\Component; -use Visus\Cuid2\Cuid2; class Show extends Component { @@ -13,8 +12,6 @@ class Show extends Component public ModelsEnvironmentVariable|SharedEnvironmentVariable $env; - public ?string $modalId = null; - public bool $isDisabled = false; public bool $isLocked = false; @@ -23,6 +20,26 @@ class Show extends Component public string $type; + public string $key; + + public ?string $value = null; + + public ?string $real_value = null; + + public bool $is_shared = false; + + public bool $is_build_time = false; + + public bool $is_multiline = false; + + public bool $is_literal = false; + + public bool $is_shown_once = false; + + public bool $is_required = false; + + public bool $is_really_required = false; + protected $listeners = [ 'refreshEnvs' => 'refresh', 'refresh', @@ -30,40 +47,59 @@ class Show extends Component ]; protected $rules = [ - 'env.key' => 'required|string', - 'env.value' => 'nullable', - 'env.is_build_time' => 'required|boolean', - 'env.is_multiline' => 'required|boolean', - 'env.is_literal' => 'required|boolean', - 'env.is_shown_once' => 'required|boolean', - 'env.real_value' => 'nullable', - 'env.is_required' => 'required|boolean', + 'key' => 'required|string', + 'value' => 'nullable', + 'is_build_time' => 'required|boolean', + 'is_multiline' => 'required|boolean', + 'is_literal' => 'required|boolean', + 'is_shown_once' => 'required|boolean', + 'real_value' => 'nullable', + 'is_required' => 'required|boolean', ]; - protected $validationAttributes = [ - 'env.key' => 'Key', - 'env.value' => 'Value', - 'env.is_build_time' => 'Build Time', - 'env.is_multiline' => 'Multiline', - 'env.is_literal' => 'Literal', - 'env.is_shown_once' => 'Shown Once', - 'env.is_required' => 'Required', - ]; - - public function refresh() - { - $this->env->refresh(); - $this->checkEnvs(); - } - public function mount() { + $this->syncData(); if ($this->env->getMorphClass() === \App\Models\SharedEnvironmentVariable::class) { $this->isSharedVariable = true; } - $this->modalId = new Cuid2; $this->parameters = get_route_parameters(); $this->checkEnvs(); + + } + + public function refresh() + { + $this->syncData(); + $this->checkEnvs(); + } + + public function syncData(bool $toModel = false) + { + if ($toModel) { + $this->validate(); + $this->env->key = $this->key; + $this->env->value = $this->value; + $this->env->is_build_time = $this->is_build_time; + $this->env->is_multiline = $this->is_multiline; + $this->env->is_literal = $this->is_literal; + $this->env->is_shown_once = $this->is_shown_once; + $this->env->is_required = $this->is_required; + $this->env->is_shared = $this->is_shared; + $this->env->save(); + } else { + + $this->key = $this->env->key; + $this->value = $this->env->value; + $this->is_build_time = $this->env->is_build_time ?? false; + $this->is_multiline = $this->env->is_multiline; + $this->is_literal = $this->env->is_literal; + $this->is_shown_once = $this->env->is_shown_once; + $this->is_required = $this->env->is_required ?? false; + $this->is_really_required = $this->env->is_really_required ?? false; + $this->is_shared = $this->env->is_shared ?? false; + $this->real_value = $this->env->real_value; + } } public function checkEnvs() @@ -107,17 +143,17 @@ class Show extends Component try { if ($this->isSharedVariable) { $this->validate([ - 'env.key' => 'required|string', - 'env.value' => 'nullable', - 'env.is_shown_once' => 'required|boolean', + 'key' => 'required|string', + 'value' => 'nullable', + 'is_shown_once' => 'required|boolean', ]); } else { $this->validate(); } - if (! $this->isSharedVariable && $this->env->is_required && str($this->env->real_value)->isEmpty()) { + if (! $this->isSharedVariable && $this->is_required && str($this->value)->isEmpty()) { $oldValue = $this->env->getOriginal('value'); - $this->env->value = $oldValue; + $this->value = $oldValue; $this->dispatch('error', 'Required environment variable cannot be empty.'); return; @@ -126,10 +162,10 @@ class Show extends Component $this->serialize(); if ($this->isSharedVariable) { - unset($this->env->is_required); + unset($this->is_required); } - $this->env->save(); + $this->syncData(true); $this->dispatch('success', 'Environment variable updated.'); $this->dispatch('envsUpdated'); } catch (\Exception $e) { diff --git a/app/Livewire/Project/Shared/ResourceOperations.php b/app/Livewire/Project/Shared/ResourceOperations.php index e67df6aa9..c6a32f0c7 100644 --- a/app/Livewire/Project/Shared/ResourceOperations.php +++ b/app/Livewire/Project/Shared/ResourceOperations.php @@ -15,7 +15,7 @@ class ResourceOperations extends Component public $projectUuid; - public $environmentName; + public $environmentUuid; public $projects; @@ -25,7 +25,7 @@ class ResourceOperations extends Component { $parameters = get_route_parameters(); $this->projectUuid = data_get($parameters, 'project_uuid'); - $this->environmentName = data_get($parameters, 'environment_name'); + $this->environmentUuid = data_get($parameters, 'environment_uuid'); $this->projects = Project::ownedByCurrentTeam()->get(); $this->servers = currentTeam()->servers; } @@ -42,9 +42,11 @@ class ResourceOperations extends Component $uuid = (string) new Cuid2; $server = $new_destination->server; if ($this->resource->getMorphClass() === \App\Models\Application::class) { + $name = 'clone-of-'.str($this->resource->name)->limit(20).'-'.$uuid; + $new_resource = $this->resource->replicate()->fill([ 'uuid' => $uuid, - 'name' => $this->resource->name.'-clone-'.$uuid, + 'name' => $name, 'fqdn' => generateFqdn($server, $uuid), 'status' => 'exited', 'destination_id' => $new_destination->id, @@ -58,21 +60,26 @@ class ResourceOperations extends Component $environmentVaribles = $this->resource->environment_variables()->get(); foreach ($environmentVaribles as $environmentVarible) { $newEnvironmentVariable = $environmentVarible->replicate()->fill([ - 'application_id' => $new_resource->id, + 'resourceable_id' => $new_resource->id, + 'resourceable_type' => $new_resource->getMorphClass(), ]); $newEnvironmentVariable->save(); } $persistentVolumes = $this->resource->persistentStorages()->get(); foreach ($persistentVolumes as $volume) { + $volumeName = str($volume->name)->replace($this->resource->uuid, $new_resource->uuid)->value(); + if ($volumeName === $volume->name) { + $volumeName = $new_resource->uuid.'-'.str($volume->name)->afterLast('-'); + } $newPersistentVolume = $volume->replicate()->fill([ - 'name' => $new_resource->uuid.'-'.str($volume->name)->afterLast('-'), + 'name' => $volumeName, 'resource_id' => $new_resource->id, ]); $newPersistentVolume->save(); } $route = route('project.application.configuration', [ 'project_uuid' => $this->projectUuid, - 'environment_name' => $this->environmentName, + 'environment_uuid' => $this->environmentUuid, 'application_uuid' => $new_resource->uuid, ]).'#resource-operations'; @@ -115,7 +122,7 @@ class ResourceOperations extends Component } $route = route('project.database.configuration', [ 'project_uuid' => $this->projectUuid, - 'environment_name' => $this->environmentName, + 'environment_uuid' => $this->environmentUuid, 'database_uuid' => $new_resource->uuid, ]).'#resource-operations'; @@ -141,7 +148,7 @@ class ResourceOperations extends Component $new_resource->parse(); $route = route('project.service.configuration', [ 'project_uuid' => $this->projectUuid, - 'environment_name' => $this->environmentName, + 'environment_uuid' => $this->environmentUuid, 'service_uuid' => $new_resource->uuid, ]).'#resource-operations'; @@ -159,7 +166,7 @@ class ResourceOperations extends Component if ($this->resource->type() === 'application') { $route = route('project.application.configuration', [ 'project_uuid' => $new_environment->project->uuid, - 'environment_name' => $new_environment->name, + 'environment_uuid' => $new_environment->uuid, 'application_uuid' => $this->resource->uuid, ]).'#resource-operations'; @@ -167,7 +174,7 @@ class ResourceOperations extends Component } elseif (str($this->resource->type())->startsWith('standalone-')) { $route = route('project.database.configuration', [ 'project_uuid' => $new_environment->project->uuid, - 'environment_name' => $new_environment->name, + 'environment_uuid' => $new_environment->uuid, 'database_uuid' => $this->resource->uuid, ]).'#resource-operations'; @@ -175,7 +182,7 @@ class ResourceOperations extends Component } elseif ($this->resource->type() === 'service') { $route = route('project.service.configuration', [ 'project_uuid' => $new_environment->project->uuid, - 'environment_name' => $new_environment->name, + 'environment_uuid' => $new_environment->uuid, 'service_uuid' => $this->resource->uuid, ]).'#resource-operations'; diff --git a/app/Livewire/Project/Shared/ScheduledTask/Show.php b/app/Livewire/Project/Shared/ScheduledTask/Show.php index 0900a1d70..0764ab3b9 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/Show.php +++ b/app/Livewire/Project/Shared/ScheduledTask/Show.php @@ -46,7 +46,7 @@ class Show extends Component #[Locked] public string $task_uuid; - public function mount(string $task_uuid, string $project_uuid, string $environment_name, ?string $application_uuid = null, ?string $service_uuid = null) + public function mount(string $task_uuid, string $project_uuid, string $environment_uuid, ?string $application_uuid = null, ?string $service_uuid = null) { try { $this->task_uuid = $task_uuid; @@ -60,7 +60,7 @@ class Show extends Component $this->resource = Service::ownedByCurrentTeam()->where('uuid', $service_uuid)->firstOrFail(); } $this->parameters = [ - 'environment_name' => $environment_name, + 'environment_uuid' => $environment_uuid, 'project_uuid' => $project_uuid, 'application_uuid' => $application_uuid, 'service_uuid' => $service_uuid, diff --git a/app/Livewire/Project/Show.php b/app/Livewire/Project/Show.php index 2335519c7..886a20218 100644 --- a/app/Livewire/Project/Show.php +++ b/app/Livewire/Project/Show.php @@ -6,6 +6,7 @@ use App\Models\Environment; use App\Models\Project; use Livewire\Attributes\Validate; use Livewire\Component; +use Visus\Cuid2\Cuid2; class Show extends Component { @@ -33,17 +34,26 @@ class Show extends Component $environment = Environment::create([ 'name' => $this->name, 'project_id' => $this->project->id, + 'uuid' => (string) new Cuid2, ]); return redirect()->route('project.resource.index', [ 'project_uuid' => $this->project->uuid, - 'environment_name' => $environment->name, + 'environment_uuid' => $environment->uuid, ]); } catch (\Throwable $e) { handleError($e, $this); } } + public function navigateToEnvironment($projectUuid, $environmentUuid) + { + return redirect()->route('project.resource.index', [ + 'project_uuid' => $projectUuid, + 'environment_uuid' => $environmentUuid, + ]); + } + public function render() { return view('livewire.project.show'); diff --git a/app/Livewire/Server/Advanced.php b/app/Livewire/Server/Advanced.php index 0650de9a0..577730f24 100644 --- a/app/Livewire/Server/Advanced.php +++ b/app/Livewire/Server/Advanced.php @@ -13,14 +13,11 @@ class Advanced extends Component public array $parameters = []; - #[Validate(['integer', 'min:1'])] - public int $concurrentBuilds = 1; + #[Validate(['string'])] + public string $serverDiskUsageCheckFrequency = '0 23 * * *'; - #[Validate(['integer', 'min:1'])] - public int $dynamicTimeout = 1; - - #[Validate('boolean')] - public bool $forceDockerCleanup = false; + #[Validate(['integer', 'min:1', 'max:99'])] + public int $serverDiskUsageNotificationThreshold = 50; #[Validate(['string', 'required'])] public string $dockerCleanupFrequency = '*/10 * * * *'; @@ -28,8 +25,8 @@ class Advanced extends Component #[Validate(['integer', 'min:1', 'max:99'])] public int $dockerCleanupThreshold = 10; - #[Validate(['integer', 'min:1', 'max:99'])] - public int $serverDiskUsageNotificationThreshold = 50; + #[Validate('boolean')] + public bool $forceDockerCleanup = false; #[Validate('boolean')] public bool $deleteUnusedVolumes = false; @@ -37,6 +34,12 @@ class Advanced extends Component #[Validate('boolean')] public bool $deleteUnusedNetworks = false; + #[Validate(['integer', 'min:1'])] + public int $concurrentBuilds = 1; + + #[Validate(['integer', 'min:1'])] + public int $dynamicTimeout = 1; + public function mount(string $server_uuid) { try { @@ -60,6 +63,7 @@ class Advanced extends Component $this->server->settings->server_disk_usage_notification_threshold = $this->serverDiskUsageNotificationThreshold; $this->server->settings->delete_unused_volumes = $this->deleteUnusedVolumes; $this->server->settings->delete_unused_networks = $this->deleteUnusedNetworks; + $this->server->settings->server_disk_usage_check_frequency = $this->serverDiskUsageCheckFrequency; $this->server->settings->save(); } else { $this->concurrentBuilds = $this->server->settings->concurrent_builds; @@ -70,6 +74,7 @@ class Advanced extends Component $this->serverDiskUsageNotificationThreshold = $this->server->settings->server_disk_usage_notification_threshold; $this->deleteUnusedVolumes = $this->server->settings->delete_unused_volumes; $this->deleteUnusedNetworks = $this->server->settings->delete_unused_networks; + $this->serverDiskUsageCheckFrequency = $this->server->settings->server_disk_usage_check_frequency; } } @@ -100,6 +105,10 @@ class Advanced extends Component $this->dockerCleanupFrequency = $this->server->settings->getOriginal('docker_cleanup_frequency'); throw new \Exception('Invalid Cron / Human expression for Docker Cleanup Frequency.'); } + if (! validate_cron_expression($this->serverDiskUsageCheckFrequency)) { + $this->serverDiskUsageCheckFrequency = $this->server->settings->getOriginal('server_disk_usage_check_frequency'); + throw new \Exception('Invalid Cron / Human expression for Disk Usage Check Frequency.'); + } $this->syncData(true); $this->dispatch('success', 'Server updated.'); } catch (\Throwable $e) { diff --git a/app/Livewire/Server/Show.php b/app/Livewire/Server/Show.php index ac5211c1b..6d267b9c8 100644 --- a/app/Livewire/Server/Show.php +++ b/app/Livewire/Server/Show.php @@ -4,6 +4,7 @@ namespace App\Livewire\Server; use App\Actions\Server\StartSentinel; use App\Actions\Server\StopSentinel; +use App\Events\ServerReachabilityChanged; use App\Models\Server; use Livewire\Attributes\Computed; use Livewire\Attributes\Validate; @@ -202,6 +203,7 @@ class Show extends Component $this->server->settings->is_reachable = $this->isReachable = true; $this->server->settings->is_usable = $this->isUsable = true; $this->server->settings->save(); + ServerReachabilityChanged::dispatch($this->server); $this->dispatch('proxyStatusUpdated'); } else { $this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.

Check this documentation for further help.

Error: '.$error); diff --git a/app/Livewire/SharedVariables/Environment/Show.php b/app/Livewire/SharedVariables/Environment/Show.php index 6a33eb60d..e88ac5f13 100644 --- a/app/Livewire/SharedVariables/Environment/Show.php +++ b/app/Livewire/SharedVariables/Environment/Show.php @@ -42,8 +42,8 @@ class Show extends Component public function mount() { $this->parameters = get_route_parameters(); - $this->project = Project::ownedByCurrentTeam()->where('uuid', request()->route('project_uuid'))->first(); - $this->environment = $this->project->environments()->where('name', request()->route('environment_name'))->first(); + $this->project = Project::ownedByCurrentTeam()->where('uuid', request()->route('project_uuid'))->firstOrFail(); + $this->environment = $this->project->environments()->where('uuid', request()->route('environment_uuid'))->firstOrFail(); } public function render() diff --git a/app/Livewire/Source/Github/Change.php b/app/Livewire/Source/Github/Change.php index 467927484..8f4f02f70 100644 --- a/app/Livewire/Source/Github/Change.php +++ b/app/Livewire/Source/Github/Change.php @@ -130,14 +130,14 @@ class Change extends Component } else { $parameters = data_get(session('from'), 'parameters'); $back = data_get(session('from'), 'back'); - $environment_name = data_get($parameters, 'environment_name'); + $environment_uuid = data_get($parameters, 'environment_uuid'); $project_uuid = data_get($parameters, 'project_uuid'); $type = data_get($parameters, 'type'); $destination = data_get($parameters, 'destination'); session()->forget('from'); return redirect()->route($back, [ - 'environment_name' => $environment_name, + 'environment_uuid' => $environment_uuid, 'project_uuid' => $project_uuid, 'type' => $type, 'destination' => $destination, diff --git a/app/Models/Application.php b/app/Models/Application.php index bfb2a1041..13f15468d 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -325,7 +325,7 @@ class Application extends BaseModel if (data_get($this, 'environment.project.uuid')) { return route('project.application.configuration', [ 'project_uuid' => data_get($this, 'environment.project.uuid'), - 'environment_name' => data_get($this, 'environment.name'), + 'environment_uuid' => data_get($this, 'environment.uuid'), 'application_uuid' => data_get($this, 'uuid'), ]); } @@ -338,7 +338,7 @@ class Application extends BaseModel if (data_get($this, 'environment.project.uuid')) { $route = route('project.application.scheduled-tasks', [ 'project_uuid' => data_get($this, 'environment.project.uuid'), - 'environment_name' => data_get($this, 'environment.name'), + 'environment_uuid' => data_get($this, 'environment.uuid'), 'application_uuid' => data_get($this, 'uuid'), 'task_uuid' => $task_uuid, ]); @@ -695,46 +695,62 @@ class Application extends BaseModel return $this->settings->is_static ? [80] : $this->ports_exposes_array; } - public function environment_variables(): HasMany + public function environment_variables() { - return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false)->orderBy('key', 'asc'); + return $this->morphMany(EnvironmentVariable::class, 'resourceable') + ->where('is_preview', false) + ->orderBy('key', 'asc'); } - public function runtime_environment_variables(): HasMany + public function runtime_environment_variables() { - return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false)->where('key', 'not like', 'NIXPACKS_%'); + return $this->morphMany(EnvironmentVariable::class, 'resourceable') + ->where('is_preview', false) + ->where('key', 'not like', 'NIXPACKS_%'); } - // Preview Deployments - - public function build_environment_variables(): HasMany + public function build_environment_variables() { - return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false)->where('is_build_time', true)->where('key', 'not like', 'NIXPACKS_%'); + return $this->morphMany(EnvironmentVariable::class, 'resourceable') + ->where('is_preview', false) + ->where('is_build_time', true) + ->where('key', 'not like', 'NIXPACKS_%'); } - public function nixpacks_environment_variables(): HasMany + public function nixpacks_environment_variables() { - return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false)->where('key', 'like', 'NIXPACKS_%'); + return $this->morphMany(EnvironmentVariable::class, 'resourceable') + ->where('is_preview', false) + ->where('key', 'like', 'NIXPACKS_%'); } - public function environment_variables_preview(): HasMany + public function environment_variables_preview() { - return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->orderBy('key', 'asc'); + return $this->morphMany(EnvironmentVariable::class, 'resourceable') + ->where('is_preview', true) + ->orderByRaw("LOWER(key) LIKE LOWER('SERVICE%') DESC, LOWER(key) ASC"); } - public function runtime_environment_variables_preview(): HasMany + public function runtime_environment_variables_preview() { - return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->where('key', 'not like', 'NIXPACKS_%'); + return $this->morphMany(EnvironmentVariable::class, 'resourceable') + ->where('is_preview', true) + ->where('key', 'not like', 'NIXPACKS_%'); } - public function build_environment_variables_preview(): HasMany + public function build_environment_variables_preview() { - return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->where('is_build_time', true)->where('key', 'not like', 'NIXPACKS_%'); + return $this->morphMany(EnvironmentVariable::class, 'resourceable') + ->where('is_preview', true) + ->where('is_build_time', true) + ->where('key', 'not like', 'NIXPACKS_%'); } - public function nixpacks_environment_variables_preview(): HasMany + public function nixpacks_environment_variables_preview() { - return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->where('key', 'like', 'NIXPACKS_%'); + return $this->morphMany(EnvironmentVariable::class, 'resourceable') + ->where('is_preview', true) + ->where('key', 'like', 'NIXPACKS_%'); } public function scheduled_tasks(): HasMany diff --git a/app/Models/Environment.php b/app/Models/Environment.php index 71e8bbd21..b8f1090d8 100644 --- a/app/Models/Environment.php +++ b/app/Models/Environment.php @@ -3,7 +3,6 @@ namespace App\Models; use Illuminate\Database\Eloquent\Casts\Attribute; -use Illuminate\Database\Eloquent\Model; use OpenApi\Attributes as OA; #[OA\Schema( @@ -18,7 +17,7 @@ use OpenApi\Attributes as OA; 'description' => ['type' => 'string'], ] )] -class Environment extends Model +class Environment extends BaseModel { protected $guarded = []; diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php index 96c57e63e..507ff0d7e 100644 --- a/app/Models/EnvironmentVariable.php +++ b/app/Models/EnvironmentVariable.php @@ -14,9 +14,8 @@ use Visus\Cuid2\Cuid2; properties: [ 'id' => ['type' => 'integer'], 'uuid' => ['type' => 'string'], - 'application_id' => ['type' => 'integer'], - 'service_id' => ['type' => 'integer'], - 'database_id' => ['type' => 'integer'], + 'resourceable_type' => ['type' => 'string'], + 'resourceable_id' => ['type' => 'integer'], 'is_build_time' => ['type' => 'boolean'], 'is_literal' => ['type' => 'boolean'], 'is_multiline' => ['type' => 'boolean'], @@ -42,6 +41,8 @@ class EnvironmentVariable extends Model 'is_multiline' => 'boolean', 'is_preview' => 'boolean', 'version' => 'string', + 'resourceable_type' => 'string', + 'resourceable_id' => 'integer', ]; protected $appends = ['real_value', 'is_shared', 'is_really_required']; @@ -53,18 +54,25 @@ class EnvironmentVariable extends Model $model->uuid = (string) new Cuid2; } }); + static::created(function (EnvironmentVariable $environment_variable) { - if ($environment_variable->application_id && ! $environment_variable->is_preview) { - $found = ModelsEnvironmentVariable::where('key', $environment_variable->key)->where('application_id', $environment_variable->application_id)->where('is_preview', true)->first(); + if ($environment_variable->resourceable_type === Application::class && ! $environment_variable->is_preview) { + $found = ModelsEnvironmentVariable::where('key', $environment_variable->key) + ->where('resourceable_type', Application::class) + ->where('resourceable_id', $environment_variable->resourceable_id) + ->where('is_preview', true) + ->first(); + if (! $found) { - $application = Application::find($environment_variable->application_id); - if ($application->build_pack !== 'dockerfile') { + $application = Application::find($environment_variable->resourceable_id); + if ($application && $application->build_pack !== 'dockerfile') { ModelsEnvironmentVariable::create([ 'key' => $environment_variable->key, 'value' => $environment_variable->value, 'is_build_time' => $environment_variable->is_build_time, 'is_multiline' => $environment_variable->is_multiline ?? false, - 'application_id' => $environment_variable->application_id, + 'resourceable_type' => Application::class, + 'resourceable_id' => $environment_variable->resourceable_id, 'is_preview' => true, ]); } @@ -74,6 +82,7 @@ class EnvironmentVariable extends Model 'version' => config('constants.coolify.version'), ]); }); + static::saving(function (EnvironmentVariable $environmentVariable) { $environmentVariable->updateIsShared(); }); @@ -92,43 +101,32 @@ class EnvironmentVariable extends Model ); } + /** + * Get the parent resourceable model. + */ + public function resourceable() + { + return $this->morphTo(); + } + public function resource() { - $resource = null; - if ($this->application_id) { - $resource = Application::find($this->application_id); - } elseif ($this->service_id) { - $resource = Service::find($this->service_id); - } elseif ($this->standalone_postgresql_id) { - $resource = StandalonePostgresql::find($this->standalone_postgresql_id); - } elseif ($this->standalone_redis_id) { - $resource = StandaloneRedis::find($this->standalone_redis_id); - } elseif ($this->standalone_mongodb_id) { - $resource = StandaloneMongodb::find($this->standalone_mongodb_id); - } elseif ($this->standalone_mysql_id) { - $resource = StandaloneMysql::find($this->standalone_mysql_id); - } elseif ($this->standalone_mariadb_id) { - $resource = StandaloneMariadb::find($this->standalone_mariadb_id); - } elseif ($this->standalone_keydb_id) { - $resource = StandaloneKeydb::find($this->standalone_keydb_id); - } elseif ($this->standalone_dragonfly_id) { - $resource = StandaloneDragonfly::find($this->standalone_dragonfly_id); - } elseif ($this->standalone_clickhouse_id) { - $resource = StandaloneClickhouse::find($this->standalone_clickhouse_id); - } - - return $resource; + return $this->resourceable; } public function realValue(): Attribute { - $resource = $this->resource(); - return Attribute::make( - get: function () use ($resource) { - $env = $this->get_real_environment_variables($this->value, $resource); + get: function () { + if (! $this->relationLoaded('resourceable')) { + $this->load('resourceable'); + } + $resource = $this->resourceable; + if (! $resource) { + return null; + } - return data_get($env, 'value', $env); + return $this->get_real_environment_variables($this->value, $resource); } ); } @@ -164,7 +162,6 @@ class EnvironmentVariable extends Model if ($sharedEnvsFound->isEmpty()) { return $environment_variable; } - foreach ($sharedEnvsFound as $sharedEnv) { $type = str($sharedEnv)->match('/(.*?)\./'); if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) { diff --git a/app/Models/Project.php b/app/Models/Project.php index f27e6c208..3b50b9b33 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -3,6 +3,7 @@ namespace App\Models; use OpenApi\Attributes as OA; +use Visus\Cuid2\Cuid2; #[OA\Schema( description: 'Project model', @@ -24,8 +25,6 @@ class Project extends BaseModel { protected $guarded = []; - protected $appends = ['default_environment']; - public static function ownedByCurrentTeam() { return Project::whereTeamId(currentTeam()->id)->orderByRaw('LOWER(name)'); @@ -40,6 +39,7 @@ class Project extends BaseModel Environment::create([ 'name' => 'production', 'project_id' => $project->id, + 'uuid' => (string) new Cuid2, ]); }); static::deleting(function ($project) { @@ -140,18 +140,4 @@ class Project extends BaseModel { return $this->postgresqls()->get()->merge($this->redis()->get())->merge($this->mongodbs()->get())->merge($this->mysqls()->get())->merge($this->mariadbs()->get())->merge($this->keydbs()->get())->merge($this->dragonflies()->get())->merge($this->clickhouses()->get()); } - - public function getDefaultEnvironmentAttribute() - { - $default = $this->environments()->where('name', 'production')->first(); - if ($default) { - return $default->name; - } - $default = $this->environments()->get(); - if ($default->count() > 0) { - return $default->sortBy('created_at')->first()->name; - } - - return null; - } } diff --git a/app/Models/Server.php b/app/Models/Server.php index cc8211789..767327b8e 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -6,6 +6,7 @@ use App\Actions\Proxy\StartProxy; use App\Actions\Server\InstallDocker; use App\Actions\Server\StartSentinel; use App\Enums\ProxyTypes; +use App\Events\ServerReachabilityChanged; use App\Jobs\CheckAndStartSentinelJob; use App\Notifications\Server\Reachable; use App\Notifications\Server\Unreachable; @@ -1024,14 +1025,63 @@ $schema://$host { $this->refresh(); $unreachableNotificationSent = (bool) $this->unreachable_notification_sent; $isReachable = (bool) $this->settings->is_reachable; - // If the server is reachable, send the reachable notification if it was sent before + + \Log::debug('Server reachability check', [ + 'server_id' => $this->id, + 'is_reachable' => $isReachable, + 'notification_sent' => $unreachableNotificationSent, + 'unreachable_count' => $this->unreachable_count, + ]); + if ($isReachable === true) { + $this->unreachable_count = 0; + $this->save(); + if ($unreachableNotificationSent === true) { + \Log::debug('Server is now reachable, sending notification', [ + 'server_id' => $this->id, + ]); $this->sendReachableNotification(); } - } else { - // If the server is unreachable, send the unreachable notification if it was not sent before - if ($unreachableNotificationSent === false) { + + return; + } + + $this->increment('unreachable_count'); + \Log::debug('Incremented unreachable count', [ + 'server_id' => $this->id, + 'new_count' => $this->unreachable_count, + ]); + + if ($this->unreachable_count === 1) { + $this->settings->is_reachable = true; + $this->settings->save(); + \Log::debug('First unreachable attempt, marking as reachable', [ + 'server_id' => $this->id, + ]); + + return; + } + + if ($this->unreachable_count >= 2 && ! $unreachableNotificationSent) { + $failedChecks = 0; + for ($i = 0; $i < 3; $i++) { + $status = $this->serverStatus(); + \Log::debug('Additional reachability check', [ + 'server_id' => $this->id, + 'attempt' => $i + 1, + 'status' => $status, + ]); + sleep(5); + if (! $status) { + $failedChecks++; + } + } + + if ($failedChecks === 3 && ! $unreachableNotificationSent) { + \Log::debug('Server confirmed unreachable after 3 attempts, sending notification', [ + 'server_id' => $this->id, + ]); $this->sendUnreachableNotification(); } } @@ -1065,6 +1115,7 @@ $schema://$host { if ($this->settings->is_reachable === false) { $this->settings->is_reachable = true; $this->settings->save(); + ServerReachabilityChanged::dispatch($this); } return ['uptime' => true, 'error' => null]; @@ -1075,6 +1126,7 @@ $schema://$host { if ($this->settings->is_reachable === true) { $this->settings->is_reachable = false; $this->settings->save(); + ServerReachabilityChanged::dispatch($this); } return ['uptime' => false, 'error' => $e->getMessage()]; @@ -1165,6 +1217,7 @@ $schema://$host { $this->settings->is_reachable = true; $this->settings->is_usable = true; $this->settings->save(); + ServerReachabilityChanged::dispatch($this); return true; } diff --git a/app/Models/ServerSetting.php b/app/Models/ServerSetting.php index e078372e2..f4b776cca 100644 --- a/app/Models/ServerSetting.php +++ b/app/Models/ServerSetting.php @@ -85,9 +85,6 @@ class ServerSetting extends Model ) { $settings->server->restartSentinel(); } - if ($settings->isDirty('is_reachable')) { - $settings->server->isReachableChanged(); - } }); } diff --git a/app/Models/Service.php b/app/Models/Service.php index 5a2690490..1df579802 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -1120,7 +1120,8 @@ class Service extends BaseModel 'key' => $key, 'value' => $value, 'is_build_time' => false, - 'service_id' => $this->id, + 'resourceable_id' => $this->id, + 'resourceable_type' => $this->getMorphClass(), 'is_preview' => false, ]); } @@ -1132,7 +1133,7 @@ class Service extends BaseModel if (data_get($this, 'environment.project.uuid')) { return route('project.service.configuration', [ 'project_uuid' => data_get($this, 'environment.project.uuid'), - 'environment_name' => data_get($this, 'environment.name'), + 'environment_uuid' => data_get($this, 'environment.uuid'), 'service_uuid' => data_get($this, 'uuid'), ]); } @@ -1145,7 +1146,7 @@ class Service extends BaseModel if (data_get($this, 'environment.project.uuid')) { $route = route('project.service.scheduled-tasks', [ 'project_uuid' => data_get($this, 'environment.project.uuid'), - 'environment_name' => data_get($this, 'environment.name'), + 'environment_uuid' => data_get($this, 'environment.uuid'), 'service_uuid' => data_get($this, 'uuid'), 'task_uuid' => $task_uuid, ]); @@ -1232,14 +1233,17 @@ class Service extends BaseModel return $this->hasMany(ScheduledTask::class)->orderBy('name', 'asc'); } - public function environment_variables(): HasMany + public function environment_variables() { - return $this->hasMany(EnvironmentVariable::class)->orderByRaw("LOWER(key) LIKE LOWER('SERVICE%') DESC, LOWER(key) ASC"); + return $this->morphMany(EnvironmentVariable::class, 'resourceable') + ->orderBy('key', 'asc'); } - public function environment_variables_preview(): HasMany + public function environment_variables_preview() { - return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->orderByRaw("LOWER(key) LIKE LOWER('SERVICE%') DESC, LOWER(key) ASC"); + return $this->morphMany(EnvironmentVariable::class, 'resourceable') + ->where('is_preview', true) + ->orderByRaw("LOWER(key) LIKE LOWER('SERVICE%') DESC, LOWER(key) ASC"); } public function workdir() diff --git a/app/Models/StandaloneClickhouse.php b/app/Models/StandaloneClickhouse.php index 6d66c6854..60198115d 100644 --- a/app/Models/StandaloneClickhouse.php +++ b/app/Models/StandaloneClickhouse.php @@ -5,7 +5,6 @@ namespace App\Models; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; class StandaloneClickhouse extends BaseModel @@ -169,7 +168,7 @@ class StandaloneClickhouse extends BaseModel if (data_get($this, 'environment.project.uuid')) { return route('project.database.configuration', [ 'project_uuid' => data_get($this, 'environment.project.uuid'), - 'environment_name' => data_get($this, 'environment.name'), + 'environment_uuid' => data_get($this, 'environment.uuid'), 'database_uuid' => data_get($this, 'uuid'), ]); } @@ -251,14 +250,15 @@ class StandaloneClickhouse extends BaseModel return $this->morphTo(); } - public function environment_variables(): HasMany + public function environment_variables() { - return $this->hasMany(EnvironmentVariable::class); + return $this->morphMany(EnvironmentVariable::class, 'resourceable') + ->orderBy('key', 'asc'); } - public function runtime_environment_variables(): HasMany + public function runtime_environment_variables() { - return $this->hasMany(EnvironmentVariable::class); + return $this->morphMany(EnvironmentVariable::class, 'resourceable'); } public function persistentStorages() diff --git a/app/Models/StandaloneDragonfly.php b/app/Models/StandaloneDragonfly.php index f7d83f0a3..3c1127d8d 100644 --- a/app/Models/StandaloneDragonfly.php +++ b/app/Models/StandaloneDragonfly.php @@ -5,7 +5,6 @@ namespace App\Models; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; class StandaloneDragonfly extends BaseModel @@ -174,7 +173,7 @@ class StandaloneDragonfly extends BaseModel if (data_get($this, 'environment.project.uuid')) { return route('project.database.configuration', [ 'project_uuid' => data_get($this, 'environment.project.uuid'), - 'environment_name' => data_get($this, 'environment.name'), + 'environment_uuid' => data_get($this, 'environment.uuid'), 'database_uuid' => data_get($this, 'uuid'), ]); } @@ -251,14 +250,9 @@ class StandaloneDragonfly extends BaseModel return $this->morphTo(); } - public function environment_variables(): HasMany + public function runtime_environment_variables() { - return $this->hasMany(EnvironmentVariable::class); - } - - public function runtime_environment_variables(): HasMany - { - return $this->hasMany(EnvironmentVariable::class); + return $this->morphMany(EnvironmentVariable::class, 'resourceable'); } public function persistentStorages() @@ -319,4 +313,10 @@ class StandaloneDragonfly extends BaseModel { return false; } + + public function environment_variables() + { + return $this->morphMany(EnvironmentVariable::class, 'resourceable') + ->orderBy('key', 'asc'); + } } diff --git a/app/Models/StandaloneKeydb.php b/app/Models/StandaloneKeydb.php index 083c743d9..ebf1c22e9 100644 --- a/app/Models/StandaloneKeydb.php +++ b/app/Models/StandaloneKeydb.php @@ -5,7 +5,6 @@ namespace App\Models; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; class StandaloneKeydb extends BaseModel @@ -174,7 +173,7 @@ class StandaloneKeydb extends BaseModel if (data_get($this, 'environment.project.uuid')) { return route('project.database.configuration', [ 'project_uuid' => data_get($this, 'environment.project.uuid'), - 'environment_name' => data_get($this, 'environment.name'), + 'environment_uuid' => data_get($this, 'environment.uuid'), 'database_uuid' => data_get($this, 'uuid'), ]); } @@ -251,14 +250,9 @@ class StandaloneKeydb extends BaseModel return $this->morphTo(); } - public function environment_variables(): HasMany + public function runtime_environment_variables() { - return $this->hasMany(EnvironmentVariable::class); - } - - public function runtime_environment_variables(): HasMany - { - return $this->hasMany(EnvironmentVariable::class); + return $this->morphMany(EnvironmentVariable::class, 'resourceable'); } public function persistentStorages() @@ -319,4 +313,10 @@ class StandaloneKeydb extends BaseModel { return false; } + + public function environment_variables() + { + return $this->morphMany(EnvironmentVariable::class, 'resourceable') + ->orderBy('key', 'asc'); + } } diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php index 833dad6c4..004ead4d9 100644 --- a/app/Models/StandaloneMariadb.php +++ b/app/Models/StandaloneMariadb.php @@ -5,7 +5,6 @@ namespace App\Models; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; class StandaloneMariadb extends BaseModel @@ -174,7 +173,7 @@ class StandaloneMariadb extends BaseModel if (data_get($this, 'environment.project.uuid')) { return route('project.database.configuration', [ 'project_uuid' => data_get($this, 'environment.project.uuid'), - 'environment_name' => data_get($this, 'environment.name'), + 'environment_uuid' => data_get($this, 'environment.uuid'), 'database_uuid' => data_get($this, 'uuid'), ]); } @@ -251,14 +250,15 @@ class StandaloneMariadb extends BaseModel return $this->morphTo(); } - public function environment_variables(): HasMany + public function environment_variables() { - return $this->hasMany(EnvironmentVariable::class); + return $this->morphMany(EnvironmentVariable::class, 'resourceable') + ->orderBy('key', 'asc'); } - public function runtime_environment_variables(): HasMany + public function runtime_environment_variables() { - return $this->hasMany(EnvironmentVariable::class); + return $this->morphMany(EnvironmentVariable::class, 'resourceable'); } public function persistentStorages() diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php index dd8893180..aba0f6123 100644 --- a/app/Models/StandaloneMongodb.php +++ b/app/Models/StandaloneMongodb.php @@ -5,7 +5,6 @@ namespace App\Models; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; class StandaloneMongodb extends BaseModel @@ -183,7 +182,7 @@ class StandaloneMongodb extends BaseModel if (data_get($this, 'environment.project.uuid')) { return route('project.database.configuration', [ 'project_uuid' => data_get($this, 'environment.project.uuid'), - 'environment_name' => data_get($this, 'environment.name'), + 'environment_uuid' => data_get($this, 'environment.uuid'), 'database_uuid' => data_get($this, 'uuid'), ]); } @@ -271,14 +270,9 @@ class StandaloneMongodb extends BaseModel return $this->morphTo(); } - public function environment_variables(): HasMany + public function runtime_environment_variables() { - return $this->hasMany(EnvironmentVariable::class); - } - - public function runtime_environment_variables(): HasMany - { - return $this->hasMany(EnvironmentVariable::class); + return $this->morphMany(EnvironmentVariable::class, 'resourceable'); } public function persistentStorages() @@ -339,4 +333,10 @@ class StandaloneMongodb extends BaseModel { return true; } + + public function environment_variables() + { + return $this->morphMany(EnvironmentVariable::class, 'resourceable') + ->orderBy('key', 'asc'); + } } diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php index 710fea1bc..9ae0fdcae 100644 --- a/app/Models/StandaloneMysql.php +++ b/app/Models/StandaloneMysql.php @@ -5,7 +5,6 @@ namespace App\Models; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; class StandaloneMysql extends BaseModel @@ -175,7 +174,7 @@ class StandaloneMysql extends BaseModel if (data_get($this, 'environment.project.uuid')) { return route('project.database.configuration', [ 'project_uuid' => data_get($this, 'environment.project.uuid'), - 'environment_name' => data_get($this, 'environment.name'), + 'environment_uuid' => data_get($this, 'environment.uuid'), 'database_uuid' => data_get($this, 'uuid'), ]); } @@ -252,14 +251,9 @@ class StandaloneMysql extends BaseModel return $this->morphTo(); } - public function environment_variables(): HasMany + public function runtime_environment_variables() { - return $this->hasMany(EnvironmentVariable::class); - } - - public function runtime_environment_variables(): HasMany - { - return $this->hasMany(EnvironmentVariable::class); + return $this->morphMany(EnvironmentVariable::class, 'resourceable'); } public function persistentStorages() @@ -320,4 +314,10 @@ class StandaloneMysql extends BaseModel { return true; } + + public function environment_variables() + { + return $this->morphMany(EnvironmentVariable::class, 'resourceable') + ->orderBy('key', 'asc'); + } } diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php index 4a457a6cf..dd92ae7c9 100644 --- a/app/Models/StandalonePostgresql.php +++ b/app/Models/StandalonePostgresql.php @@ -5,7 +5,6 @@ namespace App\Models; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; class StandalonePostgresql extends BaseModel @@ -170,7 +169,7 @@ class StandalonePostgresql extends BaseModel if (data_get($this, 'environment.project.uuid')) { return route('project.database.configuration', [ 'project_uuid' => data_get($this, 'environment.project.uuid'), - 'environment_name' => data_get($this, 'environment.name'), + 'environment_uuid' => data_get($this, 'environment.uuid'), 'database_uuid' => data_get($this, 'uuid'), ]); } @@ -252,14 +251,9 @@ class StandalonePostgresql extends BaseModel return $this->morphTo(); } - public function environment_variables(): HasMany + public function runtime_environment_variables() { - return $this->hasMany(EnvironmentVariable::class); - } - - public function runtime_environment_variables(): HasMany - { - return $this->hasMany(EnvironmentVariable::class); + return $this->morphMany(EnvironmentVariable::class, 'resourceable'); } public function persistentStorages() @@ -320,4 +314,10 @@ class StandalonePostgresql extends BaseModel return $parsedCollection->toArray(); } + + public function environment_variables() + { + return $this->morphMany(EnvironmentVariable::class, 'resourceable') + ->orderBy('key', 'asc'); + } } diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php index 826bb951c..ed5cf9870 100644 --- a/app/Models/StandaloneRedis.php +++ b/app/Models/StandaloneRedis.php @@ -5,7 +5,6 @@ namespace App\Models; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; class StandaloneRedis extends BaseModel @@ -170,7 +169,7 @@ class StandaloneRedis extends BaseModel if (data_get($this, 'environment.project.uuid')) { return route('project.database.configuration', [ 'project_uuid' => data_get($this, 'environment.project.uuid'), - 'environment_name' => data_get($this, 'environment.name'), + 'environment_uuid' => data_get($this, 'environment.uuid'), 'database_uuid' => data_get($this, 'uuid'), ]); } @@ -262,14 +261,9 @@ class StandaloneRedis extends BaseModel return $this->morphTo(); } - public function environment_variables(): HasMany + public function runtime_environment_variables() { - return $this->hasMany(EnvironmentVariable::class); - } - - public function runtime_environment_variables(): HasMany - { - return $this->hasMany(EnvironmentVariable::class); + return $this->morphMany(EnvironmentVariable::class, 'resourceable'); } public function persistentStorages() @@ -359,4 +353,10 @@ class StandaloneRedis extends BaseModel } ); } + + public function environment_variables() + { + return $this->morphMany(EnvironmentVariable::class, 'resourceable') + ->orderBy('key', 'asc'); + } } diff --git a/app/Models/Team.php b/app/Models/Team.php index e55cb0d19..33847a3c8 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Events\ServerReachabilityChanged; use App\Notifications\Channels\SendsDiscord; use App\Notifications\Channels\SendsEmail; use App\Notifications\Channels\SendsPushover; @@ -202,6 +203,7 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, Sen 'is_usable' => false, 'is_reachable' => false, ]); + ServerReachabilityChanged::dispatch($server); } } diff --git a/app/Notifications/Application/DeploymentFailed.php b/app/Notifications/Application/DeploymentFailed.php index 80c1c421c..0c09b1dbd 100644 --- a/app/Notifications/Application/DeploymentFailed.php +++ b/app/Notifications/Application/DeploymentFailed.php @@ -22,6 +22,8 @@ class DeploymentFailed extends CustomEmailNotification public string $project_uuid; + public string $environment_uuid; + public string $environment_name; public ?string $deployment_url = null; @@ -36,12 +38,13 @@ class DeploymentFailed extends CustomEmailNotification $this->preview = $preview; $this->application_name = data_get($application, 'name'); $this->project_uuid = data_get($application, 'environment.project.uuid'); + $this->environment_uuid = data_get($application, 'environment.uuid'); $this->environment_name = data_get($application, 'environment.name'); $this->fqdn = data_get($application, 'fqdn'); if (str($this->fqdn)->explode(',')->count() > 1) { $this->fqdn = str($this->fqdn)->explode(',')->first(); } - $this->deployment_url = base_url()."/project/{$this->project_uuid}/".urlencode($this->environment_name)."/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}"; + $this->deployment_url = base_url()."/project/{$this->project_uuid}/environments/{$this->environment_uuid}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}"; } public function via(object $notifiable): array diff --git a/app/Notifications/Application/DeploymentSuccess.php b/app/Notifications/Application/DeploymentSuccess.php index b1a3d5225..e1067e9bc 100644 --- a/app/Notifications/Application/DeploymentSuccess.php +++ b/app/Notifications/Application/DeploymentSuccess.php @@ -22,6 +22,8 @@ class DeploymentSuccess extends CustomEmailNotification public string $project_uuid; + public string $environment_uuid; + public string $environment_name; public ?string $deployment_url = null; @@ -36,12 +38,13 @@ class DeploymentSuccess extends CustomEmailNotification $this->preview = $preview; $this->application_name = data_get($application, 'name'); $this->project_uuid = data_get($application, 'environment.project.uuid'); + $this->environment_uuid = data_get($application, 'environment.uuid'); $this->environment_name = data_get($application, 'environment.name'); $this->fqdn = data_get($application, 'fqdn'); if (str($this->fqdn)->explode(',')->count() > 1) { $this->fqdn = str($this->fqdn)->explode(',')->first(); } - $this->deployment_url = base_url()."/project/{$this->project_uuid}/".urlencode($this->environment_name)."/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}"; + $this->deployment_url = base_url()."/project/{$this->project_uuid}/environments/{$this->environment_uuid}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}"; } public function via(object $notifiable): array @@ -144,7 +147,7 @@ class DeploymentSuccess extends CustomEmailNotification { if ($this->preview) { $title = "Pull request #{$this->preview->pull_request_id} successfully deployed"; - $message = 'New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . ''; + $message = 'New PR'.$this->preview->pull_request_id.' version successfully deployed of '.$this->application_name.''; if ($this->preview->fqdn) { $buttons[] = [ 'text' => 'Open Application', @@ -153,7 +156,7 @@ class DeploymentSuccess extends CustomEmailNotification } } else { $title = 'New version successfully deployed'; - $message = 'New version successfully deployed of ' . $this->application_name . ''; + $message = 'New version successfully deployed of '.$this->application_name.''; if ($this->fqdn) { $buttons[] = [ 'text' => 'Open Application', diff --git a/app/Notifications/Application/StatusChanged.php b/app/Notifications/Application/StatusChanged.php index c9c7344c4..669f6e584 100644 --- a/app/Notifications/Application/StatusChanged.php +++ b/app/Notifications/Application/StatusChanged.php @@ -15,6 +15,8 @@ class StatusChanged extends CustomEmailNotification public string $project_uuid; + public string $environment_uuid; + public string $environment_name; public ?string $resource_url = null; @@ -26,12 +28,13 @@ class StatusChanged extends CustomEmailNotification $this->onQueue('high'); $this->resource_name = data_get($resource, 'name'); $this->project_uuid = data_get($resource, 'environment.project.uuid'); + $this->environment_uuid = data_get($resource, 'environment.uuid'); $this->environment_name = data_get($resource, 'environment.name'); $this->fqdn = data_get($resource, 'fqdn', null); if (str($this->fqdn)->explode(',')->count() > 1) { $this->fqdn = str($this->fqdn)->explode(',')->first(); } - $this->resource_url = base_url()."/project/{$this->project_uuid}/".urlencode($this->environment_name)."/application/{$this->resource->uuid}"; + $this->resource_url = base_url()."/project/{$this->project_uuid}/environments/{$this->environment_uuid}/application/{$this->resource->uuid}"; } public function via(object $notifiable): array @@ -80,7 +83,7 @@ class StatusChanged extends CustomEmailNotification public function toPushover(): PushoverMessage { - $message = $this->resource_name . ' has been stopped.'; + $message = $this->resource_name.' has been stopped.'; return new PushoverMessage( title: 'Application stopped', diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index aa3579f8d..428f78cb5 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -11,6 +11,7 @@ use Illuminate\Foundation\Events\MaintenanceModeEnabled; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; use SocialiteProviders\Authentik\AuthentikExtendSocialite; use SocialiteProviders\Azure\AzureExtendSocialite; +use SocialiteProviders\Infomaniak\InfomaniakExtendSocialite; use SocialiteProviders\Manager\SocialiteWasCalled; class EventServiceProvider extends ServiceProvider @@ -25,6 +26,7 @@ class EventServiceProvider extends ServiceProvider SocialiteWasCalled::class => [ AzureExtendSocialite::class.'@handle', AuthentikExtendSocialite::class.'@handle', + InfomaniakExtendSocialite::class.'@handle', ], ProxyStarted::class => [ ProxyStartedNotification::class, diff --git a/app/View/Components/Forms/Button.php b/app/View/Components/Forms/Button.php index da8b46dec..bf88d3f88 100644 --- a/app/View/Components/Forms/Button.php +++ b/app/View/Components/Forms/Button.php @@ -15,7 +15,8 @@ class Button extends Component public bool $disabled = false, public bool $noStyle = false, public ?string $modalId = null, - public string $defaultClass = 'button' + public string $defaultClass = 'button', + public bool $showLoadingIndicator = true, ) { if ($this->noStyle) { $this->defaultClass = ''; diff --git a/bootstrap/helpers/api.php b/bootstrap/helpers/api.php index 875866e2f..307c7ed1b 100644 --- a/bootstrap/helpers/api.php +++ b/bootstrap/helpers/api.php @@ -166,6 +166,7 @@ function removeUnnecessaryFieldsFromRequest(Request $request) { $request->offsetUnset('project_uuid'); $request->offsetUnset('environment_name'); + $request->offsetUnset('environment_uuid'); $request->offsetUnset('destination_uuid'); $request->offsetUnset('server_uuid'); $request->offsetUnset('type'); diff --git a/bootstrap/helpers/services.php b/bootstrap/helpers/services.php index fd2e1231f..cd99713a2 100644 --- a/bootstrap/helpers/services.php +++ b/bootstrap/helpers/services.php @@ -2,6 +2,7 @@ use App\Models\Application; use App\Models\EnvironmentVariable; +use App\Models\Service; use App\Models\ServiceApplication; use App\Models\ServiceDatabase; use Illuminate\Support\Stringable; @@ -119,7 +120,10 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) if ($resourceFqdns->count() === 1) { $resourceFqdns = $resourceFqdns->first(); $variableName = 'SERVICE_FQDN_'.str($resource->name)->upper()->replace('-', ''); - $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); + $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class) + ->where('resourceable_id', $resource->service_id) + ->where('key', $variableName) + ->first(); $fqdn = Url::fromString($resourceFqdns); $port = $fqdn->getPort(); $path = $fqdn->getPath(); @@ -134,7 +138,10 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) } if ($port) { $variableName = $variableName."_$port"; - $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); + $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class) + ->where('resourceable_id', $resource->service_id) + ->where('key', $variableName) + ->first(); if ($generatedEnv) { if ($path === '/') { $generatedEnv->value = $fqdn; @@ -145,7 +152,10 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) } } $variableName = 'SERVICE_URL_'.str($resource->name)->upper()->replace('-', ''); - $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); + $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class) + ->where('resourceable_id', $resource->service_id) + ->where('key', $variableName) + ->first(); $url = Url::fromString($fqdn); $port = $url->getPort(); $path = $url->getPath(); @@ -161,7 +171,10 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) } if ($port) { $variableName = $variableName."_$port"; - $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); + $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class) + ->where('resourceable_id', $resource->service_id) + ->where('key', $variableName) + ->first(); if ($generatedEnv) { if ($path === '/') { $generatedEnv->value = $url; @@ -179,10 +192,16 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) $path = $host->getPath(); $host = $host->getScheme().'://'.$host->getHost(); if ($port) { - $port_envs = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'like', "SERVICE_FQDN_%_$port")->get(); + $port_envs = EnvironmentVariable::where('resourceable_type', Service::class) + ->where('resourceable_id', $resource->service_id) + ->where('key', 'like', "SERVICE_FQDN_%_$port") + ->get(); foreach ($port_envs as $port_env) { $service_fqdn = str($port_env->key)->beforeLast('_')->after('SERVICE_FQDN_'); - $env = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'SERVICE_FQDN_'.$service_fqdn)->first(); + $env = EnvironmentVariable::where('resourceable_type', Service::class) + ->where('resourceable_id', $resource->service_id) + ->where('key', 'SERVICE_FQDN_'.$service_fqdn) + ->first(); if ($env) { if ($path === '/') { $env->value = $host; @@ -198,10 +217,16 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) } $port_env->save(); } - $port_envs_url = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'like', "SERVICE_URL_%_$port")->get(); + $port_envs_url = EnvironmentVariable::where('resourceable_type', Service::class) + ->where('resourceable_id', $resource->service_id) + ->where('key', 'like', "SERVICE_URL_%_$port") + ->get(); foreach ($port_envs_url as $port_env_url) { $service_url = str($port_env_url->key)->beforeLast('_')->after('SERVICE_URL_'); - $env = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'SERVICE_URL_'.$service_url)->first(); + $env = EnvironmentVariable::where('resourceable_type', Service::class) + ->where('resourceable_id', $resource->service_id) + ->where('key', 'SERVICE_URL_'.$service_url) + ->first(); if ($env) { if ($path === '/') { $env->value = $url; @@ -219,7 +244,10 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) } } else { $variableName = 'SERVICE_FQDN_'.str($resource->name)->upper()->replace('-', ''); - $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); + $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class) + ->where('resourceable_id', $resource->service_id) + ->where('key', $variableName) + ->first(); $fqdn = Url::fromString($fqdn); $fqdn = $fqdn->getScheme().'://'.$fqdn->getHost().$fqdn->getPath(); if ($generatedEnv) { @@ -227,7 +255,10 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) $generatedEnv->save(); } $variableName = 'SERVICE_URL_'.str($resource->name)->upper()->replace('-', ''); - $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); + $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class) + ->where('resourceable_id', $resource->service_id) + ->where('key', $variableName) + ->first(); $url = Url::fromString($fqdn); $url = $url->getHost().$url->getPath(); if ($generatedEnv) { diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 7aa411cd1..b9fde2070 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -1819,7 +1819,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'key' => $key, 'value' => $fqdn, 'is_build_time' => false, - 'service_id' => $resource->id, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, 'is_preview' => false, ]); } @@ -1831,7 +1832,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } $env = EnvironmentVariable::where([ 'key' => $key, - 'service_id' => $resource->id, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, ])->first(); if ($env) { $env_url = Url::fromString($savedService->fqdn); @@ -1854,14 +1856,16 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal if ($value?->startsWith('$')) { $foundEnv = EnvironmentVariable::where([ 'key' => $key, - 'service_id' => $resource->id, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, ])->first(); $value = replaceVariables($value); $key = $value; if ($value->startsWith('SERVICE_')) { $foundEnv = EnvironmentVariable::where([ 'key' => $key, - 'service_id' => $resource->id, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, ])->first(); ['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value); if (! is_null($command)) { @@ -1895,7 +1899,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'key' => $key, 'value' => $fqdn, 'is_build_time' => false, - 'service_id' => $resource->id, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, 'is_preview' => false, ]); } @@ -1912,7 +1917,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } $env = EnvironmentVariable::where([ 'key' => $key, - 'service_id' => $resource->id, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, ])->first(); if ($env) { $env_url = Url::fromString($env->value); @@ -1932,7 +1938,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'key' => $key, 'value' => $generatedValue, 'is_build_time' => false, - 'service_id' => $resource->id, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, 'is_preview' => false, ]); } @@ -1957,18 +1964,21 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } $foundEnv = EnvironmentVariable::where([ 'key' => $key, - 'service_id' => $resource->id, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, ])->first(); if ($foundEnv) { $defaultValue = data_get($foundEnv, 'value'); } EnvironmentVariable::updateOrCreate([ 'key' => $key, - 'service_id' => $resource->id, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, ], [ 'value' => $defaultValue, 'is_build_time' => false, - 'service_id' => $resource->id, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, 'is_preview' => false, ]); } @@ -2831,6 +2841,10 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal data_set($service, 'container_name', $containerName); data_forget($service, 'volumes.*.content'); data_forget($service, 'volumes.*.isDirectory'); + data_forget($service, 'volumes.*.is_directory'); + data_forget($service, 'exclude_from_hc'); + data_set($service, 'environment', $serviceVariables->toArray()); + updateCompose($savedService); return $service; }); @@ -2869,13 +2883,11 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int } if ($isApplication) { - $nameOfId = 'application_id'; $pullRequestId = $pull_request_id; $isPullRequest = $pullRequestId == 0 ? false : true; $server = data_get($resource, 'destination.server'); $fileStorages = $resource->fileStorages(); } elseif ($isService) { - $nameOfId = 'service_id'; $server = data_get($resource, 'server'); $allServices = get_service_templates(); } else { @@ -3042,9 +3054,10 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int } if (substr_count(str($key)->value(), '_') === 2) { - $resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->firstOrCreate([ + $resource->environment_variables()->firstOrCreate([ 'key' => $key->value(), - $nameOfId => $resource->id, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, ], [ 'value' => $fqdn, 'is_build_time' => false, @@ -3053,9 +3066,10 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int } if (substr_count(str($key)->value(), '_') === 3) { $newKey = str($key)->beforeLast('_'); - $resource->environment_variables()->where('key', $newKey->value())->where($nameOfId, $resource->id)->firstOrCreate([ + $resource->environment_variables()->firstOrCreate([ 'key' => $newKey->value(), - $nameOfId => $resource->id, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, ], [ 'value' => $fqdn, 'is_build_time' => false, @@ -3071,7 +3085,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $key = str($key); $value = replaceVariables($value); $command = parseCommandFromMagicEnvVariable($key); - $found = $resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->first(); + $found = $resource->environment_variables()->where('key', $key->value())->where('resourceable_type', get_class($resource))->where('resourceable_id', $resource->id)->first(); if ($found) { continue; } @@ -3085,9 +3099,10 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int } elseif ($isService) { $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); } - $resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->firstOrCreate([ + $resource->environment_variables()->firstOrCreate([ 'key' => $key->value(), - $nameOfId => $resource->id, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, ], [ 'value' => $fqdn, 'is_build_time' => false, @@ -3104,9 +3119,10 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); } $fqdn = str($fqdn)->replace('http://', '')->replace('https://', ''); - $resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->firstOrCreate([ + $resource->environment_variables()->firstOrCreate([ 'key' => $key->value(), - $nameOfId => $resource->id, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, ], [ 'value' => $fqdn, 'is_build_time' => false, @@ -3114,9 +3130,10 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int ]); } else { $value = generateEnvValue($command, $resource); - $resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->firstOrCreate([ + $resource->environment_variables()->firstOrCreate([ 'key' => $key->value(), - $nameOfId => $resource->id, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, ], [ 'value' => $value, 'is_build_time' => false, @@ -3464,9 +3481,10 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $originalValue = $value; $parsedValue = replaceVariables($value); if ($value->startsWith('$SERVICE_')) { - $resource->environment_variables()->where('key', $key)->where($nameOfId, $resource->id)->firstOrCreate([ + $resource->environment_variables()->firstOrCreate([ 'key' => $key, - $nameOfId => $resource->id, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, ], [ 'value' => $value, 'is_build_time' => false, @@ -3480,9 +3498,10 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int } if ($key->value() === $parsedValue->value()) { $value = null; - $resource->environment_variables()->where('key', $key)->where($nameOfId, $resource->id)->firstOrCreate([ + $resource->environment_variables()->firstOrCreate([ 'key' => $key, - $nameOfId => $resource->id, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, ], [ 'value' => $value, 'is_build_time' => false, @@ -3516,22 +3535,24 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int if ($originalValue->value() === $value->value()) { // This means the variable does not have a default value, so it needs to be created in Coolify $parsedKeyValue = replaceVariables($value); - $resource->environment_variables()->where('key', $parsedKeyValue)->where($nameOfId, $resource->id)->firstOrCreate([ + $resource->environment_variables()->firstOrCreate([ 'key' => $parsedKeyValue, - $nameOfId => $resource->id, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, ], [ 'is_build_time' => false, 'is_preview' => false, 'is_required' => $isRequired, ]); // Add the variable to the environment so it will be shown in the deployable compose file - $environment[$parsedKeyValue->value()] = $resource->environment_variables()->where('key', $parsedKeyValue)->where($nameOfId, $resource->id)->first()->value; + $environment[$parsedKeyValue->value()] = $resource->environment_variables()->where('key', $parsedKeyValue)->where('resourceable_type', get_class($resource))->where('resourceable_id', $resource->id)->first()->value; continue; } - $resource->environment_variables()->where('key', $key)->where($nameOfId, $resource->id)->firstOrCreate([ + $resource->environment_variables()->firstOrCreate([ 'key' => $key, - $nameOfId => $resource->id, + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, ], [ 'value' => $value, 'is_build_time' => false, diff --git a/bootstrap/helpers/socialite.php b/bootstrap/helpers/socialite.php index 130227e81..09dffb78a 100644 --- a/bootstrap/helpers/socialite.php +++ b/bootstrap/helpers/socialite.php @@ -40,6 +40,7 @@ function get_socialite_provider(string $provider) 'github' => \Laravel\Socialite\Two\GithubProvider::class, 'gitlab' => \Laravel\Socialite\Two\GitlabProvider::class, 'google' => \Laravel\Socialite\Two\GoogleProvider::class, + 'infomaniak' => \SocialiteProviders\Infomaniak\Provider::class, ]; return Socialite::buildProvider( diff --git a/bootstrap/helpers/subscriptions.php b/bootstrap/helpers/subscriptions.php index ab9ee9b9d..510516a2f 100644 --- a/bootstrap/helpers/subscriptions.php +++ b/bootstrap/helpers/subscriptions.php @@ -69,6 +69,7 @@ function allowedPathsForUnsubscribedAccounts() 'logout', 'force-password-reset', 'livewire/update', + 'admin', ]; } function allowedPathsForBoardingAccounts() diff --git a/composer.json b/composer.json index b8dc354c3..8e8ad0a4b 100644 --- a/composer.json +++ b/composer.json @@ -40,6 +40,7 @@ "resend/resend-laravel": "^0.15.0", "sentry/sentry-laravel": "^4.6", "socialiteproviders/authentik": "^5.2", + "socialiteproviders/infomaniak": "^4.0", "socialiteproviders/microsoft-azure": "^5.1", "spatie/laravel-activitylog": "^4.7.3", "spatie/laravel-data": "^4.11", diff --git a/composer.lock b/composer.lock index 3fbe72afb..50d64cba2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "871067cb42e6347ca53ff36e81ac5079", + "content-hash": "5517a7becb5d31fa2ef3ecfaea06f7a7", "packages": [ { "name": "3sidedcube/laravel-redoc", @@ -7467,6 +7467,57 @@ }, "time": "2023-11-07T22:21:16+00:00" }, + { + "name": "socialiteproviders/infomaniak", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/SocialiteProviders/Infomaniak.git", + "reference": "9796ad686204443bfdf3ff19a6c409e8771667e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SocialiteProviders/Infomaniak/zipball/9796ad686204443bfdf3ff19a6c409e8771667e1", + "reference": "9796ad686204443bfdf3ff19a6c409e8771667e1", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^8.0", + "socialiteproviders/manager": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "SocialiteProviders\\Infomaniak\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Leopold Jacquot", + "email": "leopold.jacquot@infomaniak.com" + } + ], + "description": "Infomaniak OAuth2 Provider for Laravel Socialite", + "keywords": [ + "infomaniak", + "laravel", + "oauth", + "oauth2", + "provider", + "socialite" + ], + "support": { + "docs": "https://socialiteproviders.com/qq", + "issues": "https://github.com/socialiteproviders/providers/issues", + "source": "https://github.com/socialiteproviders/providers" + }, + "time": "2024-11-20T05:42:36+00:00" + }, { "name": "socialiteproviders/manager", "version": "v4.7.0", diff --git a/config/constants.php b/config/constants.php index a8a70235b..99859ffb8 100644 --- a/config/constants.php +++ b/config/constants.php @@ -2,7 +2,7 @@ return [ 'coolify' => [ - 'version' => '4.0.0-beta.379', + 'version' => '4.0.0-beta.380', 'self_hosted' => env('SELF_HOSTED', true), 'autoupdate' => env('AUTOUPDATE'), 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), diff --git a/database/migrations/2024_11_22_124742_add_uuid_to_environments_table.php b/database/migrations/2024_11_22_124742_add_uuid_to_environments_table.php new file mode 100644 index 000000000..b106427af --- /dev/null +++ b/database/migrations/2024_11_22_124742_add_uuid_to_environments_table.php @@ -0,0 +1,38 @@ +string('uuid')->after('id')->nullable()->unique(); + }); + + DB::table('environments') + ->whereNull('uuid') + ->chunkById(100, function ($environments) { + foreach ($environments as $environment) { + DB::table('environments') + ->where('id', $environment->id) + ->update(['uuid' => (string) new Cuid2]); + } + }); + + Schema::table('environments', function (Blueprint $table) { + $table->string('uuid')->nullable(false)->change(); + }); + } + + public function down(): void + { + Schema::table('environments', function (Blueprint $table) { + $table->dropColumn('uuid'); + }); + } +}; diff --git a/database/migrations/2024_12_16_134437_add_resourceable_columns_to_environment_variables_table.php b/database/migrations/2024_12_16_134437_add_resourceable_columns_to_environment_variables_table.php new file mode 100644 index 000000000..c4b718638 --- /dev/null +++ b/database/migrations/2024_12_16_134437_add_resourceable_columns_to_environment_variables_table.php @@ -0,0 +1,165 @@ +string('resourceable_type')->nullable(); + $table->unsignedBigInteger('resourceable_id')->nullable(); + $table->index(['resourceable_type', 'resourceable_id']); + }); + + // Populate the new columns + DB::table('environment_variables')->whereNotNull('application_id') + ->update([ + 'resourceable_type' => 'App\\Models\\Application', + 'resourceable_id' => DB::raw('application_id'), + ]); + + DB::table('environment_variables')->whereNotNull('service_id') + ->update([ + 'resourceable_type' => 'App\\Models\\Service', + 'resourceable_id' => DB::raw('service_id'), + ]); + + DB::table('environment_variables')->whereNotNull('standalone_postgresql_id') + ->update([ + 'resourceable_type' => 'App\\Models\\StandalonePostgresql', + 'resourceable_id' => DB::raw('standalone_postgresql_id'), + ]); + + DB::table('environment_variables')->whereNotNull('standalone_redis_id') + ->update([ + 'resourceable_type' => 'App\\Models\\StandaloneRedis', + 'resourceable_id' => DB::raw('standalone_redis_id'), + ]); + + DB::table('environment_variables')->whereNotNull('standalone_mongodb_id') + ->update([ + 'resourceable_type' => 'App\\Models\\StandaloneMongodb', + 'resourceable_id' => DB::raw('standalone_mongodb_id'), + ]); + + DB::table('environment_variables')->whereNotNull('standalone_mysql_id') + ->update([ + 'resourceable_type' => 'App\\Models\\StandaloneMysql', + 'resourceable_id' => DB::raw('standalone_mysql_id'), + ]); + + DB::table('environment_variables')->whereNotNull('standalone_mariadb_id') + ->update([ + 'resourceable_type' => 'App\\Models\\StandaloneMariadb', + 'resourceable_id' => DB::raw('standalone_mariadb_id'), + ]); + + DB::table('environment_variables')->whereNotNull('standalone_keydb_id') + ->update([ + 'resourceable_type' => 'App\\Models\\StandaloneKeydb', + 'resourceable_id' => DB::raw('standalone_keydb_id'), + ]); + + DB::table('environment_variables')->whereNotNull('standalone_dragonfly_id') + ->update([ + 'resourceable_type' => 'App\\Models\\StandaloneDragonfly', + 'resourceable_id' => DB::raw('standalone_dragonfly_id'), + ]); + + DB::table('environment_variables')->whereNotNull('standalone_clickhouse_id') + ->update([ + 'resourceable_type' => 'App\\Models\\StandaloneClickhouse', + 'resourceable_id' => DB::raw('standalone_clickhouse_id'), + ]); + + // After successful migration, we can drop the old foreign key columns + Schema::table('environment_variables', function (Blueprint $table) { + $table->dropColumn([ + 'application_id', + 'service_id', + 'standalone_postgresql_id', + 'standalone_redis_id', + 'standalone_mongodb_id', + 'standalone_mysql_id', + 'standalone_mariadb_id', + 'standalone_keydb_id', + 'standalone_dragonfly_id', + 'standalone_clickhouse_id', + ]); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('environment_variables', function (Blueprint $table) { + // Restore the old columns + $table->unsignedBigInteger('application_id')->nullable(); + $table->unsignedBigInteger('service_id')->nullable(); + $table->unsignedBigInteger('standalone_postgresql_id')->nullable(); + $table->unsignedBigInteger('standalone_redis_id')->nullable(); + $table->unsignedBigInteger('standalone_mongodb_id')->nullable(); + $table->unsignedBigInteger('standalone_mysql_id')->nullable(); + $table->unsignedBigInteger('standalone_mariadb_id')->nullable(); + $table->unsignedBigInteger('standalone_keydb_id')->nullable(); + $table->unsignedBigInteger('standalone_dragonfly_id')->nullable(); + $table->unsignedBigInteger('standalone_clickhouse_id')->nullable(); + }); + + Schema::table('environment_variables', function (Blueprint $table) { + // Restore data from polymorphic relationship + DB::table('environment_variables') + ->where('resourceable_type', 'App\\Models\\Application') + ->update(['application_id' => DB::raw('resourceable_id')]); + + DB::table('environment_variables') + ->where('resourceable_type', 'App\\Models\\Service') + ->update(['service_id' => DB::raw('resourceable_id')]); + + DB::table('environment_variables') + ->where('resourceable_type', 'App\\Models\\StandalonePostgresql') + ->update(['standalone_postgresql_id' => DB::raw('resourceable_id')]); + + DB::table('environment_variables') + ->where('resourceable_type', 'App\\Models\\StandaloneRedis') + ->update(['standalone_redis_id' => DB::raw('resourceable_id')]); + + DB::table('environment_variables') + ->where('resourceable_type', 'App\\Models\\StandaloneMongodb') + ->update(['standalone_mongodb_id' => DB::raw('resourceable_id')]); + + DB::table('environment_variables') + ->where('resourceable_type', 'App\\Models\\StandaloneMysql') + ->update(['standalone_mysql_id' => DB::raw('resourceable_id')]); + + DB::table('environment_variables') + ->where('resourceable_type', 'App\\Models\\StandaloneMariadb') + ->update(['standalone_mariadb_id' => DB::raw('resourceable_id')]); + + DB::table('environment_variables') + ->where('resourceable_type', 'App\\Models\\StandaloneKeydb') + ->update(['standalone_keydb_id' => DB::raw('resourceable_id')]); + + DB::table('environment_variables') + ->where('resourceable_type', 'App\\Models\\StandaloneDragonfly') + ->update(['standalone_dragonfly_id' => DB::raw('resourceable_id')]); + + DB::table('environment_variables') + ->where('resourceable_type', 'App\\Models\\StandaloneClickhouse') + ->update(['standalone_clickhouse_id' => DB::raw('resourceable_id')]); + + // Drop the polymorphic columns + $table->dropIndex(['resourceable_type', 'resourceable_id']); + $table->dropColumn(['resourceable_type', 'resourceable_id']); + }); + } +}; diff --git a/database/migrations/2024_12_17_140637_add_server_disk_usage_check_frequency_to_server_settings_table.php b/database/migrations/2024_12_17_140637_add_server_disk_usage_check_frequency_to_server_settings_table.php new file mode 100644 index 000000000..be0f4bc0f --- /dev/null +++ b/database/migrations/2024_12_17_140637_add_server_disk_usage_check_frequency_to_server_settings_table.php @@ -0,0 +1,28 @@ +string('server_disk_usage_check_frequency')->default('0 23 * * *'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('server_settings', function (Blueprint $table) { + $table->dropColumn('server_disk_usage_check_frequency'); + }); + } +}; diff --git a/database/seeders/OauthSettingSeeder.php b/database/seeders/OauthSettingSeeder.php index bf902175f..fa692d2dc 100644 --- a/database/seeders/OauthSettingSeeder.php +++ b/database/seeders/OauthSettingSeeder.php @@ -4,6 +4,7 @@ namespace Database\Seeders; use App\Models\OauthSetting; use Illuminate\Database\Seeder; +use Illuminate\Support\Facades\Log; class OauthSettingSeeder extends Seeder { @@ -12,62 +13,54 @@ class OauthSettingSeeder extends Seeder */ public function run(): void { - $providers = collect([ - 'azure', - 'bitbucket', - 'github', - 'gitlab', - 'google', - 'authentik', - ]); + try { + $providers = collect([ + 'azure', + 'bitbucket', + 'github', + 'gitlab', + 'google', + 'authentik', + 'infomaniak', + ]); - $isOauthSeeded = OauthSetting::count() > 0; + $isOauthSeeded = OauthSetting::count() > 0; - // We changed how providers are defined in the database, so we authentik does not exists, we need to recreate all of the auth providers - // Before authentik was a provider, providers started with 0 id + // We changed how providers are defined in the database, so we authentik does not exists, we need to recreate all of the auth providers + // Before authentik was a provider, providers started with 0 id - $isOauthAuthentik = OauthSetting::where('provider', 'authentik')->exists(); - if ($isOauthSeeded) { - if (! $isOauthAuthentik) { - $allProviders = OauthSetting::all(); - $notFoundProviders = $providers->diff($allProviders->pluck('provider')); - - $allProviders->each(function ($provider) { - $provider->delete(); - }); - $allProviders->each(function ($provider) use ($providers) { - $providerName = $provider->provider; - - $foundProvider = $providers->first(function ($provider) use ($providerName) { - return $provider === $providerName; - }); - - if ($foundProvider) { - $newProvder = new OauthSetting; - $newProvder = $provider; - unset($newProvder->id); - $newProvder->save(); - } - }); - - foreach ($notFoundProviders as $provider) { - OauthSetting::create([ - 'provider' => $provider, - ]); - } - } else { + $isOauthAuthentik = OauthSetting::where('provider', 'authentik')->exists(); + if (! $isOauthSeeded || $isOauthAuthentik) { foreach ($providers as $provider) { OauthSetting::updateOrCreate([ 'provider' => $provider, ]); } + + return; } - } else { - foreach ($providers as $provider) { - OauthSetting::updateOrCreate([ + + $allProviders = OauthSetting::all(); + $notFoundProviders = $providers->diff($allProviders->pluck('provider')); + + $allProviders->each(function ($provider) { + $provider->delete(); + }); + $allProviders->each(function ($provider) { + $provider = new OauthSetting; + $provider->provider = $provider->provider; + unset($provider->id); + $provider->save(); + }); + + foreach ($notFoundProviders as $provider) { + OauthSetting::create([ 'provider' => $provider, ]); } + + } catch (\Exception $e) { + Log::error($e->getMessage()); } } } diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 76f8e9ca6..3fadd914c 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -7,7 +7,7 @@ services: - USER_ID=${USERID:-1000} - GROUP_ID=${GROUPID:-1000} ports: - - "${APP_PORT:-8000}:80" + - "${APP_PORT:-8000}:8080" environment: AUTORUN_ENABLED: false PUSHER_HOST: "${PUSHER_HOST}" diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index d86b2336b..c7349f7b0 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -54,7 +54,7 @@ services: - SSH_MUX_ENABLED - SSH_MUX_PERSIST_TIME ports: - - "${APP_PORT:-8000}:80" + - "${APP_PORT:-8000}:8080" expose: - "${APP_PORT:-8000}" healthcheck: diff --git a/docker/coolify-realtime/terminal-server.js b/docker/coolify-realtime/terminal-server.js index 6633204b2..6649f866c 100755 --- a/docker/coolify-realtime/terminal-server.js +++ b/docker/coolify-realtime/terminal-server.js @@ -33,7 +33,7 @@ const verifyClient = async (info, callback) => { try { // Authenticate with Laravel backend - const response = await axios.post(`http://coolify/terminal/auth`, null, { + const response = await axios.post(`http://coolify:8080/terminal/auth`, null, { headers: { 'Cookie': `${sessionCookieName}=${laravelSession}`, 'X-XSRF-TOKEN': xsrfToken diff --git a/docker/development/etc/nginx/site-opts.d/http.conf b/docker/development/etc/nginx/site-opts.d/http.conf index 41735cf06..a5bbd78a3 100644 --- a/docker/development/etc/nginx/site-opts.d/http.conf +++ b/docker/development/etc/nginx/site-opts.d/http.conf @@ -1,5 +1,3 @@ -listen 80 default_server; -listen [::]:80 default_server; listen 8080 default_server; listen [::]:8080 default_server; @@ -39,7 +37,7 @@ location ~ \.php$ { fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; - fastcgi_buffers 8 8k; - fastcgi_buffer_size 8k; + fastcgi_buffers 16 16k; + fastcgi_buffer_size 32k; fastcgi_read_timeout 99; } diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile index 09281a666..aaef29f9d 100644 --- a/docker/production/Dockerfile +++ b/docker/production/Dockerfile @@ -99,6 +99,9 @@ RUN mkdir -p /usr/local/bin && \ COPY docker/production/etc/php/conf.d/zzz-custom-php.ini /usr/local/etc/php/conf.d/zzz-custom-php.ini ENV PHP_OPCACHE_ENABLE=1 +# Configure entrypoint +COPY --chmod=755 docker/production/entrypoint.d/ /etc/entrypoint.d + # Copy application files from previous stages COPY --from=base --chown=www-data:www-data /var/www/html/vendor ./vendor COPY --from=static-assets --chown=www-data:www-data /app/public/build ./public/build diff --git a/docker/production/entrypoint.d/99-debug-mode.sh b/docker/production/entrypoint.d/99-debug-mode.sh new file mode 100644 index 000000000..e0a181373 --- /dev/null +++ b/docker/production/entrypoint.d/99-debug-mode.sh @@ -0,0 +1,8 @@ +# Debug mode +if [ "$APP_DEBUG" = "true" ]; then + echo "Debug mode is enabled" + echo "Installing development dependencies..." + composer install --dev --no-scripts + echo "Clearing optimized classes..." + php artisan optimize:clear +fi diff --git a/docker/production/etc/nginx/site-opts.d/http.conf b/docker/production/etc/nginx/site-opts.d/http.conf index 41735cf06..a5bbd78a3 100644 --- a/docker/production/etc/nginx/site-opts.d/http.conf +++ b/docker/production/etc/nginx/site-opts.d/http.conf @@ -1,5 +1,3 @@ -listen 80 default_server; -listen [::]:80 default_server; listen 8080 default_server; listen [::]:8080 default_server; @@ -39,7 +37,7 @@ location ~ \.php$ { fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; - fastcgi_buffers 8 8k; - fastcgi_buffer_size 8k; + fastcgi_buffers 16 16k; + fastcgi_buffer_size 32k; fastcgi_read_timeout 99; } diff --git a/lang/ar.json b/lang/ar.json index 4b9afbe99..b473318d3 100644 --- a/lang/ar.json +++ b/lang/ar.json @@ -5,6 +5,7 @@ "auth.login.github": "تسجيل الدخول باستخدام GitHub", "auth.login.gitlab": "تسجيل الدخول باستخدام Gitlab", "auth.login.google": "تسجيل الدخول باستخدام Google", + "auth.login.infomaniak": "تسجيل الدخول باستخدام Infomaniak", "auth.already_registered": "هل سبق لك التسجيل؟", "auth.confirm_password": "تأكيد كلمة المرور", "auth.forgot_password": "نسيت كلمة المرور", diff --git a/lang/cs.json b/lang/cs.json index 48b47b06a..270fd272b 100644 --- a/lang/cs.json +++ b/lang/cs.json @@ -5,6 +5,7 @@ "auth.login.github": "Přihlásit se pomocí GitHubu", "auth.login.gitlab": "Přihlásit se pomocí Gitlabu", "auth.login.google": "Přihlásit se pomocí Google", + "auth.login.infomaniak": "Přihlásit se pomocí Infomaniak", "auth.already_registered": "Již jste registrováni?", "auth.confirm_password": "Potvrďte heslo", "auth.forgot_password": "Zapomněli jste heslo", diff --git a/lang/de.json b/lang/de.json index 29fec629f..c5644e3a7 100644 --- a/lang/de.json +++ b/lang/de.json @@ -5,6 +5,7 @@ "auth.login.github": "Mit GitHub anmelden", "auth.login.gitlab": "Mit GitLab anmelden", "auth.login.google": "Mit Google anmelden", + "auth.login.infomaniak": "Mit Infomaniak anmelden", "auth.already_registered": "Bereits registriert?", "auth.confirm_password": "Passwort bestätigen", "auth.forgot_password": "Passwort vergessen", diff --git a/lang/en.json b/lang/en.json index 4e0749ece..cdca68601 100644 --- a/lang/en.json +++ b/lang/en.json @@ -6,6 +6,7 @@ "auth.login.github": "Login with GitHub", "auth.login.gitlab": "Login with Gitlab", "auth.login.google": "Login with Google", + "auth.login.infomaniak": "Login with Infomaniak", "auth.already_registered": "Already registered?", "auth.confirm_password": "Confirm password", "auth.forgot_password": "Forgot password", diff --git a/lang/es.json b/lang/es.json index 0d8c0c940..aceacd462 100644 --- a/lang/es.json +++ b/lang/es.json @@ -5,6 +5,7 @@ "auth.login.github": "Acceder con GitHub", "auth.login.gitlab": "Acceder con Gitlab", "auth.login.google": "Acceder con Google", + "auth.login.infomaniak": "Acceder con Infomaniak", "auth.already_registered": "¿Ya estás registrado?", "auth.confirm_password": "Confirmar contraseña", "auth.forgot_password": "¿Olvidaste tu contraseña?", @@ -27,4 +28,4 @@ "input.recovery_code": "Código de recuperación", "button.save": "Guardar", "repository.url": "Examples
Para repositorios públicos, usar https://....
Para repositorios privados, usar git@....

https://github.com/coollabsio/coolify-examples main la rama 'main' será seleccionada.
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify la rama 'nodejs-fastify' será seleccionada.
https://gitea.com/sedlav/expressjs.git main la rama 'main' será seleccionada.
https://gitlab.com/andrasbacsai/nodejs-example.git main la rama 'main' será seleccionada." -} \ No newline at end of file +} diff --git a/lang/fa.json b/lang/fa.json index d0ecc4a3b..7a714e626 100644 --- a/lang/fa.json +++ b/lang/fa.json @@ -5,6 +5,7 @@ "auth.login.github": "ورود با گیت هاب", "auth.login.gitlab": "ورود با گیت لب", "auth.login.google": "ورود با گوگل", + "auth.login.infomaniak": "ورود با Infomaniak", "auth.already_registered": "قبلاً ثبت نام کرده‌اید؟", "auth.confirm_password": "تایید رمز عبور", "auth.forgot_password": "فراموشی رمز عبور", diff --git a/lang/fr.json b/lang/fr.json index dbd5a1bf7..a94d633d3 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -5,6 +5,7 @@ "auth.login.github": "Connexion avec GitHub", "auth.login.gitlab": "Connexion avec Gitlab", "auth.login.google": "Connexion avec Google", + "auth.login.infomaniak": "Connexion avec Infomaniak", "auth.already_registered": "Déjà enregistré ?", "auth.confirm_password": "Confirmer le mot de passe", "auth.forgot_password": "Mot de passe oublié", diff --git a/lang/it.json b/lang/it.json index 6e4feb9cc..30b9f3902 100644 --- a/lang/it.json +++ b/lang/it.json @@ -5,6 +5,7 @@ "auth.login.github": "Accedi con GitHub", "auth.login.gitlab": "Accedi con Gitlab", "auth.login.google": "Accedi con Google", + "auth.login.infomaniak": "Accedi con Infomaniak", "auth.already_registered": "Già registrato?", "auth.confirm_password": "Conferma password", "auth.forgot_password": "Password dimenticata", diff --git a/lang/ja.json b/lang/ja.json index 4652a3b17..4d4589900 100644 --- a/lang/ja.json +++ b/lang/ja.json @@ -5,6 +5,7 @@ "auth.login.github": "GitHubでログイン", "auth.login.gitlab": "Gitlabでログイン", "auth.login.google": "Googleでログイン", + "auth.login.infomaniak": "Infomaniakでログイン", "auth.already_registered": "すでに登録済みですか?", "auth.confirm_password": "パスワードを確認", "auth.forgot_password": "パスワードを忘れた", diff --git a/lang/pt.json b/lang/pt.json index b5dd5c434..c5f393e65 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -5,6 +5,7 @@ "auth.login.github": "Entrar com GitHub", "auth.login.gitlab": "Entrar com Gitlab", "auth.login.google": "Entrar com Google", + "auth.login.infomaniak": "Entrar com Infomaniak", "auth.already_registered": "Já tem uma conta?", "auth.confirm_password": "Confirmar senha", "auth.forgot_password": "Esqueceu a senha?", diff --git a/lang/ro.json b/lang/ro.json index db1aa85db..4c7968cfa 100644 --- a/lang/ro.json +++ b/lang/ro.json @@ -5,6 +5,7 @@ "auth.login.github": "Autentificare prin GitHub", "auth.login.gitlab": "Autentificare prin Gitlab", "auth.login.google": "Autentificare prin Google", + "auth.login.infomaniak": "Autentificare prin Infomaniak", "auth.already_registered": "Sunteți deja înregistrat?", "auth.confirm_password": "Confirmați parola", "auth.forgot_password": "Ați uitat parola", diff --git a/lang/tr.json b/lang/tr.json index 255b0d15b..3cbcee409 100644 --- a/lang/tr.json +++ b/lang/tr.json @@ -5,6 +5,7 @@ "auth.login.github": "GitHub ile Giriş Yap", "auth.login.gitlab": "GitLab ile Giriş Yap", "auth.login.google": "Google ile Giriş Yap", + "auth.login.infomaniak": "Infomaniak ile Giriş Yap", "auth.already_registered": "Zaten kayıtlı mısınız?", "auth.confirm_password": "Şifreyi Onayla", "auth.forgot_password": "Şifremi Unuttum", diff --git a/lang/vi.json b/lang/vi.json index 548dbe8b7..bb43fd34d 100644 --- a/lang/vi.json +++ b/lang/vi.json @@ -5,6 +5,7 @@ "auth.login.github": "Đăng Nhập Bằng GitHub", "auth.login.gitlab": "Đăng Nhập Bằng Gitlab", "auth.login.google": "Đăng Nhập Bằng Google", + "auth.login.infomaniak": "Đăng Nhập Bằng Infomaniak", "auth.already_registered": "Đã đăng ký?", "auth.confirm_password": "Nhập lại mật khẩu", "auth.forgot_password": "Quên mật khẩu", diff --git a/lang/zh-cn.json b/lang/zh-cn.json index 70c457fa8..944887a5f 100644 --- a/lang/zh-cn.json +++ b/lang/zh-cn.json @@ -5,6 +5,7 @@ "auth.login.github": "使用 GitHub 登录", "auth.login.gitlab": "使用 Gitlab 登录", "auth.login.google": "使用 Google 登录", + "auth.login.infomaniak": "使用 Infomaniak 登录", "auth.already_registered": "已经注册?", "auth.confirm_password": "确认密码", "auth.forgot_password": "忘记密码", diff --git a/lang/zh-tw.json b/lang/zh-tw.json index 63956f7a1..c42ebb33e 100644 --- a/lang/zh-tw.json +++ b/lang/zh-tw.json @@ -5,6 +5,7 @@ "auth.login.github": "使用 GitHub 登入", "auth.login.gitlab": "使用 Gitlab 登入", "auth.login.google": "使用 Google 登入", + "auth.login.infomaniak": "使用 Infomaniak 登入", "auth.already_registered": "已經註冊?", "auth.confirm_password": "確認密碼", "auth.forgot_password": "忘記密碼", diff --git a/openapi.json b/openapi.json index 659adc62f..819f229cc 100644 --- a/openapi.json +++ b/openapi.json @@ -65,6 +65,7 @@ "project_uuid", "server_uuid", "environment_name", + "environment_uuid", "git_repository", "git_branch", "build_pack", @@ -81,7 +82,11 @@ }, "environment_name": { "type": "string", - "description": "The environment name." + "description": "The environment name. You need to provide at least one of environment_name or environment_uuid." + }, + "environment_uuid": { + "type": "string", + "description": "The environment UUID. You need to provide at least one of environment_name or environment_uuid." }, "git_repository": { "type": "string", @@ -389,6 +394,7 @@ "project_uuid", "server_uuid", "environment_name", + "environment_uuid", "github_app_uuid", "git_repository", "git_branch", @@ -406,7 +412,11 @@ }, "environment_name": { "type": "string", - "description": "The environment name." + "description": "The environment name. You need to provide at least one of environment_name or environment_uuid." + }, + "environment_uuid": { + "type": "string", + "description": "The environment UUID. You need to provide at least one of environment_name or environment_uuid." }, "github_app_uuid": { "type": "string", @@ -718,6 +728,7 @@ "project_uuid", "server_uuid", "environment_name", + "environment_uuid", "private_key_uuid", "git_repository", "git_branch", @@ -735,7 +746,11 @@ }, "environment_name": { "type": "string", - "description": "The environment name." + "description": "The environment name. You need to provide at least one of environment_name or environment_uuid." + }, + "environment_uuid": { + "type": "string", + "description": "The environment UUID. You need to provide at least one of environment_name or environment_uuid." }, "private_key_uuid": { "type": "string", @@ -1047,6 +1062,7 @@ "project_uuid", "server_uuid", "environment_name", + "environment_uuid", "dockerfile" ], "properties": { @@ -1060,7 +1076,11 @@ }, "environment_name": { "type": "string", - "description": "The environment name." + "description": "The environment name. You need to provide at least one of environment_name or environment_uuid." + }, + "environment_uuid": { + "type": "string", + "description": "The environment UUID. You need to provide at least one of environment_name or environment_uuid." }, "dockerfile": { "type": "string", @@ -1305,6 +1325,7 @@ "project_uuid", "server_uuid", "environment_name", + "environment_uuid", "docker_registry_image_name", "ports_exposes" ], @@ -1319,7 +1340,11 @@ }, "environment_name": { "type": "string", - "description": "The environment name." + "description": "The environment name. You need to provide at least one of environment_name or environment_uuid." + }, + "environment_uuid": { + "type": "string", + "description": "The environment UUID. You need to provide at least one of environment_name or environment_uuid." }, "docker_registry_image_name": { "type": "string", @@ -1546,6 +1571,7 @@ "project_uuid", "server_uuid", "environment_name", + "environment_uuid", "docker_compose_raw" ], "properties": { @@ -1559,7 +1585,11 @@ }, "environment_name": { "type": "string", - "description": "The environment name." + "description": "The environment name. You need to provide at least one of environment_name or environment_uuid." + }, + "environment_uuid": { + "type": "string", + "description": "The environment UUID. You need to provide at least one of environment_name or environment_uuid." }, "docker_compose_raw": { "type": "string", @@ -3151,7 +3181,8 @@ "required": [ "server_uuid", "project_uuid", - "environment_name" + "environment_name", + "environment_uuid" ], "properties": { "server_uuid": { @@ -3164,7 +3195,11 @@ }, "environment_name": { "type": "string", - "description": "Name of the environment" + "description": "Name of the environment. You need to provide at least one of environment_name or environment_uuid." + }, + "environment_uuid": { + "type": "string", + "description": "UUID of the environment. You need to provide at least one of environment_name or environment_uuid." }, "postgres_user": { "type": "string", @@ -3287,7 +3322,8 @@ "required": [ "server_uuid", "project_uuid", - "environment_name" + "environment_name", + "environment_uuid" ], "properties": { "server_uuid": { @@ -3300,7 +3336,11 @@ }, "environment_name": { "type": "string", - "description": "Name of the environment" + "description": "Name of the environment. You need to provide at least one of environment_name or environment_uuid." + }, + "environment_uuid": { + "type": "string", + "description": "UUID of the environment. You need to provide at least one of environment_name or environment_uuid." }, "destination_uuid": { "type": "string", @@ -3407,7 +3447,8 @@ "required": [ "server_uuid", "project_uuid", - "environment_name" + "environment_name", + "environment_uuid" ], "properties": { "server_uuid": { @@ -3420,7 +3461,11 @@ }, "environment_name": { "type": "string", - "description": "Name of the environment" + "description": "Name of the environment. You need to provide at least one of environment_name or environment_uuid." + }, + "environment_uuid": { + "type": "string", + "description": "UUID of the environment. You need to provide at least one of environment_name or environment_uuid." }, "destination_uuid": { "type": "string", @@ -3523,7 +3568,8 @@ "required": [ "server_uuid", "project_uuid", - "environment_name" + "environment_name", + "environment_uuid" ], "properties": { "server_uuid": { @@ -3536,7 +3582,11 @@ }, "environment_name": { "type": "string", - "description": "Name of the environment" + "description": "Name of the environment. You need to provide at least one of environment_name or environment_uuid." + }, + "environment_uuid": { + "type": "string", + "description": "UUID of the environment. You need to provide at least one of environment_name or environment_uuid." }, "destination_uuid": { "type": "string", @@ -3643,7 +3693,8 @@ "required": [ "server_uuid", "project_uuid", - "environment_name" + "environment_name", + "environment_uuid" ], "properties": { "server_uuid": { @@ -3656,7 +3707,11 @@ }, "environment_name": { "type": "string", - "description": "Name of the environment" + "description": "Name of the environment. You need to provide at least one of environment_name or environment_uuid." + }, + "environment_uuid": { + "type": "string", + "description": "UUID of the environment. You need to provide at least one of environment_name or environment_uuid." }, "destination_uuid": { "type": "string", @@ -3763,7 +3818,8 @@ "required": [ "server_uuid", "project_uuid", - "environment_name" + "environment_name", + "environment_uuid" ], "properties": { "server_uuid": { @@ -3776,7 +3832,11 @@ }, "environment_name": { "type": "string", - "description": "Name of the environment" + "description": "Name of the environment. You need to provide at least one of environment_name or environment_uuid." + }, + "environment_uuid": { + "type": "string", + "description": "UUID of the environment. You need to provide at least one of environment_name or environment_uuid." }, "destination_uuid": { "type": "string", @@ -3895,7 +3955,8 @@ "required": [ "server_uuid", "project_uuid", - "environment_name" + "environment_name", + "environment_uuid" ], "properties": { "server_uuid": { @@ -3908,7 +3969,11 @@ }, "environment_name": { "type": "string", - "description": "Name of the environment" + "description": "Name of the environment. You need to provide at least one of environment_name or environment_uuid." + }, + "environment_uuid": { + "type": "string", + "description": "UUID of the environment. You need to provide at least one of environment_name or environment_uuid." }, "destination_uuid": { "type": "string", @@ -4027,7 +4092,8 @@ "required": [ "server_uuid", "project_uuid", - "environment_name" + "environment_name", + "environment_uuid" ], "properties": { "server_uuid": { @@ -4040,7 +4106,11 @@ }, "environment_name": { "type": "string", - "description": "Name of the environment" + "description": "Name of the environment. You need to provide at least one of environment_name or environment_uuid." + }, + "environment_uuid": { + "type": "string", + "description": "UUID of the environment. You need to provide at least one of environment_name or environment_uuid." }, "destination_uuid": { "type": "string", @@ -4880,14 +4950,14 @@ ] } }, - "\/projects\/{uuid}\/{environment_name}": { + "\/projects\/{uuid}\/{environment_name_or_uuid}": { "get": { "tags": [ "Projects" ], "summary": "Environment", - "description": "Get environment by name.", - "operationId": "get-environment-by-name", + "description": "Get environment by name or UUID.", + "operationId": "get-environment-by-name-or-uuid", "parameters": [ { "name": "uuid", @@ -4899,9 +4969,9 @@ } }, { - "name": "environment_name", + "name": "environment_name_or_uuid", "in": "path", - "description": "Environment name", + "description": "Environment name or UUID", "required": true, "schema": { "type": "string" @@ -5796,6 +5866,7 @@ "server_uuid", "project_uuid", "environment_name", + "environment_uuid", "type" ], "properties": { @@ -5907,7 +5978,11 @@ }, "environment_name": { "type": "string", - "description": "Environment name." + "description": "Environment name. You need to provide at least one of environment_name or environment_uuid." + }, + "environment_uuid": { + "type": "string", + "description": "Environment UUID. You need to provide at least one of environment_name or environment_uuid." }, "server_uuid": { "type": "string", @@ -7354,13 +7429,10 @@ "uuid": { "type": "string" }, - "application_id": { - "type": "integer" + "resourceable_type": { + "type": "string" }, - "service_id": { - "type": "integer" - }, - "database_id": { + "resourceable_id": { "type": "integer" }, "is_build_time": { diff --git a/openapi.yaml b/openapi.yaml index b20fc0fdc..2d1803113 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -47,6 +47,7 @@ paths: - project_uuid - server_uuid - environment_name + - environment_uuid - git_repository - git_branch - build_pack @@ -60,7 +61,10 @@ paths: description: 'The server UUID.' environment_name: type: string - description: 'The environment name.' + description: 'The environment name. You need to provide at least one of environment_name or environment_uuid.' + environment_uuid: + type: string + description: 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.' git_repository: type: string description: 'The git repository URL.' @@ -278,6 +282,7 @@ paths: - project_uuid - server_uuid - environment_name + - environment_uuid - github_app_uuid - git_repository - git_branch @@ -292,7 +297,10 @@ paths: description: 'The server UUID.' environment_name: type: string - description: 'The environment name.' + description: 'The environment name. You need to provide at least one of environment_name or environment_uuid.' + environment_uuid: + type: string + description: 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.' github_app_uuid: type: string description: 'The Github App UUID.' @@ -513,6 +521,7 @@ paths: - project_uuid - server_uuid - environment_name + - environment_uuid - private_key_uuid - git_repository - git_branch @@ -527,7 +536,10 @@ paths: description: 'The server UUID.' environment_name: type: string - description: 'The environment name.' + description: 'The environment name. You need to provide at least one of environment_name or environment_uuid.' + environment_uuid: + type: string + description: 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.' private_key_uuid: type: string description: 'The private key UUID.' @@ -748,6 +760,7 @@ paths: - project_uuid - server_uuid - environment_name + - environment_uuid - dockerfile properties: project_uuid: @@ -758,7 +771,10 @@ paths: description: 'The server UUID.' environment_name: type: string - description: 'The environment name.' + description: 'The environment name. You need to provide at least one of environment_name or environment_uuid.' + environment_uuid: + type: string + description: 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.' dockerfile: type: string description: 'The Dockerfile content.' @@ -930,6 +946,7 @@ paths: - project_uuid - server_uuid - environment_name + - environment_uuid - docker_registry_image_name - ports_exposes properties: @@ -941,7 +958,10 @@ paths: description: 'The server UUID.' environment_name: type: string - description: 'The environment name.' + description: 'The environment name. You need to provide at least one of environment_name or environment_uuid.' + environment_uuid: + type: string + description: 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.' docker_registry_image_name: type: string description: 'The docker registry image name.' @@ -1103,6 +1123,7 @@ paths: - project_uuid - server_uuid - environment_name + - environment_uuid - docker_compose_raw properties: project_uuid: @@ -1113,7 +1134,10 @@ paths: description: 'The server UUID.' environment_name: type: string - description: 'The environment name.' + description: 'The environment name. You need to provide at least one of environment_name or environment_uuid.' + environment_uuid: + type: string + description: 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.' docker_compose_raw: type: string description: 'The Docker Compose raw content.' @@ -2173,6 +2197,7 @@ paths: - server_uuid - project_uuid - environment_name + - environment_uuid properties: server_uuid: type: string @@ -2182,7 +2207,10 @@ paths: description: 'UUID of the project' environment_name: type: string - description: 'Name of the environment' + description: 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.' + environment_uuid: + type: string + description: 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.' postgres_user: type: string description: 'PostgreSQL user' @@ -2271,6 +2299,7 @@ paths: - server_uuid - project_uuid - environment_name + - environment_uuid properties: server_uuid: type: string @@ -2280,7 +2309,10 @@ paths: description: 'UUID of the project' environment_name: type: string - description: 'Name of the environment' + description: 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.' + environment_uuid: + type: string + description: 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.' destination_uuid: type: string description: 'UUID of the destination if the server has multiple destinations' @@ -2357,6 +2389,7 @@ paths: - server_uuid - project_uuid - environment_name + - environment_uuid properties: server_uuid: type: string @@ -2366,7 +2399,10 @@ paths: description: 'UUID of the project' environment_name: type: string - description: 'Name of the environment' + description: 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.' + environment_uuid: + type: string + description: 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.' destination_uuid: type: string description: 'UUID of the destination if the server has multiple destinations' @@ -2440,6 +2476,7 @@ paths: - server_uuid - project_uuid - environment_name + - environment_uuid properties: server_uuid: type: string @@ -2449,7 +2486,10 @@ paths: description: 'UUID of the project' environment_name: type: string - description: 'Name of the environment' + description: 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.' + environment_uuid: + type: string + description: 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.' destination_uuid: type: string description: 'UUID of the destination if the server has multiple destinations' @@ -2526,6 +2566,7 @@ paths: - server_uuid - project_uuid - environment_name + - environment_uuid properties: server_uuid: type: string @@ -2535,7 +2576,10 @@ paths: description: 'UUID of the project' environment_name: type: string - description: 'Name of the environment' + description: 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.' + environment_uuid: + type: string + description: 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.' destination_uuid: type: string description: 'UUID of the destination if the server has multiple destinations' @@ -2612,6 +2656,7 @@ paths: - server_uuid - project_uuid - environment_name + - environment_uuid properties: server_uuid: type: string @@ -2621,7 +2666,10 @@ paths: description: 'UUID of the project' environment_name: type: string - description: 'Name of the environment' + description: 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.' + environment_uuid: + type: string + description: 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.' destination_uuid: type: string description: 'UUID of the destination if the server has multiple destinations' @@ -2707,6 +2755,7 @@ paths: - server_uuid - project_uuid - environment_name + - environment_uuid properties: server_uuid: type: string @@ -2716,7 +2765,10 @@ paths: description: 'UUID of the project' environment_name: type: string - description: 'Name of the environment' + description: 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.' + environment_uuid: + type: string + description: 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.' destination_uuid: type: string description: 'UUID of the destination if the server has multiple destinations' @@ -2802,6 +2854,7 @@ paths: - server_uuid - project_uuid - environment_name + - environment_uuid properties: server_uuid: type: string @@ -2811,7 +2864,10 @@ paths: description: 'UUID of the project' environment_name: type: string - description: 'Name of the environment' + description: 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.' + environment_uuid: + type: string + description: 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.' destination_uuid: type: string description: 'UUID of the destination if the server has multiple destinations' @@ -3329,13 +3385,13 @@ paths: security: - bearerAuth: [] - '/projects/{uuid}/{environment_name}': + '/projects/{uuid}/{environment_name_or_uuid}': get: tags: - Projects summary: Environment - description: 'Get environment by name.' - operationId: get-environment-by-name + description: 'Get environment by name or UUID.' + operationId: get-environment-by-name-or-uuid parameters: - name: uuid @@ -3345,9 +3401,9 @@ paths: schema: type: string - - name: environment_name + name: environment_name_or_uuid in: path - description: 'Environment name' + description: 'Environment name or UUID' required: true schema: type: string @@ -3908,6 +3964,7 @@ paths: - server_uuid - project_uuid - environment_name + - environment_uuid - type properties: type: @@ -3927,7 +3984,10 @@ paths: description: 'Project UUID.' environment_name: type: string - description: 'Environment name.' + description: 'Environment name. You need to provide at least one of environment_name or environment_uuid.' + environment_uuid: + type: string + description: 'Environment UUID. You need to provide at least one of environment_name or environment_uuid.' server_uuid: type: string description: 'Server UUID.' @@ -4892,11 +4952,9 @@ components: type: integer uuid: type: string - application_id: - type: integer - service_id: - type: integer - database_id: + resourceable_type: + type: string + resourceable_id: type: integer is_build_time: type: boolean diff --git a/other/nightly/docker-compose.prod.yml b/other/nightly/docker-compose.prod.yml index d86b2336b..f9cded363 100644 --- a/other/nightly/docker-compose.prod.yml +++ b/other/nightly/docker-compose.prod.yml @@ -54,7 +54,7 @@ services: - SSH_MUX_ENABLED - SSH_MUX_PERSIST_TIME ports: - - "${APP_PORT:-8000}:80" + - "${APP_PORT:-8000}:8080" expose: - "${APP_PORT:-8000}" healthcheck: @@ -93,7 +93,7 @@ services: retries: 10 timeout: 2s soketi: - image: 'ghcr.io/coollabsio/coolify-realtime:1.0.4' + image: 'ghcr.io/coollabsio/coolify-realtime:next' ports: - "${SOKETI_PORT:-6001}:6001" - "6002:6002" diff --git a/resources/css/app.css b/resources/css/app.css index 32d476c1a..6e7704eb7 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -6,11 +6,11 @@ html, body { - @apply h-full bg-neutral-50 text-neutral-800 dark:bg-base dark:text-neutral-400 w-full; + @apply min-h-full bg-neutral-50 dark:bg-base dark:text-neutral-400 w-full; } body { - @apply text-sm antialiased scrollbar; + @apply text-sm antialiased scrollbar min-h-screen; } .apexcharts-tooltip { diff --git a/resources/views/components/forms/button.blade.php b/resources/views/components/forms/button.blade.php index a109e84ac..96cdb4420 100644 --- a/resources/views/components/forms/button.blade.php +++ b/resources/views/components/forms/button.blade.php @@ -8,11 +8,13 @@ @endisset> {{ $slot }} - @if ($attributes->whereStartsWith('wire:click')->first()) - - @elseif($attributes->whereStartsWith('wire:target')->first()) - + @if ($showLoadingIndicator) + @if ($attributes->whereStartsWith('wire:click')->first()) + + @elseif($attributes->whereStartsWith('wire:target')->first()) + + @endif @endif diff --git a/resources/views/components/loading.blade.php b/resources/views/components/loading.blade.php index 7ee3b1d1a..80a47375e 100644 --- a/resources/views/components/loading.blade.php +++ b/resources/views/components/loading.blade.php @@ -3,8 +3,8 @@ @if (isset($text))
{{ $text }}
@endif - + diff --git a/resources/views/components/navbar.blade.php b/resources/views/components/navbar.blade.php index 7d343c645..9a75200b1 100644 --- a/resources/views/components/navbar.blade.php +++ b/resources/views/components/navbar.blade.php @@ -340,17 +340,19 @@ @endif - @if (isCloud() && isInstanceAdmin()) -
  • - - - - - Admin - -
  • + @if (isCloud() || isDev()) + @if (isInstanceAdmin() || session('impersonating')) +
  • + + + + + Admin + +
  • + @endif @endif
    @if (isInstanceAdmin() && !isCloud()) diff --git a/resources/views/components/resources/breadcrumbs.blade.php b/resources/views/components/resources/breadcrumbs.blade.php index f91e04037..5f7029fd0 100644 --- a/resources/views/components/resources/breadcrumbs.blade.php +++ b/resources/views/components/resources/breadcrumbs.blade.php @@ -21,7 +21,10 @@
  • {{ data_get($resource, 'environment.name') }} + href="{{ route('project.resource.index', [ + 'environment_uuid' => data_get($resource, 'environment.uuid'), + 'project_uuid' => data_get($resource, 'environment.project.uuid'), + ]) }}">{{ data_get($resource, 'environment.name') }}
    + diff --git a/resources/views/components/status/running.blade.php b/resources/views/components/status/running.blade.php index 0e0f08fa4..4e5f0c275 100644 --- a/resources/views/components/status/running.blade.php +++ b/resources/views/components/status/running.blade.php @@ -5,30 +5,42 @@ 'noLoading' => false, ])
    - @if (!$noLoading) - - @endif - -
    -
    - @if ($lastDeploymentLink) - - {{ str($status)->before(':')->headline() }} - - @else - {{ str($status)->before(':')->headline() }} - @endif -
    - @if (!str($status)->startsWith('Proxy') && !str($status)->contains('(')) - @if (str($status)->contains('unhealthy')) - +
    + +
    +
    + +
    +
    +
    + @if ($lastDeploymentLink) + + {{ str($status)->before(':')->headline() }} + + @else + {{ str($status)->before(':')->headline() }} + @endif +
    + @php + $showUnhealthyHelper = + !str($status)->startsWith('Proxy') && + !str($status)->contains('(') && + str($status)->contains('unhealthy'); + @endphp + @if ($showUnhealthyHelper) + - @endif - @endif - +
    +
    diff --git a/resources/views/livewire/admin/index.blade.php b/resources/views/livewire/admin/index.blade.php index 06914e1e4..7bf3c7c0c 100644 --- a/resources/views/livewire/admin/index.blade.php +++ b/resources/views/livewire/admin/index.blade.php @@ -1,7 +1,12 @@

    Admin Dashboard

    -

    Who am I now?

    -
    {{ auth()->user()->name }}
    +
    +

    Who am I now?

    + @if (session('impersonating')) + Go back to root + @endif +
    +
    {{ auth()->user()->name }} ({{ auth()->user()->email }})
    Search diff --git a/resources/views/livewire/dashboard.blade.php b/resources/views/livewire/dashboard.blade.php index decd75c46..decebbac8 100644 --- a/resources/views/livewire/dashboard.blade.php +++ b/resources/views/livewire/dashboard.blade.php @@ -25,7 +25,7 @@
    @foreach ($projects as $project) diff --git a/resources/views/livewire/project/application/configuration.blade.php b/resources/views/livewire/project/application/configuration.blade.php index b5ba6c822..b8f4b96f6 100644 --- a/resources/views/livewire/project/application/configuration.blade.php +++ b/resources/views/livewire/project/application/configuration.blade.php @@ -5,34 +5,36 @@

    Configuration

    -
    + +
    - General + General + Advanced @if ($application->destination->server->isSwarm()) - Swarm + $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" + wire:navigate>Swarm Configuration @endif - Advanced - @if ($application->build_pack !== 'static') - Environment - Variables - @endif - @if ($application->build_pack !== 'static') - Storages - - @endif + Environment Variables + Persistent Storage @if ($application->git_based()) - Source + Git Source @endif - Servers + Servers @if (str($application->status)->contains('degraded')) @@ -50,102 +52,74 @@ @endif - Scheduled Tasks - + Scheduled Tasks + Webhooks + Preview Deployments + Healthcheck + Rollback + Resource Limits - Webhooks - - @if ($application->git_based()) - Preview - Deployments - - @endif - @if ($application->build_pack !== 'static' && $application->build_pack !== 'dockercompose') - Healthchecks - - @endif - Rollback - - @if ($application->build_pack !== 'dockercompose') - Resource Limits - - @endif - Resource Operations - - Metrics - - Tags - - Danger Zone - + Resource Operations + Metrics + Tags + Danger Zone
    -
    + @if (request()->route()->getName() === 'project.application.configuration') -
    -
    + @elseif (request()->route()->getName() === 'project.application.swarm' && $application->destination->server->isSwarm()) -
    -
    + @elseif (request()->route()->getName() === 'project.application.advanced') -
    -
    + @elseif (request()->route()->getName() === 'project.application.environment-variables') -
    - @if ($application->git_based()) -
    - -
    - @endif -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    + @elseif (request()->route()->getName() === 'project.application.persistent-storage') + + @elseif (request()->route()->getName() === 'project.application.source' && $application->git_based()) + + @elseif (request()->route()->getName() === 'project.application.servers') + + @elseif (request()->route()->getName() === 'project.application.scheduled-tasks.show') -
    -
    + @elseif (request()->route()->getName() === 'project.application.webhooks') + + @elseif (request()->route()->getName() === 'project.application.preview-deployments') + + @elseif (request()->route()->getName() === 'project.application.healthcheck') + + @elseif (request()->route()->getName() === 'project.application.rollback') + + @elseif (request()->route()->getName() === 'project.application.resource-limits') + + @elseif (request()->route()->getName() === 'project.application.resource-operations') -
    -
    + @elseif (request()->route()->getName() === 'project.application.metrics') -
    -
    - -
    -
    + @elseif (request()->route()->getName() === 'project.application.tags') + + @elseif (request()->route()->getName() === 'project.application.danger') -
    + @endif
    diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index 4812fd4ba..bdcde8deb 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -84,7 +84,7 @@ @@ -161,8 +161,7 @@
    Nixpacks will detect the required configuration automatically. - Framework + Framework Specific Docs
    @endif diff --git a/resources/views/livewire/project/application/heading.blade.php b/resources/views/livewire/project/application/heading.blade.php index 8f3d1d15c..aecab0265 100644 --- a/resources/views/livewire/project/application/heading.blade.php +++ b/resources/views/livewire/project/application/heading.blade.php @@ -2,17 +2,21 @@