feat: lazy load stuffs + tell user if compose based deployments have missing envs

This commit is contained in:
Andras Bacsai 2024-04-15 14:23:25 +02:00
parent 007ba5fce9
commit 16f9c727f2
12 changed files with 165 additions and 45 deletions

View File

@ -708,17 +708,67 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function save_environment_variables()
{
$envs = collect([]);
$ports = $this->application->settings->is_static ? [80] : $this->application->ports_exposes_array;
if ($this->pull_request_id !== 0) {
$this->env_filename = ".env-pr-$this->pull_request_id";
foreach ($this->application->environment_variables_preview as $env) {
$envs->push($env->key . '=' . $env->real_value);
$real_value = $env->real_value;
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
$real_value = escapeEnvVariables($env->real_value);
}
if ($env->is_literal) {
$real_value = '\'' . $real_value . '\'';
}
$envs->push($env->key . '=' . $real_value);
}
// Add PORT if not exists, use the first port as default
if ($this->application->environment_variables_preview->filter(fn ($env) => Str::of($env)->startsWith('PORT'))->isEmpty()) {
$envs->push("PORT={$ports[0]}");
}
// Add HOST if not exists
if ($this->application->environment_variables_preview->filter(fn ($env) => Str::of($env)->startsWith('HOST'))->isEmpty()) {
$envs->push("HOST=0.0.0.0");
}
if ($this->application->environment_variables_preview->filter(fn ($env) => Str::of($env)->startsWith('SOURCE_COMMIT'))->isEmpty()) {
if (!is_null($this->commit)) {
$envs->push("SOURCE_COMMIT={$this->commit}");
} else {
$envs->push("SOURCE_COMMIT=unknown");
}
}
} else {
$this->env_filename = ".env";
foreach ($this->application->environment_variables as $env) {
$envs->push($env->key . '=' . $env->real_value);
$real_value = $env->real_value;
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
$real_value = escapeEnvVariables($env->real_value);
}
if ($env->is_literal) {
$real_value = '\'' . $real_value . '\'';
}
$envs->push($env->key . '=' . $real_value);
}
// Add PORT if not exists, use the first port as default
if ($this->application->environment_variables->filter(fn ($env) => Str::of($env)->startsWith('PORT'))->isEmpty()) {
$envs->push("PORT={$ports[0]}");
}
// Add HOST if not exists
if ($this->application->environment_variables->filter(fn ($env) => Str::of($env)->startsWith('HOST'))->isEmpty()) {
$envs->push("HOST=0.0.0.0");
}
if ($this->application->environment_variables->filter(fn ($env) => Str::of($env)->startsWith('SOURCE_COMMIT'))->isEmpty()) {
if (!is_null($this->commit)) {
$envs->push("SOURCE_COMMIT={$this->commit}");
} else {
$envs->push("SOURCE_COMMIT=unknown");
}
}
}
if ($envs->isEmpty()) {
$this->env_filename = null;
$this->execute_remote_command(
@ -1128,7 +1178,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
$persistent_storages = $this->generate_local_persistent_volumes();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables($ports);
// $environment_variables = $this->generate_environment_variables($ports);
$this->save_environment_variables();
if (data_get($this->application, 'custom_labels')) {
$this->application->parseContainerLabels();
$labels = collect(preg_split("/\r\n|\n|\r/", base64_decode($this->application->custom_labels)));
@ -1182,7 +1233,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
'image' => $this->production_image_name,
'container_name' => $this->container_name,
'restart' => RESTART_MODE,
'environment' => $environment_variables,
// 'env_file' => $this->env_filename,
// 'environment' => $environment_variables,
'expose' => $ports,
'networks' => [
$this->destination->network,
@ -1361,7 +1413,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->docker_compose = Yaml::dump($docker_compose, 10);
$this->docker_compose_base64 = base64_encode($this->docker_compose);
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]);
$this->save_environment_variables();
}
private function generate_local_persistent_volumes()

View File

@ -165,7 +165,6 @@ class General extends Component
return handleError($e, $this);
} finally {
$this->initLoadingCompose = false;
}
}
public function generateDomain(string $serviceName)

View File

@ -15,8 +15,10 @@ class All extends Component
public ?string $variables = null;
public ?string $variablesPreview = null;
public string $view = 'normal';
protected $listeners = ['refreshEnvs', 'saveKey' => 'submit'];
protected $listeners = [
'refreshEnvs',
'saveKey' => 'submit',
];
public function mount()
{
$resourceClass = get_class($this->resource);

View File

@ -16,6 +16,9 @@ class Show extends Component
public bool $isLocked = false;
public bool $isSharedVariable = false;
public string $type;
protected $listeners = [
"compose_loaded" => '$refresh',
];
protected $rules = [
'env.key' => 'required|string',
@ -43,7 +46,6 @@ class Show extends Component
$this->modalId = new Cuid2(7);
$this->parameters = get_route_parameters();
$this->checkEnvs();
ray($this->env);
}
public function checkEnvs()
{

View File

@ -6,6 +6,7 @@ use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
class EnvironmentVariable extends Model
{
@ -81,6 +82,44 @@ class EnvironmentVariable extends Model
}
);
}
protected function isFoundInCompose(): Attribute
{
return Attribute::make(
get: function () {
if (!$this->application_id) {
return true;
}
$found_in_compose = false;
$resource = $this->resource();
$compose = data_get($resource, 'docker_compose_raw');
if (!$compose) {
return true;
}
$yaml = Yaml::parse($compose);
$services = collect(data_get($yaml, 'services'));
if ($services->isEmpty()) {
return false;
}
foreach ($services as $service) {
$environments = collect(data_get($service, 'environment'));
if ($environments->isEmpty()) {
$found_in_compose = false;
break;
}
$found_in_compose = $environments->contains(function ($item) {
if (str($item)->contains('=')) {
$item = str($item)->before('=');
}
return strpos($item, $this->key) !== false;
});
if ($found_in_compose) {
break;
}
}
return $found_in_compose;
}
);
}
protected function isShared(): Attribute
{
return Attribute::make(

View File

@ -146,4 +146,5 @@ return [
*/
'pagination_theme' => 'tailwind',
'lazy_placeholder' => 'components.page-loading',
];

View File

@ -0,0 +1,13 @@
@props(['text' => "Loading..."])
<div class="inline-flex items-center justify-center" {{ $attributes }}>
@if (isset($text))
<div>{{ $text }}</div>
@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"
viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<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">
</path>
</svg>
</div>

View File

@ -83,48 +83,48 @@
<livewire:project.application.general :application="$application" />
</div>
<div x-cloak x-show="activeTab === 'swarm'" class="h-full">
<livewire:project.application.swarm :application="$application" />
<livewire:project.application.swarm :application="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'advanced'" class="h-full">
<livewire:project.application.advanced :application="$application" />
<livewire:project.application.advanced :application="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'environment-variables'">
<livewire:project.shared.environment-variable.all :resource="$application" />
<livewire:project.shared.environment-variable.all :resource="$application" lazy />
</div>
@if ($application->git_based())
<div x-cloak x-show="activeTab === 'source'">
<livewire:project.application.source :application="$application" />
<livewire:project.application.source :application="$application" lazy />
</div>
@endif
<div x-cloak x-show="activeTab === 'servers'">
<livewire:project.shared.destination :resource="$application" :servers="$servers" />
<livewire:project.shared.destination :resource="$application" :servers="$servers" lazy />
</div>
<div x-cloak x-show="activeTab === 'storages'">
<livewire:project.service.storage :resource="$application" />
<livewire:project.service.storage :resource="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'webhooks'">
<livewire:project.shared.webhooks :resource="$application" />
<livewire:project.shared.webhooks :resource="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'previews'">
<livewire:project.application.previews :application="$application" />
<livewire:project.application.previews :application="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'health'">
<livewire:project.shared.health-checks :resource="$application" />
<livewire:project.shared.health-checks :resource="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'rollback'">
<livewire:project.application.rollback :application="$application" />
<livewire:project.application.rollback :application="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'resource-limits'">
<livewire:project.shared.resource-limits :resource="$application" />
<livewire:project.shared.resource-limits :resource="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'scheduled-tasks'">
<livewire:project.shared.scheduled-task.all :resource="$application" />
<livewire:project.shared.scheduled-task.all :resource="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'resource-operations'">
<livewire:project.shared.resource-operations :resource="$application" />
<livewire:project.shared.resource-operations :resource="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'tags'">
<livewire:project.shared.tags :resource="$application" />
<livewire:project.shared.tags :resource="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'danger'">
<livewire:project.shared.danger :resource="$application" />

View File

@ -159,29 +159,29 @@
<span class="dark:text-warning">Please modify storage layout in your Docker Compose file.</span>
@foreach ($applications as $application)
<livewire:project.service.storage wire:key="application-{{ $application->id }}"
:resource="$application" />
:resource="$application" lazy />
@endforeach
@foreach ($databases as $database)
<livewire:project.service.storage wire:key="database-{{ $database->id }}" :resource="$database" />
<livewire:project.service.storage wire:key="database-{{ $database->id }}" :resource="$database" lazy />
@endforeach
</div>
<div x-cloak x-show="activeTab === 'webhooks'">
<livewire:project.shared.webhooks :resource="$service" />
<livewire:project.shared.webhooks :resource="$service" lazy />
</div>
<div x-cloak x-show="activeTab === 'logs'">
<livewire:project.shared.logs :resource="$service" />
<livewire:project.shared.logs :resource="$service" />
</div>
<div x-cloak x-show="activeTab === 'execute-command'">
<livewire:project.shared.execute-container-command :resource="$service" />
<livewire:project.shared.execute-container-command :resource="$service" />
</div>
<div x-cloak x-show="activeTab === 'environment-variables'">
<livewire:project.shared.environment-variable.all :resource="$service" />
<livewire:project.shared.environment-variable.all :resource="$service" lazy />
</div>
<div x-cloak x-show="activeTab === 'resource-operations'">
<livewire:project.shared.resource-operations :resource="$service" />
<livewire:project.shared.resource-operations :resource="$service" lazy />
</div>
<div x-cloak x-show="activeTab === 'tags'">
<livewire:project.shared.tags :resource="$service" />
<livewire:project.shared.tags :resource="$service" lazy />
</div>
<div x-cloak x-show="activeTab === 'danger'">
<livewire:project.shared.danger :resource="$service" />

View File

@ -11,7 +11,7 @@
wire:click='switch'>{{ $view === 'normal' ? 'Developer view' : 'Normal view' }}</x-forms.button>
</div>
<div>Environment variables (secrets) for this resource.</div>
@if ($resource->type() === 'service')
@if ($resource->type() === 'service' || $resource?->build_pack === 'dockercompose')
<div>Hardcoded variables are not shown here.</div>
@endif
</div>

View File

@ -1,6 +1,15 @@
<div>
<form wire:submit='submit'
class="flex flex-col items-center gap-4 p-4 bg-white border lg:items-start dark:bg-base dark:border-coolgray-300">
@if (!$env->isFoundInCompose)
<div class="flex items-center justify-center gap-2 dark:text-warning text-coollabs"> <svg
class="hidden w-4 h-4 dark:text-warning lg:block" viewBox="0 0 256 256"
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>This variable is not found in the compose file, so it won't be used.</div>
@endif
@if ($isLocked)
<div class="flex flex-1 w-full gap-2">
<x-forms.input disabled id="env.key" />
@ -49,7 +58,7 @@
helper="This means that when you use $VARIABLES, 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 form another value. In this case, you should set this to true."
label="Is Literal?" />
@else
@if ($type === 'team' || $type === 'environment' || $type === 'project')
@if ($isSharedVariable)
<x-forms.checkbox instantSave id="env.is_multiline" label="Is Multiline?" />
@else
<x-forms.checkbox instantSave id="env.is_build_time" label="Build Variable?" />

View File

@ -1,18 +1,22 @@
<div>
<h2>Tags</h2>
<div class="flex gap-2 pt-4">
@forelse ($this->resource->tags as $tagId => $tag)
<div class="px-2 py-1 text-center rounded select-none dark:text-white w-fit bg-neutral-200 hover:bg-neutral-300 dark:bg-coolgray-100 dark:hover:bg-coolgray-200">
{{ $tag->name }}
<svg wire:click="deleteTag('{{ $tag->id }}')"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
class="inline-block w-3 h-3 rounded cursor-pointer stroke-current hover:bg-red-500">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</div>
@empty
<div class="py-1">No tags yet</div>
@endforelse
@if (data_get($this->resource, 'tags'))
@forelse (data_get($this->resource,'tags') as $tagId => $tag)
<div
class="px-2 py-1 text-center rounded select-none dark:text-white w-fit bg-neutral-200 hover:bg-neutral-300 dark:bg-coolgray-100 dark:hover:bg-coolgray-200">
{{ $tag->name }}
<svg wire:click="deleteTag('{{ $tag->id }}')" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24"
class="inline-block w-3 h-3 rounded cursor-pointer stroke-current hover:bg-red-500">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12">
</path>
</svg>
</div>
@empty
<div class="py-1">No tags yet</div>
@endforelse
@endif
</div>
<form wire:submit='submit' class="flex items-end gap-2 pt-4">
<div class="w-64">