diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index f02c4255d..6e7370bb3 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([ @@ -1893,8 +1896,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) { @@ -1921,7 +1925,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) { @@ -2064,6 +2068,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', @@ -2085,8 +2090,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) { @@ -2111,10 +2117,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) { @@ -2139,12 +2147,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( @@ -2257,8 +2268,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.', @@ -2272,6 +2285,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([ @@ -2279,7 +2294,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.', @@ -2293,6 +2308,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([ @@ -2380,7 +2397,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/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index bcaba7107..f5ecdcf28 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([ @@ -333,7 +336,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, ]); @@ -673,7 +677,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 +804,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 +914,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 +1001,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/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/CloneMe.php b/app/Livewire/Project/CloneMe.php index 4d2bc6589..a8e3e6cab 100644 --- a/app/Livewire/Project/CloneMe.php +++ b/app/Livewire/Project/CloneMe.php @@ -119,7 +119,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 +145,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(); } diff --git a/app/Livewire/Project/New/DockerCompose.php b/app/Livewire/Project/New/DockerCompose.php index 199a20cf6..e6667e6f3 100644 --- a/app/Livewire/Project/New/DockerCompose.php +++ b/app/Livewire/Project/New/DockerCompose.php @@ -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"; diff --git a/app/Livewire/Project/Resource/Create.php b/app/Livewire/Project/Resource/Create.php index 9266a57fc..4fbda2211 100644 --- a/app/Livewire/Project/Resource/Create.php +++ b/app/Livewire/Project/Resource/Create.php @@ -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, ]); 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 2bccde0e9..4b66bfdcb 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php @@ -20,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', @@ -27,39 +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->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() @@ -103,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; @@ -122,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 de7bb3c05..98b31641a 100644 --- a/app/Livewire/Project/Shared/ResourceOperations.php +++ b/app/Livewire/Project/Shared/ResourceOperations.php @@ -60,7 +60,8 @@ 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(); } diff --git a/app/Models/Application.php b/app/Models/Application.php index bfb2a1041..6413c8fea 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -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/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/Service.php b/app/Models/Service.php index 5a2690490..c605f189b 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, ]); } @@ -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..ec56d0fdd 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 @@ -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..683c9acb4 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 @@ -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..e239020ac 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 @@ -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..c010cf441 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 @@ -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..e18317088 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 @@ -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..e6c976a8a 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 @@ -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..141768a7c 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 @@ -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..df1681fc9 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 @@ -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/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/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/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/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/server/navbar.blade.php b/resources/views/components/server/navbar.blade.php index 518831c83..fe0b02ce8 100644 --- a/resources/views/components/server/navbar.blade.php +++ b/resources/views/components/server/navbar.blade.php @@ -49,5 +49,6 @@
+ 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/project/database/heading.blade.php b/resources/views/livewire/project/database/heading.blade.php index e334b2ce9..237d679c6 100644 --- a/resources/views/livewire/project/database/heading.blade.php +++ b/resources/views/livewire/project/database/heading.blade.php @@ -3,7 +3,7 @@ Database Startup - + - @foreach ($resource->environment_variables_preview as $env) + {{-- @foreach ($resource->environment_variables_preview as $env) - @endforeach + @endforeach --}} @endif @else
diff --git a/resources/views/livewire/project/shared/environment-variable/show.blade.php b/resources/views/livewire/project/shared/environment-variable/show.blade.php index 96d1331ed..a005728ed 100644 --- a/resources/views/livewire/project/shared/environment-variable/show.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/show.blade.php @@ -1,17 +1,14 @@
- $env->is_really_required, - 'dark:border-coolgray-300' => !$env->is_really_required, - ]) - > + $is_really_required, + 'dark:border-coolgray-300' => !$is_really_required, + ])> @if ($isLocked)
- + - + @@ -25,49 +22,49 @@ @else @if ($isDisabled)
- - - @if ($env->is_shared) - + + + @if ($is_shared) + @endif
@else
- @if ($env->is_multiline) - - + @if ($is_multiline) + + @else - - + + @endif - @if ($env->is_shared) - + @if ($is_shared) + @endif
@endif
@if ($type === 'service') - @else - @if ($env->is_shared) - - @else @if ($isSharedVariable) - + @else - - - @if (!data_get($env, 'is_multiline')) - + @if ($is_multiline === false) + @endif @@ -84,7 +81,7 @@ @@ -97,7 +94,7 @@ diff --git a/resources/views/livewire/server/proxy/deploy.blade.php b/resources/views/livewire/server/proxy/deploy.blade.php index 8882704cc..5be8d36a5 100644 --- a/resources/views/livewire/server/proxy/deploy.blade.php +++ b/resources/views/livewire/server/proxy/deploy.blade.php @@ -20,17 +20,12 @@ @endif - + - + $wire.$on('checkProxyEvent', () => { - $wire.$dispatch('info', 'Starting proxy.'); $wire.$call('checkProxy'); }); $wire.$on('restartEvent', () => { diff --git a/resources/views/livewire/server/proxy/status.blade.php b/resources/views/livewire/server/proxy/status.blade.php index 4dce706e8..5aa3ca765 100644 --- a/resources/views/livewire/server/proxy/status.blade.php +++ b/resources/views/livewire/server/proxy/status.blade.php @@ -1,14 +1,15 @@
+ Refresh @if (data_get($server, 'proxy.status') === 'running') - + @elseif (data_get($server, 'proxy.status') === 'restarting') - + @elseif (data_get($server, 'proxy.force_stop')) - + @elseif (data_get($server, 'proxy.status') === 'exited') - + @else - + @endif - Refresh +