feat: migrate env variables to polymorphic relationship

fix: proxy status query ui
This commit is contained in:
Andras Bacsai
2024-12-17 10:38:32 +01:00
parent 7edd2285b5
commit 2a9d499251
33 changed files with 625 additions and 374 deletions

View File

@@ -27,6 +27,9 @@ class ApplicationsController extends Controller
{ {
$application->makeHidden([ $application->makeHidden([
'id', 'id',
'resourceable',
'resourceable_id',
'resourceable_type',
]); ]);
if (request()->attributes->get('can_read_sensitive', false) === false) { if (request()->attributes->get('can_read_sensitive', false) === false) {
$application->makeHidden([ $application->makeHidden([
@@ -1893,8 +1896,9 @@ class ApplicationsController extends Controller
$is_preview = $request->is_preview ?? false; $is_preview = $request->is_preview ?? false;
$is_build_time = $request->is_build_time ?? false; $is_build_time = $request->is_build_time ?? false;
$is_literal = $request->is_literal ?? false; $is_literal = $request->is_literal ?? false;
$key = str($request->key)->trim()->replace(' ', '_')->value;
if ($is_preview) { if ($is_preview) {
$env = $application->environment_variables_preview->where('key', $request->key)->first(); $env = $application->environment_variables_preview->where('key', $key)->first();
if ($env) { if ($env) {
$env->value = $request->value; $env->value = $request->value;
if ($env->is_build_time != $is_build_time) { if ($env->is_build_time != $is_build_time) {
@@ -1921,7 +1925,7 @@ class ApplicationsController extends Controller
], 404); ], 404);
} }
} else { } else {
$env = $application->environment_variables->where('key', $request->key)->first(); $env = $application->environment_variables->where('key', $key)->first();
if ($env) { if ($env) {
$env->value = $request->value; $env->value = $request->value;
if ($env->is_build_time != $is_build_time) { if ($env->is_build_time != $is_build_time) {
@@ -2064,6 +2068,7 @@ class ApplicationsController extends Controller
$bulk_data = collect($bulk_data)->map(function ($item) { $bulk_data = collect($bulk_data)->map(function ($item) {
return collect($item)->only(['key', 'value', 'is_preview', 'is_build_time', 'is_literal']); return collect($item)->only(['key', 'value', 'is_preview', 'is_build_time', 'is_literal']);
}); });
$returnedEnvs = collect();
foreach ($bulk_data as $item) { foreach ($bulk_data as $item) {
$validator = customApiValidator($item, [ $validator = customApiValidator($item, [
'key' => 'string|required', 'key' => 'string|required',
@@ -2085,8 +2090,9 @@ class ApplicationsController extends Controller
$is_literal = $item->get('is_literal') ?? false; $is_literal = $item->get('is_literal') ?? false;
$is_multi_line = $item->get('is_multiline') ?? false; $is_multi_line = $item->get('is_multiline') ?? false;
$is_shown_once = $item->get('is_shown_once') ?? false; $is_shown_once = $item->get('is_shown_once') ?? false;
$key = str($item->get('key'))->trim()->replace(' ', '_')->value;
if ($is_preview) { 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) { if ($env) {
$env->value = $item->get('value'); $env->value = $item->get('value');
if ($env->is_build_time != $is_build_time) { if ($env->is_build_time != $is_build_time) {
@@ -2111,10 +2117,12 @@ class ApplicationsController extends Controller
'is_literal' => $is_literal, 'is_literal' => $is_literal,
'is_multiline' => $is_multi_line, 'is_multiline' => $is_multi_line,
'is_shown_once' => $is_shown_once, 'is_shown_once' => $is_shown_once,
'resourceable_type' => get_class($application),
'resourceable_id' => $application->id,
]); ]);
} }
} else { } else {
$env = $application->environment_variables->where('key', $item->get('key'))->first(); $env = $application->environment_variables->where('key', $key)->first();
if ($env) { if ($env) {
$env->value = $item->get('value'); $env->value = $item->get('value');
if ($env->is_build_time != $is_build_time) { if ($env->is_build_time != $is_build_time) {
@@ -2139,12 +2147,15 @@ class ApplicationsController extends Controller
'is_literal' => $is_literal, 'is_literal' => $is_literal,
'is_multiline' => $is_multi_line, 'is_multiline' => $is_multi_line,
'is_shown_once' => $is_shown_once, '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( #[OA\Post(
@@ -2257,8 +2268,10 @@ class ApplicationsController extends Controller
], 422); ], 422);
} }
$is_preview = $request->is_preview ?? false; $is_preview = $request->is_preview ?? false;
$key = str($request->key)->trim()->replace(' ', '_')->value;
if ($is_preview) { if ($is_preview) {
$env = $application->environment_variables_preview->where('key', $request->key)->first(); $env = $application->environment_variables_preview->where('key', $key)->first();
if ($env) { if ($env) {
return response()->json([ return response()->json([
'message' => 'Environment variable already exists. Use PATCH request to update it.', '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_literal' => $request->is_literal ?? false,
'is_multiline' => $request->is_multiline ?? false, 'is_multiline' => $request->is_multiline ?? false,
'is_shown_once' => $request->is_shown_once ?? false, 'is_shown_once' => $request->is_shown_once ?? false,
'resourceable_type' => get_class($application),
'resourceable_id' => $application->id,
]); ]);
return response()->json([ return response()->json([
@@ -2279,7 +2294,7 @@ class ApplicationsController extends Controller
])->setStatusCode(201); ])->setStatusCode(201);
} }
} else { } else {
$env = $application->environment_variables->where('key', $request->key)->first(); $env = $application->environment_variables->where('key', $key)->first();
if ($env) { if ($env) {
return response()->json([ return response()->json([
'message' => 'Environment variable already exists. Use PATCH request to update it.', '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_literal' => $request->is_literal ?? false,
'is_multiline' => $request->is_multiline ?? false, 'is_multiline' => $request->is_multiline ?? false,
'is_shown_once' => $request->is_shown_once ?? false, 'is_shown_once' => $request->is_shown_once ?? false,
'resourceable_type' => get_class($application),
'resourceable_id' => $application->id,
]); ]);
return response()->json([ return response()->json([
@@ -2380,7 +2397,10 @@ class ApplicationsController extends Controller
'message' => 'Application not found.', 'message' => 'Application not found.',
], 404); ], 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) { if (! $found_env) {
return response()->json([ return response()->json([
'message' => 'Environment variable not found.', 'message' => 'Environment variable not found.',

View File

@@ -20,6 +20,9 @@ class ServicesController extends Controller
{ {
$service->makeHidden([ $service->makeHidden([
'id', 'id',
'resourceable',
'resourceable_id',
'resourceable_type',
]); ]);
if (request()->attributes->get('can_read_sensitive', false) === false) { if (request()->attributes->get('can_read_sensitive', false) === false) {
$service->makeHidden([ $service->makeHidden([
@@ -333,7 +336,8 @@ class ServicesController extends Controller
EnvironmentVariable::create([ EnvironmentVariable::create([
'key' => $key, 'key' => $key,
'value' => $generatedValue, 'value' => $generatedValue,
'service_id' => $service->id, 'resourceable_id' => $service->id,
'resourceable_type' => $service->getMorphClass(),
'is_build_time' => false, 'is_build_time' => false,
'is_preview' => false, 'is_preview' => false,
]); ]);
@@ -673,7 +677,8 @@ class ServicesController extends Controller
], 422); ], 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) { if (! $env) {
return response()->json(['message' => 'Environment variable not found.'], 404); return response()->json(['message' => 'Environment variable not found.'], 404);
} }
@@ -799,9 +804,9 @@ class ServicesController extends Controller
'errors' => $validator->errors(), 'errors' => $validator->errors(),
], 422); ], 422);
} }
$key = str($item['key'])->trim()->replace(' ', '_')->value;
$env = $service->environment_variables()->updateOrCreate( $env = $service->environment_variables()->updateOrCreate(
['key' => $item['key']], ['key' => $key],
$item $item
); );
@@ -909,7 +914,8 @@ class ServicesController extends Controller
], 422); ], 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) { if ($existingEnv) {
return response()->json([ return response()->json([
'message' => 'Environment variable already exists. Use PATCH request to update it.', '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) $env = EnvironmentVariable::where('uuid', $request->env_uuid)
->where('service_id', $service->id) ->where('resourceable_type', Service::class)
->where('resourceable_id', $service->id)
->first(); ->first();
if (! $env) { if (! $env) {

View File

@@ -23,7 +23,7 @@ class DeploymentNavbar extends Component
public function mount() 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->server = $this->application->destination->server;
$this->is_debug_enabled = $this->application->settings->is_debug_enabled; $this->is_debug_enabled = $this->application->settings->is_debug_enabled;
} }

View File

@@ -119,7 +119,7 @@ class CloneMe extends Component
$environmentVaribles = $application->environment_variables()->get(); $environmentVaribles = $application->environment_variables()->get();
foreach ($environmentVaribles as $environmentVarible) { foreach ($environmentVaribles as $environmentVarible) {
$newEnvironmentVariable = $environmentVarible->replicate()->fill([ $newEnvironmentVariable = $environmentVarible->replicate()->fill([
'application_id' => $newApplication->id, 'resourceable_id' => $newApplication->id,
]); ]);
$newEnvironmentVariable->save(); $newEnvironmentVariable->save();
} }
@@ -145,17 +145,8 @@ class CloneMe extends Component
$environmentVaribles = $database->environment_variables()->get(); $environmentVaribles = $database->environment_variables()->get();
foreach ($environmentVaribles as $environmentVarible) { foreach ($environmentVaribles as $environmentVarible) {
$payload = []; $payload = [];
if ($database->type() === 'standalone-postgresql') { $payload['resourceable_id'] = $newDatabase->id;
$payload['standalone_postgresql_id'] = $newDatabase->id; $payload['resourceable_type'] = $newDatabase->getMorphClass();
} 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;
}
$newEnvironmentVariable = $environmentVarible->replicate()->fill($payload); $newEnvironmentVariable = $environmentVarible->replicate()->fill($payload);
$newEnvironmentVariable->save(); $newEnvironmentVariable->save();
} }

View File

@@ -87,7 +87,8 @@ class DockerCompose extends Component
'value' => $variable, 'value' => $variable,
'is_build_time' => false, 'is_build_time' => false,
'is_preview' => false, 'is_preview' => false,
'service_id' => $service->id, 'resourceable_id' => $service->id,
'resourceable_type' => $service->getMorphClass(),
]); ]);
} }
$service->name = "service-$service->uuid"; $service->name = "service-$service->uuid";

View File

@@ -95,7 +95,8 @@ class Create extends Component
EnvironmentVariable::create([ EnvironmentVariable::create([
'key' => $key, 'key' => $key,
'value' => $value, 'value' => $value,
'service_id' => $service->id, 'resourceable_id' => $service->id,
'resourceable_type' => $service->getMorphClass(),
'is_build_time' => false, 'is_build_time' => false,
'is_preview' => false, 'is_preview' => false,
]); ]);

View File

@@ -4,7 +4,6 @@ namespace App\Livewire\Project\Shared\EnvironmentVariable;
use App\Models\EnvironmentVariable; use App\Models\EnvironmentVariable;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2;
class All extends Component class All extends Component
{ {
@@ -14,38 +13,35 @@ class All extends Component
public bool $showPreview = false; public bool $showPreview = false;
public ?string $modalId = null;
public ?string $variables = null; public ?string $variables = null;
public ?string $variablesPreview = null; public ?string $variablesPreview = null;
public string $view = 'normal'; public string $view = 'normal';
public bool $is_env_sorting_enabled = false;
protected $listeners = [ protected $listeners = [
'saveKey' => 'submit', 'saveKey' => 'submit',
'refreshEnvs', 'refreshEnvs',
'environmentVariableDeleted' => 'refreshEnvs', 'environmentVariableDeleted' => 'refreshEnvs',
]; ];
protected $rules = [
'resource.settings.is_env_sorting_enabled' => 'required|boolean',
];
public function mount() public function mount()
{ {
$this->is_env_sorting_enabled = data_get($this->resource, 'settings.is_env_sorting_enabled', false);
$this->resourceClass = get_class($this->resource); $this->resourceClass = get_class($this->resource);
$resourceWithPreviews = [\App\Models\Application::class]; $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) { if (str($this->resourceClass)->contains($resourceWithPreviews) && ! $simpleDockerfile) {
$this->showPreview = true; $this->showPreview = true;
} }
$this->modalId = new Cuid2;
$this->sortEnvironmentVariables(); $this->sortEnvironmentVariables();
} }
public function instantSave() public function instantSave()
{ {
$this->resource->settings->is_env_sorting_enabled = $this->is_env_sorting_enabled;
$this->resource->settings->save(); $this->resource->settings->save();
$this->sortEnvironmentVariables(); $this->sortEnvironmentVariables();
$this->dispatch('success', 'Environment variable settings updated.'); $this->dispatch('success', 'Environment variable settings updated.');
@@ -53,7 +49,7 @@ class All extends Component
public function sortEnvironmentVariables() 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) { if ($this->resource->environment_variables) {
$this->resource->environment_variables = $this->resource->environment_variables->sortBy('order')->values(); $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_multiline = $data['is_multiline'] ?? false;
$environment->is_literal = $data['is_literal'] ?? false; $environment->is_literal = $data['is_literal'] ?? false;
$environment->is_preview = $data['is_preview'] ?? false; $environment->is_preview = $data['is_preview'] ?? false;
$environment->resourceable_id = $this->resource->id;
$resourceType = $this->resource->type(); $environment->resourceable_type = $this->resource->getMorphClass();
$resourceIdField = $this->getResourceIdField($resourceType);
if ($resourceIdField) {
$environment->$resourceIdField = $this->resource->id;
}
return $environment; 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) private function deleteRemovedVariables($isPreview, $variables)
{ {
$method = $isPreview ? 'environment_variables_preview' : 'environment_variables'; $method = $isPreview ? 'environment_variables_preview' : 'environment_variables';
@@ -231,34 +204,14 @@ class All extends Component
$environment->is_build_time = false; $environment->is_build_time = false;
$environment->is_multiline = false; $environment->is_multiline = false;
$environment->is_preview = $isPreview; $environment->is_preview = $isPreview;
$environment->resourceable_id = $this->resource->id;
$environment->resourceable_type = $this->resource->getMorphClass();
$this->setEnvironmentResourceId($environment);
$environment->save(); $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() public function refreshEnvs()
{ {
$this->resource->refresh(); $this->resource->refresh();

View File

@@ -20,6 +20,26 @@ class Show extends Component
public string $type; 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 = [ protected $listeners = [
'refreshEnvs' => 'refresh', 'refreshEnvs' => 'refresh',
'refresh', 'refresh',
@@ -27,39 +47,59 @@ class Show extends Component
]; ];
protected $rules = [ protected $rules = [
'env.key' => 'required|string', 'key' => 'required|string',
'env.value' => 'nullable', 'value' => 'nullable',
'env.is_build_time' => 'required|boolean', 'is_build_time' => 'required|boolean',
'env.is_multiline' => 'required|boolean', 'is_multiline' => 'required|boolean',
'env.is_literal' => 'required|boolean', 'is_literal' => 'required|boolean',
'env.is_shown_once' => 'required|boolean', 'is_shown_once' => 'required|boolean',
'env.real_value' => 'nullable', 'real_value' => 'nullable',
'env.is_required' => 'required|boolean', '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() public function mount()
{ {
$this->syncData();
if ($this->env->getMorphClass() === \App\Models\SharedEnvironmentVariable::class) { if ($this->env->getMorphClass() === \App\Models\SharedEnvironmentVariable::class) {
$this->isSharedVariable = true; $this->isSharedVariable = true;
} }
$this->parameters = get_route_parameters(); $this->parameters = get_route_parameters();
$this->checkEnvs(); $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() public function checkEnvs()
@@ -103,17 +143,17 @@ class Show extends Component
try { try {
if ($this->isSharedVariable) { if ($this->isSharedVariable) {
$this->validate([ $this->validate([
'env.key' => 'required|string', 'key' => 'required|string',
'env.value' => 'nullable', 'value' => 'nullable',
'env.is_shown_once' => 'required|boolean', 'is_shown_once' => 'required|boolean',
]); ]);
} else { } else {
$this->validate(); $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'); $oldValue = $this->env->getOriginal('value');
$this->env->value = $oldValue; $this->value = $oldValue;
$this->dispatch('error', 'Required environment variable cannot be empty.'); $this->dispatch('error', 'Required environment variable cannot be empty.');
return; return;
@@ -122,10 +162,10 @@ class Show extends Component
$this->serialize(); $this->serialize();
if ($this->isSharedVariable) { 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('success', 'Environment variable updated.');
$this->dispatch('envsUpdated'); $this->dispatch('envsUpdated');
} catch (\Exception $e) { } catch (\Exception $e) {

View File

@@ -60,7 +60,8 @@ class ResourceOperations extends Component
$environmentVaribles = $this->resource->environment_variables()->get(); $environmentVaribles = $this->resource->environment_variables()->get();
foreach ($environmentVaribles as $environmentVarible) { foreach ($environmentVaribles as $environmentVarible) {
$newEnvironmentVariable = $environmentVarible->replicate()->fill([ $newEnvironmentVariable = $environmentVarible->replicate()->fill([
'application_id' => $new_resource->id, 'resourceable_id' => $new_resource->id,
'resourceable_type' => $new_resource->getMorphClass(),
]); ]);
$newEnvironmentVariable->save(); $newEnvironmentVariable->save();
} }

View File

@@ -695,46 +695,62 @@ class Application extends BaseModel
return $this->settings->is_static ? [80] : $this->ports_exposes_array; 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()
public function build_environment_variables(): HasMany
{ {
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 public function scheduled_tasks(): HasMany

View File

@@ -14,9 +14,8 @@ use Visus\Cuid2\Cuid2;
properties: [ properties: [
'id' => ['type' => 'integer'], 'id' => ['type' => 'integer'],
'uuid' => ['type' => 'string'], 'uuid' => ['type' => 'string'],
'application_id' => ['type' => 'integer'], 'resourceable_type' => ['type' => 'string'],
'service_id' => ['type' => 'integer'], 'resourceable_id' => ['type' => 'integer'],
'database_id' => ['type' => 'integer'],
'is_build_time' => ['type' => 'boolean'], 'is_build_time' => ['type' => 'boolean'],
'is_literal' => ['type' => 'boolean'], 'is_literal' => ['type' => 'boolean'],
'is_multiline' => ['type' => 'boolean'], 'is_multiline' => ['type' => 'boolean'],
@@ -42,6 +41,8 @@ class EnvironmentVariable extends Model
'is_multiline' => 'boolean', 'is_multiline' => 'boolean',
'is_preview' => 'boolean', 'is_preview' => 'boolean',
'version' => 'string', 'version' => 'string',
'resourceable_type' => 'string',
'resourceable_id' => 'integer',
]; ];
protected $appends = ['real_value', 'is_shared', 'is_really_required']; protected $appends = ['real_value', 'is_shared', 'is_really_required'];
@@ -53,18 +54,25 @@ class EnvironmentVariable extends Model
$model->uuid = (string) new Cuid2; $model->uuid = (string) new Cuid2;
} }
}); });
static::created(function (EnvironmentVariable $environment_variable) { static::created(function (EnvironmentVariable $environment_variable) {
if ($environment_variable->application_id && ! $environment_variable->is_preview) { if ($environment_variable->resourceable_type === Application::class && ! $environment_variable->is_preview) {
$found = ModelsEnvironmentVariable::where('key', $environment_variable->key)->where('application_id', $environment_variable->application_id)->where('is_preview', true)->first(); $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) { if (! $found) {
$application = Application::find($environment_variable->application_id); $application = Application::find($environment_variable->resourceable_id);
if ($application->build_pack !== 'dockerfile') { if ($application && $application->build_pack !== 'dockerfile') {
ModelsEnvironmentVariable::create([ ModelsEnvironmentVariable::create([
'key' => $environment_variable->key, 'key' => $environment_variable->key,
'value' => $environment_variable->value, 'value' => $environment_variable->value,
'is_build_time' => $environment_variable->is_build_time, 'is_build_time' => $environment_variable->is_build_time,
'is_multiline' => $environment_variable->is_multiline ?? false, '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, 'is_preview' => true,
]); ]);
} }
@@ -74,6 +82,7 @@ class EnvironmentVariable extends Model
'version' => config('constants.coolify.version'), 'version' => config('constants.coolify.version'),
]); ]);
}); });
static::saving(function (EnvironmentVariable $environmentVariable) { static::saving(function (EnvironmentVariable $environmentVariable) {
$environmentVariable->updateIsShared(); $environmentVariable->updateIsShared();
}); });
@@ -92,43 +101,32 @@ class EnvironmentVariable extends Model
); );
} }
/**
* Get the parent resourceable model.
*/
public function resourceable()
{
return $this->morphTo();
}
public function resource() public function resource()
{ {
$resource = null; return $this->resourceable;
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;
} }
public function realValue(): Attribute public function realValue(): Attribute
{ {
$resource = $this->resource();
return Attribute::make( return Attribute::make(
get: function () use ($resource) { get: function () {
$env = $this->get_real_environment_variables($this->value, $resource); 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()) { if ($sharedEnvsFound->isEmpty()) {
return $environment_variable; return $environment_variable;
} }
foreach ($sharedEnvsFound as $sharedEnv) { foreach ($sharedEnvsFound as $sharedEnv) {
$type = str($sharedEnv)->match('/(.*?)\./'); $type = str($sharedEnv)->match('/(.*?)\./');
if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) { if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {

View File

@@ -1120,7 +1120,8 @@ class Service extends BaseModel
'key' => $key, 'key' => $key,
'value' => $value, 'value' => $value,
'is_build_time' => false, 'is_build_time' => false,
'service_id' => $this->id, 'resourceable_id' => $this->id,
'resourceable_type' => $this->getMorphClass(),
'is_preview' => false, 'is_preview' => false,
]); ]);
} }
@@ -1232,14 +1233,17 @@ class Service extends BaseModel
return $this->hasMany(ScheduledTask::class)->orderBy('name', 'asc'); 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() public function workdir()

View File

@@ -5,7 +5,6 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
class StandaloneClickhouse extends BaseModel class StandaloneClickhouse extends BaseModel
@@ -251,14 +250,15 @@ class StandaloneClickhouse extends BaseModel
return $this->morphTo(); 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() public function persistentStorages()

View File

@@ -5,7 +5,6 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
class StandaloneDragonfly extends BaseModel class StandaloneDragonfly extends BaseModel
@@ -251,14 +250,9 @@ class StandaloneDragonfly extends BaseModel
return $this->morphTo(); return $this->morphTo();
} }
public function environment_variables(): HasMany public function runtime_environment_variables()
{ {
return $this->hasMany(EnvironmentVariable::class); return $this->morphMany(EnvironmentVariable::class, 'resourceable');
}
public function runtime_environment_variables(): HasMany
{
return $this->hasMany(EnvironmentVariable::class);
} }
public function persistentStorages() public function persistentStorages()
@@ -319,4 +313,10 @@ class StandaloneDragonfly extends BaseModel
{ {
return false; return false;
} }
public function environment_variables()
{
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
->orderBy('key', 'asc');
}
} }

View File

@@ -5,7 +5,6 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
class StandaloneKeydb extends BaseModel class StandaloneKeydb extends BaseModel
@@ -251,14 +250,9 @@ class StandaloneKeydb extends BaseModel
return $this->morphTo(); return $this->morphTo();
} }
public function environment_variables(): HasMany public function runtime_environment_variables()
{ {
return $this->hasMany(EnvironmentVariable::class); return $this->morphMany(EnvironmentVariable::class, 'resourceable');
}
public function runtime_environment_variables(): HasMany
{
return $this->hasMany(EnvironmentVariable::class);
} }
public function persistentStorages() public function persistentStorages()
@@ -319,4 +313,10 @@ class StandaloneKeydb extends BaseModel
{ {
return false; return false;
} }
public function environment_variables()
{
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
->orderBy('key', 'asc');
}
} }

View File

@@ -5,7 +5,6 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
class StandaloneMariadb extends BaseModel class StandaloneMariadb extends BaseModel
@@ -251,14 +250,15 @@ class StandaloneMariadb extends BaseModel
return $this->morphTo(); 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() public function persistentStorages()

View File

@@ -5,7 +5,6 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
class StandaloneMongodb extends BaseModel class StandaloneMongodb extends BaseModel
@@ -271,14 +270,9 @@ class StandaloneMongodb extends BaseModel
return $this->morphTo(); return $this->morphTo();
} }
public function environment_variables(): HasMany public function runtime_environment_variables()
{ {
return $this->hasMany(EnvironmentVariable::class); return $this->morphMany(EnvironmentVariable::class, 'resourceable');
}
public function runtime_environment_variables(): HasMany
{
return $this->hasMany(EnvironmentVariable::class);
} }
public function persistentStorages() public function persistentStorages()
@@ -339,4 +333,10 @@ class StandaloneMongodb extends BaseModel
{ {
return true; return true;
} }
public function environment_variables()
{
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
->orderBy('key', 'asc');
}
} }

View File

@@ -5,7 +5,6 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
class StandaloneMysql extends BaseModel class StandaloneMysql extends BaseModel
@@ -252,14 +251,9 @@ class StandaloneMysql extends BaseModel
return $this->morphTo(); return $this->morphTo();
} }
public function environment_variables(): HasMany public function runtime_environment_variables()
{ {
return $this->hasMany(EnvironmentVariable::class); return $this->morphMany(EnvironmentVariable::class, 'resourceable');
}
public function runtime_environment_variables(): HasMany
{
return $this->hasMany(EnvironmentVariable::class);
} }
public function persistentStorages() public function persistentStorages()
@@ -320,4 +314,10 @@ class StandaloneMysql extends BaseModel
{ {
return true; return true;
} }
public function environment_variables()
{
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
->orderBy('key', 'asc');
}
} }

View File

@@ -5,7 +5,6 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
class StandalonePostgresql extends BaseModel class StandalonePostgresql extends BaseModel
@@ -252,14 +251,9 @@ class StandalonePostgresql extends BaseModel
return $this->morphTo(); return $this->morphTo();
} }
public function environment_variables(): HasMany public function runtime_environment_variables()
{ {
return $this->hasMany(EnvironmentVariable::class); return $this->morphMany(EnvironmentVariable::class, 'resourceable');
}
public function runtime_environment_variables(): HasMany
{
return $this->hasMany(EnvironmentVariable::class);
} }
public function persistentStorages() public function persistentStorages()
@@ -320,4 +314,10 @@ class StandalonePostgresql extends BaseModel
return $parsedCollection->toArray(); return $parsedCollection->toArray();
} }
public function environment_variables()
{
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
->orderBy('key', 'asc');
}
} }

View File

@@ -5,7 +5,6 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
class StandaloneRedis extends BaseModel class StandaloneRedis extends BaseModel
@@ -262,14 +261,9 @@ class StandaloneRedis extends BaseModel
return $this->morphTo(); return $this->morphTo();
} }
public function environment_variables(): HasMany public function runtime_environment_variables()
{ {
return $this->hasMany(EnvironmentVariable::class); return $this->morphMany(EnvironmentVariable::class, 'resourceable');
}
public function runtime_environment_variables(): HasMany
{
return $this->hasMany(EnvironmentVariable::class);
} }
public function persistentStorages() public function persistentStorages()
@@ -359,4 +353,10 @@ class StandaloneRedis extends BaseModel
} }
); );
} }
public function environment_variables()
{
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
->orderBy('key', 'asc');
}
} }

View File

@@ -15,7 +15,8 @@ class Button extends Component
public bool $disabled = false, public bool $disabled = false,
public bool $noStyle = false, public bool $noStyle = false,
public ?string $modalId = null, public ?string $modalId = null,
public string $defaultClass = 'button' public string $defaultClass = 'button',
public bool $showLoadingIndicator = true,
) { ) {
if ($this->noStyle) { if ($this->noStyle) {
$this->defaultClass = ''; $this->defaultClass = '';

View File

@@ -2,6 +2,7 @@
use App\Models\Application; use App\Models\Application;
use App\Models\EnvironmentVariable; use App\Models\EnvironmentVariable;
use App\Models\Service;
use App\Models\ServiceApplication; use App\Models\ServiceApplication;
use App\Models\ServiceDatabase; use App\Models\ServiceDatabase;
use Illuminate\Support\Stringable; use Illuminate\Support\Stringable;
@@ -119,7 +120,10 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
if ($resourceFqdns->count() === 1) { if ($resourceFqdns->count() === 1) {
$resourceFqdns = $resourceFqdns->first(); $resourceFqdns = $resourceFqdns->first();
$variableName = 'SERVICE_FQDN_'.str($resource->name)->upper()->replace('-', ''); $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); $fqdn = Url::fromString($resourceFqdns);
$port = $fqdn->getPort(); $port = $fqdn->getPort();
$path = $fqdn->getPath(); $path = $fqdn->getPath();
@@ -134,7 +138,10 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
} }
if ($port) { if ($port) {
$variableName = $variableName."_$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 ($generatedEnv) {
if ($path === '/') { if ($path === '/') {
$generatedEnv->value = $fqdn; $generatedEnv->value = $fqdn;
@@ -145,7 +152,10 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
} }
} }
$variableName = 'SERVICE_URL_'.str($resource->name)->upper()->replace('-', ''); $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::fromString($fqdn);
$port = $url->getPort(); $port = $url->getPort();
$path = $url->getPath(); $path = $url->getPath();
@@ -161,7 +171,10 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
} }
if ($port) { if ($port) {
$variableName = $variableName."_$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 ($generatedEnv) {
if ($path === '/') { if ($path === '/') {
$generatedEnv->value = $url; $generatedEnv->value = $url;
@@ -179,10 +192,16 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
$path = $host->getPath(); $path = $host->getPath();
$host = $host->getScheme().'://'.$host->getHost(); $host = $host->getScheme().'://'.$host->getHost();
if ($port) { 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) { foreach ($port_envs as $port_env) {
$service_fqdn = str($port_env->key)->beforeLast('_')->after('SERVICE_FQDN_'); $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 ($env) {
if ($path === '/') { if ($path === '/') {
$env->value = $host; $env->value = $host;
@@ -198,10 +217,16 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
} }
$port_env->save(); $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) { foreach ($port_envs_url as $port_env_url) {
$service_url = str($port_env_url->key)->beforeLast('_')->after('SERVICE_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 ($env) {
if ($path === '/') { if ($path === '/') {
$env->value = $url; $env->value = $url;
@@ -219,7 +244,10 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
} }
} else { } else {
$variableName = 'SERVICE_FQDN_'.str($resource->name)->upper()->replace('-', ''); $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 = Url::fromString($fqdn);
$fqdn = $fqdn->getScheme().'://'.$fqdn->getHost().$fqdn->getPath(); $fqdn = $fqdn->getScheme().'://'.$fqdn->getHost().$fqdn->getPath();
if ($generatedEnv) { if ($generatedEnv) {
@@ -227,7 +255,10 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
$generatedEnv->save(); $generatedEnv->save();
} }
$variableName = 'SERVICE_URL_'.str($resource->name)->upper()->replace('-', ''); $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::fromString($fqdn);
$url = $url->getHost().$url->getPath(); $url = $url->getHost().$url->getPath();
if ($generatedEnv) { if ($generatedEnv) {

View File

@@ -1819,7 +1819,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
'key' => $key, 'key' => $key,
'value' => $fqdn, 'value' => $fqdn,
'is_build_time' => false, 'is_build_time' => false,
'service_id' => $resource->id, 'resourceable_type' => get_class($resource),
'resourceable_id' => $resource->id,
'is_preview' => false, 'is_preview' => false,
]); ]);
} }
@@ -1831,7 +1832,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
} }
$env = EnvironmentVariable::where([ $env = EnvironmentVariable::where([
'key' => $key, 'key' => $key,
'service_id' => $resource->id, 'resourceable_type' => get_class($resource),
'resourceable_id' => $resource->id,
])->first(); ])->first();
if ($env) { if ($env) {
$env_url = Url::fromString($savedService->fqdn); $env_url = Url::fromString($savedService->fqdn);
@@ -1854,14 +1856,16 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if ($value?->startsWith('$')) { if ($value?->startsWith('$')) {
$foundEnv = EnvironmentVariable::where([ $foundEnv = EnvironmentVariable::where([
'key' => $key, 'key' => $key,
'service_id' => $resource->id, 'resourceable_type' => get_class($resource),
'resourceable_id' => $resource->id,
])->first(); ])->first();
$value = replaceVariables($value); $value = replaceVariables($value);
$key = $value; $key = $value;
if ($value->startsWith('SERVICE_')) { if ($value->startsWith('SERVICE_')) {
$foundEnv = EnvironmentVariable::where([ $foundEnv = EnvironmentVariable::where([
'key' => $key, 'key' => $key,
'service_id' => $resource->id, 'resourceable_type' => get_class($resource),
'resourceable_id' => $resource->id,
])->first(); ])->first();
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value); ['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
if (! is_null($command)) { if (! is_null($command)) {
@@ -1895,7 +1899,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
'key' => $key, 'key' => $key,
'value' => $fqdn, 'value' => $fqdn,
'is_build_time' => false, 'is_build_time' => false,
'service_id' => $resource->id, 'resourceable_type' => get_class($resource),
'resourceable_id' => $resource->id,
'is_preview' => false, 'is_preview' => false,
]); ]);
} }
@@ -1912,7 +1917,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
} }
$env = EnvironmentVariable::where([ $env = EnvironmentVariable::where([
'key' => $key, 'key' => $key,
'service_id' => $resource->id, 'resourceable_type' => get_class($resource),
'resourceable_id' => $resource->id,
])->first(); ])->first();
if ($env) { if ($env) {
$env_url = Url::fromString($env->value); $env_url = Url::fromString($env->value);
@@ -1932,7 +1938,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
'key' => $key, 'key' => $key,
'value' => $generatedValue, 'value' => $generatedValue,
'is_build_time' => false, 'is_build_time' => false,
'service_id' => $resource->id, 'resourceable_type' => get_class($resource),
'resourceable_id' => $resource->id,
'is_preview' => false, 'is_preview' => false,
]); ]);
} }
@@ -1957,18 +1964,21 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
} }
$foundEnv = EnvironmentVariable::where([ $foundEnv = EnvironmentVariable::where([
'key' => $key, 'key' => $key,
'service_id' => $resource->id, 'resourceable_type' => get_class($resource),
'resourceable_id' => $resource->id,
])->first(); ])->first();
if ($foundEnv) { if ($foundEnv) {
$defaultValue = data_get($foundEnv, 'value'); $defaultValue = data_get($foundEnv, 'value');
} }
EnvironmentVariable::updateOrCreate([ EnvironmentVariable::updateOrCreate([
'key' => $key, 'key' => $key,
'service_id' => $resource->id, 'resourceable_type' => get_class($resource),
'resourceable_id' => $resource->id,
], [ ], [
'value' => $defaultValue, 'value' => $defaultValue,
'is_build_time' => false, 'is_build_time' => false,
'service_id' => $resource->id, 'resourceable_type' => get_class($resource),
'resourceable_id' => $resource->id,
'is_preview' => false, 'is_preview' => false,
]); ]);
} }
@@ -2831,6 +2841,10 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
data_set($service, 'container_name', $containerName); data_set($service, 'container_name', $containerName);
data_forget($service, 'volumes.*.content'); data_forget($service, 'volumes.*.content');
data_forget($service, 'volumes.*.isDirectory'); 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; return $service;
}); });
@@ -2869,13 +2883,11 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
} }
if ($isApplication) { if ($isApplication) {
$nameOfId = 'application_id';
$pullRequestId = $pull_request_id; $pullRequestId = $pull_request_id;
$isPullRequest = $pullRequestId == 0 ? false : true; $isPullRequest = $pullRequestId == 0 ? false : true;
$server = data_get($resource, 'destination.server'); $server = data_get($resource, 'destination.server');
$fileStorages = $resource->fileStorages(); $fileStorages = $resource->fileStorages();
} elseif ($isService) { } elseif ($isService) {
$nameOfId = 'service_id';
$server = data_get($resource, 'server'); $server = data_get($resource, 'server');
$allServices = get_service_templates(); $allServices = get_service_templates();
} else { } else {
@@ -3042,9 +3054,10 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
} }
if (substr_count(str($key)->value(), '_') === 2) { 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(), 'key' => $key->value(),
$nameOfId => $resource->id, 'resourceable_type' => get_class($resource),
'resourceable_id' => $resource->id,
], [ ], [
'value' => $fqdn, 'value' => $fqdn,
'is_build_time' => false, '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) { if (substr_count(str($key)->value(), '_') === 3) {
$newKey = str($key)->beforeLast('_'); $newKey = str($key)->beforeLast('_');
$resource->environment_variables()->where('key', $newKey->value())->where($nameOfId, $resource->id)->firstOrCreate([ $resource->environment_variables()->firstOrCreate([
'key' => $newKey->value(), 'key' => $newKey->value(),
$nameOfId => $resource->id, 'resourceable_type' => get_class($resource),
'resourceable_id' => $resource->id,
], [ ], [
'value' => $fqdn, 'value' => $fqdn,
'is_build_time' => false, 'is_build_time' => false,
@@ -3071,7 +3085,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$key = str($key); $key = str($key);
$value = replaceVariables($value); $value = replaceVariables($value);
$command = parseCommandFromMagicEnvVariable($key); $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) { if ($found) {
continue; continue;
} }
@@ -3085,9 +3099,10 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
} elseif ($isService) { } elseif ($isService) {
$fqdn = generateFqdn($server, "$fqdnFor-$uuid"); $fqdn = generateFqdn($server, "$fqdnFor-$uuid");
} }
$resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->firstOrCreate([ $resource->environment_variables()->firstOrCreate([
'key' => $key->value(), 'key' => $key->value(),
$nameOfId => $resource->id, 'resourceable_type' => get_class($resource),
'resourceable_id' => $resource->id,
], [ ], [
'value' => $fqdn, 'value' => $fqdn,
'is_build_time' => false, '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 = generateFqdn($server, "$fqdnFor-$uuid");
} }
$fqdn = str($fqdn)->replace('http://', '')->replace('https://', ''); $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(), 'key' => $key->value(),
$nameOfId => $resource->id, 'resourceable_type' => get_class($resource),
'resourceable_id' => $resource->id,
], [ ], [
'value' => $fqdn, 'value' => $fqdn,
'is_build_time' => false, 'is_build_time' => false,
@@ -3114,9 +3130,10 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
]); ]);
} else { } else {
$value = generateEnvValue($command, $resource); $value = generateEnvValue($command, $resource);
$resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->firstOrCreate([ $resource->environment_variables()->firstOrCreate([
'key' => $key->value(), 'key' => $key->value(),
$nameOfId => $resource->id, 'resourceable_type' => get_class($resource),
'resourceable_id' => $resource->id,
], [ ], [
'value' => $value, 'value' => $value,
'is_build_time' => false, 'is_build_time' => false,
@@ -3464,9 +3481,10 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$originalValue = $value; $originalValue = $value;
$parsedValue = replaceVariables($value); $parsedValue = replaceVariables($value);
if ($value->startsWith('$SERVICE_')) { if ($value->startsWith('$SERVICE_')) {
$resource->environment_variables()->where('key', $key)->where($nameOfId, $resource->id)->firstOrCreate([ $resource->environment_variables()->firstOrCreate([
'key' => $key, 'key' => $key,
$nameOfId => $resource->id, 'resourceable_type' => get_class($resource),
'resourceable_id' => $resource->id,
], [ ], [
'value' => $value, 'value' => $value,
'is_build_time' => false, 'is_build_time' => false,
@@ -3480,9 +3498,10 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
} }
if ($key->value() === $parsedValue->value()) { if ($key->value() === $parsedValue->value()) {
$value = null; $value = null;
$resource->environment_variables()->where('key', $key)->where($nameOfId, $resource->id)->firstOrCreate([ $resource->environment_variables()->firstOrCreate([
'key' => $key, 'key' => $key,
$nameOfId => $resource->id, 'resourceable_type' => get_class($resource),
'resourceable_id' => $resource->id,
], [ ], [
'value' => $value, 'value' => $value,
'is_build_time' => false, 'is_build_time' => false,
@@ -3516,22 +3535,24 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
if ($originalValue->value() === $value->value()) { if ($originalValue->value() === $value->value()) {
// This means the variable does not have a default value, so it needs to be created in Coolify // This means the variable does not have a default value, so it needs to be created in Coolify
$parsedKeyValue = replaceVariables($value); $parsedKeyValue = replaceVariables($value);
$resource->environment_variables()->where('key', $parsedKeyValue)->where($nameOfId, $resource->id)->firstOrCreate([ $resource->environment_variables()->firstOrCreate([
'key' => $parsedKeyValue, 'key' => $parsedKeyValue,
$nameOfId => $resource->id, 'resourceable_type' => get_class($resource),
'resourceable_id' => $resource->id,
], [ ], [
'is_build_time' => false, 'is_build_time' => false,
'is_preview' => false, 'is_preview' => false,
'is_required' => $isRequired, 'is_required' => $isRequired,
]); ]);
// Add the variable to the environment so it will be shown in the deployable compose file // 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; continue;
} }
$resource->environment_variables()->where('key', $key)->where($nameOfId, $resource->id)->firstOrCreate([ $resource->environment_variables()->firstOrCreate([
'key' => $key, 'key' => $key,
$nameOfId => $resource->id, 'resourceable_type' => get_class($resource),
'resourceable_id' => $resource->id,
], [ ], [
'value' => $value, 'value' => $value,
'is_build_time' => false, 'is_build_time' => false,

View File

@@ -0,0 +1,165 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('environment_variables', function (Blueprint $table) {
$table->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']);
});
}
};

View File

@@ -8,11 +8,13 @@
@endisset> @endisset>
{{ $slot }} {{ $slot }}
@if ($attributes->whereStartsWith('wire:click')->first()) @if ($showLoadingIndicator)
<x-loading-on-button wire:target="{{ $attributes->whereStartsWith('wire:click')->first() }}" @if ($attributes->whereStartsWith('wire:click')->first())
wire:loading.delay /> <x-loading-on-button wire:target="{{ $attributes->whereStartsWith('wire:click')->first() }}"
@elseif($attributes->whereStartsWith('wire:target')->first()) wire:loading.delay />
<x-loading-on-button wire:target="{{ $attributes->whereStartsWith('wire:target')->first() }}" @elseif($attributes->whereStartsWith('wire:target')->first())
wire:loading.delay /> <x-loading-on-button wire:target="{{ $attributes->whereStartsWith('wire:target')->first() }}"
wire:loading.delay />
@endif
@endif @endif
</button> </button>

View File

@@ -3,8 +3,8 @@
@if (isset($text)) @if (isset($text))
<div>{{ $text }}</div> <div>{{ $text }}</div>
@endif @endif
<svg class="w-4 h-4 mx-1 ml-3 text-coollabs dark:text-warning animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" <svg class="w-4 h-4 mx-1 ml-3 text-coollabs dark:text-warning animate-spin" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"> fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" <path class="opacity-75" fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"> d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">

View File

@@ -49,5 +49,6 @@
<div class="order-first sm:order-last"> <div class="order-first sm:order-last">
<livewire:server.proxy.deploy :server="$server" /> <livewire:server.proxy.deploy :server="$server" />
</div> </div>
</div> </div>
</div> </div>

View File

@@ -5,30 +5,42 @@
'noLoading' => false, 'noLoading' => false,
]) ])
<div class="flex items-center"> <div class="flex items-center">
@if (!$noLoading) <div class="flex items-center">
<x-loading wire:loading.delay.longer /> <span wire:loading.delay.longer>
@endif <div class="badge badge-warning"></div>
<span wire:loading.remove.delay.longer class="flex items-center"> </span>
<div class="badge badge-success "></div> <span wire:loading.remove.delay.longer>
<div class="pl-2 pr-1 text-xs font-bold tracking-wider text-success" @if($title) title="{{$title}}" @endif> <div class="badge badge-success"></div>
@if ($lastDeploymentLink) </span>
<a href="{{ $lastDeploymentLink }}" target="_blank" class="underline cursor-pointer"> <div class="pl-2 pr-1 text-xs font-bold tracking-wider text-success"
{{ str($status)->before(':')->headline() }} @if ($title) title="{{ $title }}" @endif>
</a> @if ($lastDeploymentLink)
@else <a href="{{ $lastDeploymentLink }}" target="_blank" class="underline cursor-pointer">
{{ str($status)->before(':')->headline() }} {{ str($status)->before(':')->headline() }}
@endif </a>
</div> @else
@if (!str($status)->startsWith('Proxy') && !str($status)->contains('(')) {{ str($status)->before(':')->headline() }}
@if (str($status)->contains('unhealthy')) @endif
<x-helper helper="Unhealthy state. <span class='dark:text-warning text-coollabs'>This doesn't mean that the resource is malfunctioning.</span><br><br>- If the resource is accessible, it indicates that no health check is configured - it is not mandatory.<br>- If the resource is not accessible (returning 404 or 503), it may indicate that a health check is needed and has not passed. <span class='dark:text-warning text-coollabs'>Your action is required.</span><br><br>More details in the <a href='https://coolify.io/docs/knowledge-base/traefik/healthcheck/' class='underline dark:text-warning text-coollabs' target='_blank'>documentation</a>." > </div>
@php
$showUnhealthyHelper =
!str($status)->startsWith('Proxy') &&
!str($status)->contains('(') &&
str($status)->contains('unhealthy');
@endphp
@if ($showUnhealthyHelper)
<x-helper
helper="Unhealthy state. <span class='dark:text-warning text-coollabs'>This doesn't mean that the resource is malfunctioning.</span><br><br>- If the resource is accessible, it indicates that no health check is configured - it is not mandatory.<br>- If the resource is not accessible (returning 404 or 503), it may indicate that a health check is needed and has not passed. <span class='dark:text-warning text-coollabs'>Your action is required.</span><br><br>More details in the <a href='https://coolify.io/docs/knowledge-base/traefik/healthcheck/' class='underline dark:text-warning text-coollabs' target='_blank'>documentation</a>.">
<x-slot:icon> <x-slot:icon>
<svg class="hidden w-4 h-4 dark:text-warning lg:block" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"> <svg class="hidden w-4 h-4 dark:text-warning lg:block" viewBox="0 0 256 256"
<path fill="currentColor" d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16"></path> xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16">
</path>
</svg> </svg>
</x-slot:icon> </x-slot:icon>
</x-helper> </x-helper>
@endif @endif
@endif </div>
</span>
</div> </div>

View File

@@ -3,7 +3,7 @@
<x-slide-over @startdatabase.window="slideOverOpen = true" closeWithX fullScreen> <x-slide-over @startdatabase.window="slideOverOpen = true" closeWithX fullScreen>
<x-slot:title>Database Startup</x-slot:title> <x-slot:title>Database Startup</x-slot:title>
<x-slot:content> <x-slot:content>
<livewire:activity-monitor header="Logs" showWaiting /> <livewire:activity-monitor header="Logs" showWaiting fullHeight />
</x-slot:content> </x-slot:content>
</x-slide-over> </x-slide-over>
<div class="navbar-main"> <div class="navbar-main">

View File

@@ -13,7 +13,7 @@
<div>Environment variables (secrets) for this resource. </div> <div>Environment variables (secrets) for this resource. </div>
@if ($this->resourceClass === 'App\Models\Application' && data_get($this->resource, 'build_pack') !== 'dockercompose') @if ($this->resourceClass === 'App\Models\Application' && data_get($this->resource, 'build_pack') !== 'dockercompose')
<div class="w-64 pt-2"> <div class="w-64 pt-2">
<x-forms.checkbox id="resource.settings.is_env_sorting_enabled" label="Sort alphabetically" <x-forms.checkbox id="is_env_sorting_enabled" label="Sort alphabetically"
helper="Turn this off if one environment is dependent on an other. It will be sorted by creation order (like you pasted them or in the order you created them)." helper="Turn this off if one environment is dependent on an other. It will be sorted by creation order (like you pasted them or in the order you created them)."
instantSave></x-forms.checkbox> instantSave></x-forms.checkbox>
</div> </div>
@@ -55,10 +55,10 @@
<h3>Preview Deployments Environment Variables</h3> <h3>Preview Deployments Environment Variables</h3>
<div>Environment (secrets) variables for Preview Deployments.</div> <div>Environment (secrets) variables for Preview Deployments.</div>
</div> </div>
@foreach ($resource->environment_variables_preview as $env) {{-- @foreach ($resource->environment_variables_preview as $env)
<livewire:project.shared.environment-variable.show wire:key="environment-{{ $env->id }}" <livewire:project.shared.environment-variable.show wire:key="environment-{{ $env->id }}"
:env="$env" :type="$resource->type()" /> :env="$env" :type="$resource->type()" />
@endforeach @endforeach --}}
@endif @endif
@else @else
<form wire:submit.prevent='submit' class="flex flex-col gap-2"> <form wire:submit.prevent='submit' class="flex flex-col gap-2">

View File

@@ -1,17 +1,14 @@
<div> <div>
<form wire:submit='submit' <form wire:submit='submit' @class([
@class([ 'flex flex-col items-center gap-4 p-4 bg-white border lg:items-start dark:bg-base',
'flex flex-col items-center gap-4 p-4 bg-white border lg:items-start dark:bg-base', 'border-error' => $is_really_required,
'border-error' => $env->is_really_required, 'dark:border-coolgray-300' => !$is_really_required,
'dark:border-coolgray-300' => !$env->is_really_required, ])>
])
>
@if ($isLocked) @if ($isLocked)
<div class="flex flex-1 w-full gap-2"> <div class="flex flex-1 w-full gap-2">
<x-forms.input disabled id="env.key" /> <x-forms.input disabled id="key" />
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
stroke-width="2">
<path d="M5 13a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-6z" /> <path d="M5 13a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-6z" />
<path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0-2 0m-3-5V7a4 4 0 1 1 8 0v4" /> <path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0-2 0m-3-5V7a4 4 0 1 1 8 0v4" />
</g> </g>
@@ -25,49 +22,49 @@
@else @else
@if ($isDisabled) @if ($isDisabled)
<div class="flex flex-col w-full gap-2 lg:flex-row"> <div class="flex flex-col w-full gap-2 lg:flex-row">
<x-forms.input disabled id="env.key" /> <x-forms.input disabled id="key" />
<x-forms.input disabled type="password" id="env.value" /> <x-forms.input disabled type="password" id="value" />
@if ($env->is_shared) @if ($is_shared)
<x-forms.input disabled type="password" id="env.real_value" /> <x-forms.input disabled type="password" id="real_value" />
@endif @endif
</div> </div>
@else @else
<div class="flex flex-col w-full gap-2 lg:flex-row"> <div class="flex flex-col w-full gap-2 lg:flex-row">
@if ($env->is_multiline) @if ($is_multiline)
<x-forms.input isMultiline="{{ $env->is_multiline }}" id="env.key" /> <x-forms.input isMultiline="{{ $is_multiline }}" id="key" />
<x-forms.textarea type="password" id="env.value" /> <x-forms.textarea type="password" id="value" />
@else @else
<x-forms.input id="env.key" /> <x-forms.input id="key" />
<x-forms.input type="password" id="env.value" /> <x-forms.input type="password" id="value" />
@endif @endif
@if ($env->is_shared) @if ($is_shared)
<x-forms.input disabled type="password" id="env.real_value" /> <x-forms.input disabled type="password" id="real_value" />
@endif @endif
</div> </div>
@endif @endif
<div class="flex flex-col w-full gap-2 lg:flex-row"> <div class="flex flex-col w-full gap-2 lg:flex-row">
@if ($type === 'service') @if ($type === 'service')
<x-forms.checkbox instantSave id="env.is_build_time" <x-forms.checkbox instantSave id="is_build_time"
helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for docker file, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`" helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for docker file, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`"
label="Build Variable?" /> label="Build Variable?" />
@else @else
@if ($env->is_shared) @if ($is_shared)
<x-forms.checkbox instantSave id="env.is_build_time" <x-forms.checkbox instantSave id="is_build_time"
helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for docker file, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`" helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for docker file, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`"
label="Build Variable?" /> label="Build Variable?" />
<x-forms.checkbox instantSave id="env.is_literal" <x-forms.checkbox instantSave id="is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true." helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" /> label="Is Literal?" />
@else @else
@if ($isSharedVariable) @if ($isSharedVariable)
<x-forms.checkbox instantSave id="env.is_multiline" label="Is Multiline?" /> <x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
@else @else
<x-forms.checkbox instantSave id="env.is_build_time" <x-forms.checkbox instantSave id="is_build_time"
helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for dockerfile, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`" helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for dockerfile, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`"
label="Build Variable?" /> label="Build Variable?" />
<x-forms.checkbox instantSave id="env.is_multiline" label="Is Multiline?" /> <x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
@if (!data_get($env, 'is_multiline')) @if ($is_multiline === false)
<x-forms.checkbox instantSave id="env.is_literal" <x-forms.checkbox instantSave id="is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true." helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" /> label="Is Literal?" />
@endif @endif
@@ -84,7 +81,7 @@
</x-forms.button> </x-forms.button>
<x-modal-confirmation title="Confirm Environment Variable Deletion?" isErrorButton <x-modal-confirmation title="Confirm Environment Variable Deletion?" isErrorButton
buttonTitle="Delete" submitAction="delete" :actions="['The selected environment variable will be permanently deleted.']" buttonTitle="Delete" submitAction="delete" :actions="['The selected environment variable will be permanently deleted.']"
confirmationText="{{ $env->key }}" confirmationText="{{ $key }}"
confirmationLabel="Please confirm the execution of the actions by entering the Environment Variable Name below" confirmationLabel="Please confirm the execution of the actions by entering the Environment Variable Name below"
shortConfirmationLabel="Environment Variable Name" :confirmWithPassword="false" shortConfirmationLabel="Environment Variable Name" :confirmWithPassword="false"
step2ButtonText="Permanently Delete" /> step2ButtonText="Permanently Delete" />
@@ -97,7 +94,7 @@
</x-forms.button> </x-forms.button>
<x-modal-confirmation title="Confirm Environment Variable Deletion?" isErrorButton <x-modal-confirmation title="Confirm Environment Variable Deletion?" isErrorButton
buttonTitle="Delete" submitAction="delete" :actions="['The selected environment variable will be permanently deleted.']" buttonTitle="Delete" submitAction="delete" :actions="['The selected environment variable will be permanently deleted.']"
confirmationText="{{ $env->key }}" confirmationText="{{ $key }}"
confirmationLabel="Please confirm the execution of the actions by entering the Environment Variable Name below" confirmationLabel="Please confirm the execution of the actions by entering the Environment Variable Name below"
shortConfirmationLabel="Environment Variable Name" :confirmWithPassword="false" shortConfirmationLabel="Environment Variable Name" :confirmWithPassword="false"
step2ButtonText="Permanently Delete" /> step2ButtonText="Permanently Delete" />

View File

@@ -20,17 +20,12 @@
</a> </a>
</button> </button>
@endif @endif
<x-modal-confirmation <x-modal-confirmation title="Confirm Proxy Restart?" buttonTitle="Restart Proxy" submitAction="restart"
title="Confirm Proxy Restart?" :actions="[
buttonTitle="Restart Proxy" 'This proxy will be stopped and started again.',
submitAction="restart" 'All resources hosted on coolify will be unavailable during the restart.',
:actions="['This proxy will be stopped and started again.', 'All resources hosted on coolify will be unavailable during the restart.']" ]" :confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Restart Proxy"
:confirmWithText="false" :dispatchEvent="true" dispatchEventType="restartEvent">
:confirmWithPassword="false"
step2ButtonText="Restart Proxy"
:dispatchEvent="true"
dispatchEventType="restartEvent"
>
<x-slot:button-title> <x-slot:button-title>
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
@@ -42,17 +37,12 @@
Restart Proxy Restart Proxy
</x-slot:button-title> </x-slot:button-title>
</x-modal-confirmation> </x-modal-confirmation>
<x-modal-confirmation <x-modal-confirmation title="Confirm Proxy Stopping?" buttonTitle="Stop Proxy" submitAction="stop(true)"
title="Confirm Proxy Stopping?" :actions="[
buttonTitle="Stop Proxy" 'The coolify proxy will be stopped.',
submitAction="stop(true)" 'All resources hosted on coolify will be unavailable.',
:actions="['The coolify proxy will be stopped.', 'All resources hosted on coolify will be unavailable.']" ]" :confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Stop Proxy" :dispatchEvent="true"
:confirmWithText="false" dispatchEventType="stopEvent">
:confirmWithPassword="false"
step2ButtonText="Stop Proxy"
:dispatchEvent="true"
dispatchEventType="stopEvent"
>
<x-slot:button-title> <x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
@@ -82,7 +72,6 @@
@script @script
<script> <script>
$wire.$on('checkProxyEvent', () => { $wire.$on('checkProxyEvent', () => {
$wire.$dispatch('info', 'Starting proxy.');
$wire.$call('checkProxy'); $wire.$call('checkProxy');
}); });
$wire.$on('restartEvent', () => { $wire.$on('restartEvent', () => {

View File

@@ -1,14 +1,15 @@
<div x-init="$wire.checkProxy()" class="flex gap-2"> <div x-init="$wire.checkProxy()" class="flex gap-2">
<x-forms.button wire:click='checkProxy(true)' :showLoadingIndicator="false">Refresh</x-forms.button>
@if (data_get($server, 'proxy.status') === 'running') @if (data_get($server, 'proxy.status') === 'running')
<x-status.running status="Proxy Running" noLoading /> <x-status.running status="Proxy Running" />
@elseif (data_get($server, 'proxy.status') === 'restarting') @elseif (data_get($server, 'proxy.status') === 'restarting')
<x-status.restarting status="Proxy Restarting" noLoading /> <x-status.restarting status="Proxy Restarting" />
@elseif (data_get($server, 'proxy.force_stop')) @elseif (data_get($server, 'proxy.force_stop'))
<x-status.stopped status="Proxy Stopped" noLoading /> <x-status.stopped status="Proxy Stopped" />
@elseif (data_get($server, 'proxy.status') === 'exited') @elseif (data_get($server, 'proxy.status') === 'exited')
<x-status.stopped status="Proxy Exited" noLoading /> <x-status.stopped status="Proxy Exited" />
@else @else
<x-status.stopped status="Proxy Not Running" noLoading /> <x-status.stopped status="Proxy Not Running" />
@endif @endif
<x-forms.button wire:click='checkProxy(true)'>Refresh</x-forms.button>
</div> </div>