From fabb97330aea82943ba1d1854d8ca9109de21281 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 26 Sep 2023 14:45:52 +0200 Subject: [PATCH] puh, fixes --- app/Actions/Service/StartService.php | 1 + app/Http/Controllers/ProjectController.php | 34 ++++++-- .../Livewire/Project/New/DockerCompose.php | 14 +++- app/Http/Livewire/Project/New/Select.php | 3 +- .../Livewire/Project/Service/Application.php | 13 ++- .../Livewire/Project/Service/Database.php | 15 +++- .../Livewire/Project/Service/FileStorage.php | 4 + app/Http/Livewire/Project/Service/Index.php | 39 +++++++-- app/Http/Livewire/Project/Service/Navbar.php | 1 + app/Http/Livewire/Project/Shared/Danger.php | 1 + .../Shared/EnvironmentVariable/Add.php | 5 +- .../Shared/EnvironmentVariable/All.php | 17 ++-- .../Shared/EnvironmentVariable/Show.php | 2 +- app/Jobs/ContainerStatusJob.php | 3 + app/Models/Service.php | 79 +++++++++++++++---- app/Models/ServiceApplication.php | 1 - bootstrap/helpers/services.php | 43 ++++++++-- ...1811_update_service_applications_table.php | 10 ++- ..._111812_update_service_databases_table.php | 8 +- ...111814_update_local_file_volumes_table.php | 28 +++++++ examples/service-templates.json | 32 ++++++++ .../components/navbar-subscription.blade.php | 11 +-- resources/views/components/navbar.blade.php | 4 +- .../components/services/navbar.blade.php | 52 +++++++----- .../views/livewire/activity-monitor.blade.php | 2 +- .../project/new/docker-compose.blade.php | 1 + .../project/service/application.blade.php | 22 ++++-- .../project/service/database.blade.php | 12 +-- .../project/service/file-storage.blade.php | 22 ++++-- .../livewire/project/service/index.blade.php | 19 +++-- .../shared/environment-variable/all.blade.php | 4 +- .../livewire/server/proxy/status.blade.php | 11 +-- 32 files changed, 386 insertions(+), 127 deletions(-) create mode 100644 database/migrations/2023_09_23_111814_update_local_file_volumes_table.php diff --git a/app/Actions/Service/StartService.php b/app/Actions/Service/StartService.php index a843e7fc2..4ca2b7114 100644 --- a/app/Actions/Service/StartService.php +++ b/app/Actions/Service/StartService.php @@ -12,6 +12,7 @@ class StartService { $service->saveComposeConfigs(); $commands[] = "cd " . $service->workdir(); + $commands[] = "echo '####### Saved configuration files to {$service->workdir()}.'"; $commands[] = "echo '####### Starting service {$service->name} on {$service->server->name}.'"; $commands[] = "echo '####### Pulling images.'"; $commands[] = "docker compose pull"; diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php index a0038a597..97b61eb87 100644 --- a/app/Http/Controllers/ProjectController.php +++ b/app/Http/Controllers/ProjectController.php @@ -69,7 +69,12 @@ class ProjectController extends Controller if ($type->startsWith('one-click-service-')) { $oneClickServiceName = $type->after('one-click-service-')->value(); $oneClickService = data_get($services, "$oneClickServiceName.compose"); - $oneClickDotEnvs = collect(data_get($services, "$oneClickServiceName.envs", [])); + $oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null); + $oneClickRequiredFqdn = data_get($services, "$oneClickServiceName.generateFqdn", []); + $oneClickRequiredFqdn = collect($oneClickRequiredFqdn); + if ($oneClickDotEnvs) { + $oneClickDotEnvs = Str::of(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/'); + } if ($oneClickService) { $service = Service::create([ 'name' => "$oneClickServiceName-" . Str::random(10), @@ -77,18 +82,37 @@ class ProjectController extends Controller 'environment_id' => $environment->id, 'server_id' => (int) $server_id, ]); - if ($oneClickDotEnvs->count() > 0) { - $oneClickDotEnvs->each(function ($value, $key) use ($service) { + $service->name = "$oneClickServiceName-" . $service->uuid; + $service->save(); + if ($oneClickDotEnvs && $oneClickDotEnvs->count() > 0) { + $oneClickDotEnvs->each(function ($value) use ($service) { + $key = Str::before($value, '='); + $value = Str::of(Str::after($value, '=')); + if ($value->contains('SERVICE_USER')) { + $value = Str::of(Str::random(10)); + } + if ($value->contains('SERVICE_PASSWORD')) { + $value = Str::of(Str::password(symbols: false)); + } + if ($value->contains('SERVICE_BASE64')) { + $length = Str::of($value)->after('SERVICE_BASE64_')->beforeLast('_')->value(); + if (is_numeric($length)) { + $length = (int) $length; + } else { + $length = 1; + } + $value = Str::of(base64_encode(Str::password(length: $length, symbols: false))); + } EnvironmentVariable::create([ 'key' => $key, - 'value' => $value, + 'value' => $value->value(), 'service_id' => $service->id, 'is_build_time' => false, 'is_preview' => false, ]); }); } - $service->parse(isNew: true); + $service->parse(isNew: true, requiredFqdns: $oneClickRequiredFqdn); return redirect()->route('project.service', [ 'service_uuid' => $service->uuid, diff --git a/app/Http/Livewire/Project/New/DockerCompose.php b/app/Http/Livewire/Project/New/DockerCompose.php index 2e95eaf9d..a43fd0c66 100644 --- a/app/Http/Livewire/Project/New/DockerCompose.php +++ b/app/Http/Livewire/Project/New/DockerCompose.php @@ -2,6 +2,7 @@ namespace App\Http\Livewire\Project\New; +use App\Models\EnvironmentVariable; use App\Models\Project; use App\Models\Service; use Livewire\Component; @@ -11,6 +12,7 @@ use Symfony\Component\Yaml\Yaml; class DockerCompose extends Component { public string $dockerComposeRaw = ''; + public string $envFile = ''; public array $parameters; public array $query; public function mount() @@ -45,13 +47,22 @@ class DockerCompose extends Component $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); $environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first(); - $service = Service::create([ 'name' => 'service' . Str::random(10), 'docker_compose_raw' => $this->dockerComposeRaw, 'environment_id' => $environment->id, 'server_id' => (int) $server_id, ]); + $variables = parseEnvFormatToArray($this->envFile); + foreach ($variables as $key => $variable) { + EnvironmentVariable::create([ + 'key' => $key, + 'value' => $variable, + 'is_build_time' => false, + 'is_preview' => false, + 'service_id' => $service->id, + ]); + } $service->name = "service-$service->uuid"; $service->parse(isNew: true); @@ -64,6 +75,5 @@ class DockerCompose extends Component } catch (\Throwable $e) { return handleError($e, $this); } - } } diff --git a/app/Http/Livewire/Project/New/Select.php b/app/Http/Livewire/Project/New/Select.php index dd9334e01..a9b50c624 100644 --- a/app/Http/Livewire/Project/New/Select.php +++ b/app/Http/Livewire/Project/New/Select.php @@ -29,6 +29,7 @@ class Select extends Component protected $queryString = [ 'server', ]; + public function mount() { $this->parameters = get_route_parameters(); @@ -46,6 +47,7 @@ class Select extends Component // return handleError($e, $this); // } // } + public function loadThings() { $this->loadServices(); @@ -77,7 +79,6 @@ class Select extends Component }); } $this->services = $cached; - } catch (\Throwable $e) { ray($e); return handleError($e, $this); diff --git a/app/Http/Livewire/Project/Service/Application.php b/app/Http/Livewire/Project/Service/Application.php index 44538278e..cf464b442 100644 --- a/app/Http/Livewire/Project/Service/Application.php +++ b/app/Http/Livewire/Project/Service/Application.php @@ -3,26 +3,29 @@ namespace App\Http\Livewire\Project\Service; use App\Models\ServiceApplication; +use Illuminate\Support\Collection; use Livewire\Component; class Application extends Component { public ServiceApplication $application; public $parameters; - public $fileStorages = null; + public $fileStorages; protected $listeners = ["refreshFileStorages"]; protected $rules = [ 'application.human_name' => 'nullable', 'application.description' => 'nullable', 'application.fqdn' => 'nullable', - 'application.image_tag' => 'required', - 'application.ignore_from_status' => 'required|boolean', + 'application.image' => 'required', + 'application.exclude_from_status' => 'required|boolean', + 'application.required_fqdn' => 'required|boolean', ]; public function render() { return view('livewire.project.service.application'); } - public function instantSave() { + public function instantSave() + { $this->submit(); } public function refreshFileStorages() @@ -42,6 +45,7 @@ class Application extends Component public function mount() { $this->parameters = get_route_parameters(); + $this->fileStorages = collect(); $this->refreshFileStorages(); } public function submit() @@ -49,6 +53,7 @@ class Application extends Component try { $this->validate(); $this->application->save(); + switchImage($this->application); $this->emit('success', 'Application saved successfully.'); } catch (\Throwable $e) { ray($e); diff --git a/app/Http/Livewire/Project/Service/Database.php b/app/Http/Livewire/Project/Service/Database.php index 36dd96d36..0a1b13dbc 100644 --- a/app/Http/Livewire/Project/Service/Database.php +++ b/app/Http/Livewire/Project/Service/Database.php @@ -8,25 +8,34 @@ use Livewire\Component; class Database extends Component { public ServiceDatabase $database; + public $fileStorages; + protected $listeners = ["refreshFileStorages"]; protected $rules = [ 'database.human_name' => 'nullable', 'database.description' => 'nullable', - 'database.image_tag' => 'required', - 'database.ignore_from_status' => 'required|boolean', - + 'database.image' => 'required', + 'database.exclude_from_status' => 'required|boolean', ]; public function render() { return view('livewire.project.service.database'); } + public function mount() { + $this->refreshFileStorages(); + } public function instantSave() { $this->submit(); } + public function refreshFileStorages() + { + $this->fileStorages = $this->database->fileStorages()->get(); + } public function submit() { try { $this->validate(); $this->database->save(); + switchImage($this->database); $this->emit('success', 'Database saved successfully.'); } catch (\Throwable $e) { ray($e); diff --git a/app/Http/Livewire/Project/Service/FileStorage.php b/app/Http/Livewire/Project/Service/FileStorage.php index b3bed8528..9e80b663b 100644 --- a/app/Http/Livewire/Project/Service/FileStorage.php +++ b/app/Http/Livewire/Project/Service/FileStorage.php @@ -15,6 +15,7 @@ class FileStorage extends Component public string $fs_path; protected $rules = [ + 'fileStorage.is_directory' => 'required', 'fileStorage.fs_path' => 'required', 'fileStorage.mount_path' => 'required', 'fileStorage.content' => 'nullable', @@ -39,6 +40,9 @@ class FileStorage extends Component return handleError($e, $this); } } + public function instantSave() { + $this->submit(); + } public function render() { return view('livewire.project.service.file-storage'); diff --git a/app/Http/Livewire/Project/Service/Index.php b/app/Http/Livewire/Project/Service/Index.php index c294edc54..88d8679bc 100644 --- a/app/Http/Livewire/Project/Service/Index.php +++ b/app/Http/Livewire/Project/Service/Index.php @@ -3,32 +3,57 @@ namespace App\Http\Livewire\Project\Service; use App\Models\Service; -use App\Models\ServiceApplication; +use DanHarrin\LivewireRateLimiting\WithRateLimiting; use Livewire\Component; class Index extends Component { + use WithRateLimiting; public Service $service; public $applications; public $databases; public array $parameters; public array $query; - protected $rules = [ 'service.docker_compose_raw' => 'required', 'service.docker_compose' => 'required', 'service.name' => 'required', 'service.description' => 'nullable', ]; - + public function manualRefreshStack() { + try { + $this->rateLimit(5); + $this->refreshStack(); + } catch(\Throwable $e) { + return handleError($e, $this); + } + } + public function refreshStack() + { + $this->applications = $this->service->applications->sort(); + $this->applications->each(function ($application) { + $application->fileStorages()->get()->each(function ($fileStorage) use ($application) { + if (!$fileStorage->is_directory && $fileStorage->content == null) { + $application->hasMissingFiles = true; + } + }); + }); + $this->databases = $this->service->databases->sort(); + $this->databases->each(function ($database) { + $database->fileStorages()->get()->each(function ($fileStorage) use ($database) { + if (!$fileStorage->is_directory && $fileStorage->content == null) { + $database->hasMissingFiles = true; + } + }); + }); + $this->emit('success', 'Stack refreshed successfully.'); + } public function mount() { $this->parameters = get_route_parameters(); $this->query = request()->query(); $this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail(); - $this->applications = $this->service->applications->sort(); - $this->databases = $this->service->databases->sort(); - + $this->refreshStack(); } public function render() { @@ -43,7 +68,7 @@ class Index extends Component $this->emit('refreshEnvs'); $this->emit('success', 'Service saved successfully.'); $this->service->saveComposeConfigs(); - } catch(\Throwable $e) { + } catch (\Throwable $e) { return handleError($e, $this); } } diff --git a/app/Http/Livewire/Project/Service/Navbar.php b/app/Http/Livewire/Project/Service/Navbar.php index b7f2730b6..e4980696c 100644 --- a/app/Http/Livewire/Project/Service/Navbar.php +++ b/app/Http/Livewire/Project/Service/Navbar.php @@ -39,5 +39,6 @@ class Navbar extends Component { StopService::run($this->service); $this->service->refresh(); + $this->emit('success', 'Service stopped successfully.'); } } diff --git a/app/Http/Livewire/Project/Shared/Danger.php b/app/Http/Livewire/Project/Shared/Danger.php index 0de51939f..b3288fe60 100644 --- a/app/Http/Livewire/Project/Shared/Danger.php +++ b/app/Http/Livewire/Project/Shared/Danger.php @@ -20,6 +20,7 @@ class Danger extends Component public function delete() { + // Should be queued try { if ($this->resource->type() === 'service') { $server = $this->resource->server; diff --git a/app/Http/Livewire/Project/Shared/EnvironmentVariable/Add.php b/app/Http/Livewire/Project/Shared/EnvironmentVariable/Add.php index af94ed77f..0150fbb65 100644 --- a/app/Http/Livewire/Project/Shared/EnvironmentVariable/Add.php +++ b/app/Http/Livewire/Project/Shared/EnvironmentVariable/Add.php @@ -9,13 +9,13 @@ class Add extends Component public $parameters; public bool $is_preview = false; public string $key; - public string $value; + public ?string $value = null; public bool $is_build_time = false; protected $listeners = ['clearAddEnv' => 'clear']; protected $rules = [ 'key' => 'required|string', - 'value' => 'required|string', + 'value' => 'nullable', 'is_build_time' => 'required|boolean', ]; protected $validationAttributes = [ @@ -32,6 +32,7 @@ class Add extends Component public function submit() { $this->validate(); + ray($this->key, $this->value, $this->is_build_time); $this->emitUp('submit', [ 'key' => $this->key, 'value' => $this->value, diff --git a/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php index 6d494e293..59d556644 100644 --- a/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -53,11 +53,11 @@ class All extends Component $this->resource->environment_variables_preview()->delete(); } else { $variables = parseEnvFormatToArray($this->variables); - ray($variables); $existingVariables = $this->resource->environment_variables(); $this->resource->environment_variables()->delete(); } foreach ($variables as $key => $variable) { + ray($key, $variable); $found = $existingVariables->where('key', $key)->first(); if ($found) { $found->value = $variable; @@ -110,11 +110,16 @@ class All extends Component $environment->is_build_time = $data['is_build_time']; $environment->is_preview = $data['is_preview']; - if ($this->resource->type() === 'application') { - $environment->application_id = $this->resource->id; - } - if ($this->resource->type() === 'standalone-postgresql') { - $environment->standalone_postgresql_id = $this->resource->id; + switch ($this->resource->type()) { + case 'application': + $environment->application_id = $this->resource->id; + break; + case 'standalone-postgresql': + $environment->standalone_postgresql_id = $this->resource->id; + break; + case 'service': + $environment->service_id = $this->resource->id; + break; } $environment->save(); $this->refreshEnvs(); diff --git a/app/Http/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Http/Livewire/Project/Shared/EnvironmentVariable/Show.php index 4eaa1231c..ec4205b9e 100644 --- a/app/Http/Livewire/Project/Shared/EnvironmentVariable/Show.php +++ b/app/Http/Livewire/Project/Shared/EnvironmentVariable/Show.php @@ -15,7 +15,7 @@ class Show extends Component protected $rules = [ 'env.key' => 'required|string', - 'env.value' => 'required|string', + 'env.value' => 'nullable', 'env.is_build_time' => 'required|boolean', ]; protected $validationAttributes = [ diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php index 137fadb6e..0cfd9b373 100644 --- a/app/Jobs/ContainerStatusJob.php +++ b/app/Jobs/ContainerStatusJob.php @@ -151,6 +151,9 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted $subType = data_get($labels, 'coolify.service.subType'); $subId = data_get($labels, 'coolify.service.subId'); $service = $services->where('id', $serviceLabelId)->first(); + if (!$service) { + continue; + } if ($subType === 'application') { $service = $service->applications()->where('id', $subId)->first(); } else { diff --git a/app/Models/Service.php b/app/Models/Service.php index f6c12ccff..bf44b8264 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -96,21 +96,47 @@ class Service extends BaseModel foreach ($envs as $env) { $commands[] = "echo '{$env->key}={$env->value}' >> .env"; } + if ($envs->count() === 0) { + $commands[] = "touch .env"; + } instant_remote_process($commands, $this->server); } - private function generateFqdn($serviceVariables, $serviceName) + private function sslip(Server $server) + { + if (isDev()) { + return "127.0.0.1.sslip.io"; + } + return "{$server->ip}.sslip.io"; + } + private function generateFqdn($serviceVariables, $serviceName, Collection $requiredFqdns) { // Add sslip.io to the service - // if (Str::of($serviceVariables)->contains('SERVICE_FQDN') || Str::of($serviceVariables)->contains('SERVICE_URL')) { - $defaultUsableFqdn = "http://$serviceName-{$this->uuid}.{$this->server->ip}.sslip.io"; - if (isDev()) { - $defaultUsableFqdn = "http://$serviceName-{$this->uuid}.127.0.0.1.sslip.io"; + $defaultUsableFqdn = null; + $sslip = $this->sslip($this->server); + if (Str::of($serviceVariables)->contains('SERVICE_FQDN') || Str::of($serviceVariables)->contains('SERVICE_URL')) { + $defaultUsableFqdn = "http://$serviceName-{$this->uuid}.{$sslip}"; + } + if ($requiredFqdns->count() > 0) { + foreach ($requiredFqdns as $requiredFqdn) { + $requiredFqdn = (array)$requiredFqdn; + $name = data_get($requiredFqdn, 'name'); + $path = data_get($requiredFqdn, 'path'); + $customFqdn = data_get($requiredFqdn, 'customFqdn'); + if ($serviceName === $name) { + $defaultUsableFqdn = "http://$serviceName-{$this->uuid}.{$sslip}{$path}"; + if ($customFqdn) { + $defaultUsableFqdn = "http://$customFqdn-{$this->uuid}.{$sslip}{$path}"; + } + } } - // } + } return $defaultUsableFqdn ?? null; } - public function parse(bool $isNew = false): Collection + public function parse(bool $isNew = false, ?Collection $requiredFqdns = null): Collection { + if (!$requiredFqdns) { + $requiredFqdns = collect([]); + } ray('parsing'); // ray()->clearAll(); if ($this->docker_compose_raw) { @@ -130,16 +156,26 @@ class Service extends BaseModel $envs = collect([]); $ports = collect([]); - $services = collect($services)->map(function ($service, $serviceName) use ($composeVolumes, $composeNetworks, $definedNetwork, $envs, $volumes, $ports, $isNew) { + $services = collect($services)->map(function ($service, $serviceName) use ($composeVolumes, $composeNetworks, $definedNetwork, $envs, $volumes, $ports, $isNew, $requiredFqdns) { $container_name = "$serviceName-{$this->uuid}"; $isDatabase = false; $serviceVariables = collect(data_get($service, 'environment', [])); + // Add env_file with at least .env to the service + $envFile = collect(data_get($service, 'env_file', [])); + if ($envFile->count() > 0) { + if (!$envFile->contains('.env')) { + $envFile->push('.env'); + } + } else { + $envFile = collect(['.env']); + } + data_set($service, 'env_file', $envFile->toArray()); + // Decide if the service is a database $image = data_get($service, 'image'); if ($image) { $imageName = Str::of($image)->before(':'); - $imageTag = Str::of($image)->after(':') ?? 'latest'; if (collect(DATABASE_DOCKER_IMAGES)->contains($imageName)) { $isDatabase = true; data_set($service, 'is_database', true); @@ -160,17 +196,29 @@ class Service extends BaseModel if ($isDatabase) { $savedService = ServiceDatabase::create([ 'name' => $serviceName, - 'image_tag' => $imageTag, + 'image' => $image, 'service_id' => $this->id ]); } else { $savedService = ServiceApplication::create([ 'name' => $serviceName, - 'fqdn' => $this->generateFqdn($serviceVariables, $serviceName), - 'image_tag' => $imageTag, + 'fqdn' => $this->generateFqdn($serviceVariables, $serviceName, $requiredFqdns), + 'image' => $image, 'service_id' => $this->id ]); } + if ($requiredFqdns->count() > 0) { + $found = false; + foreach ($requiredFqdns as $requiredFqdn) { + $requiredFqdn = (array)$requiredFqdn; + $name = data_get($requiredFqdn, 'name'); + if ($serviceName === $name) { + $savedService->required_fqdn = true; + $savedService->save(); + break; + } + } + } } else { if ($isDatabase) { $savedService = $this->databases()->whereName($serviceName)->first(); @@ -179,14 +227,13 @@ class Service extends BaseModel if (data_get($savedService, 'fqdn')) { $defaultUsableFqdn = data_get($savedService, 'fqdn', null); } else { - $defaultUsableFqdn = $this->generateFqdn($serviceVariables, $serviceName); + $defaultUsableFqdn = $this->generateFqdn($serviceVariables, $serviceName, $requiredFqdns); } $savedService->fqdn = $defaultUsableFqdn; $savedService->save(); } } - // Set image tag $fqdns = data_get($savedService, 'fqdn'); if ($fqdns) { $fqdns = collect(Str::of($fqdns)->explode(',')); @@ -209,6 +256,7 @@ class Service extends BaseModel } $savedService->ports = $collectedPorts->implode(','); $savedService->save(); + // Collect volumes $serviceVolumes = collect(data_get($service, 'volumes', [])); if ($serviceVolumes->count() > 0) { @@ -341,6 +389,7 @@ class Service extends BaseModel $value = Str::after($variable, '='); if (!Str::startsWith($value, '$SERVICE_') && !Str::startsWith($value, '${SERVICE_') && Str::startsWith($value, '$')) { $value = Str::of(replaceVariables(Str::of($value))); + $nakedName = $nakedValue = null; if ($value->contains(':')) { $nakedName = $value->before(':'); $nakedValue = $value->after(':'); @@ -464,6 +513,7 @@ class Service extends BaseModel $number = 0; } $fqdn = getFqdnWithoutPort(data_get($fqdns, $number, $fqdns->first())); + ray($fqdns); $environments = collect(data_get($service, 'environment')); $environments = $environments->map(function ($envValue) use ($value, $fqdn) { $envValue = Str::of($envValue)->replace($value, $fqdn); @@ -491,6 +541,7 @@ class Service extends BaseModel } } } + // Add labels to the service $labels = collect(data_get($service, 'labels', [])); $labels = collect([]); diff --git a/app/Models/ServiceApplication.php b/app/Models/ServiceApplication.php index 2c3bc4f7c..e7bb84d68 100644 --- a/app/Models/ServiceApplication.php +++ b/app/Models/ServiceApplication.php @@ -18,7 +18,6 @@ class ServiceApplication extends BaseModel { $services = Cache::get('services', []); $service = data_get($services, $this->name, []); - ray($this->name); return data_get($service, 'documentation', 'https://coolify.io/docs'); } public function service() diff --git a/bootstrap/helpers/services.php b/bootstrap/helpers/services.php index af99f77f0..830c17b39 100644 --- a/bootstrap/helpers/services.php +++ b/bootstrap/helpers/services.php @@ -3,7 +3,9 @@ use App\Models\Service; use App\Models\ServiceApplication; use App\Models\ServiceDatabase; +use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; +use Symfony\Component\Yaml\Yaml; function replaceRegex(?string $name = null) { @@ -22,33 +24,41 @@ function serviceStatus(Service $service) { $foundRunning = false; $isDegraded = false; + $foundRestaring = false; $applications = $service->applications; $databases = $service->databases; foreach ($applications as $application) { - if ($application->ignore_from_status) { + if ($application->exclude_from_status) { continue; } if (Str::of($application->status)->startsWith('running')) { $foundRunning = true; + } else if (Str::of($application->status)->startsWith('restarting')) { + $foundRestaring = true; } else { $isDegraded = true; } } foreach ($databases as $database) { - if ($database->ignore_from_status) { + if ($database->exclude_from_status) { continue; } if (Str::of($database->status)->startsWith('running')) { $foundRunning = true; + } else if (Str::of($database->status)->startsWith('restarting')) { + $foundRestaring = true; } else { $isDegraded = true; } } + if ($foundRestaring) { + return 'degraded'; + } if ($foundRunning && !$isDegraded) { return 'running'; } else if ($foundRunning && $isDegraded) { return 'degraded'; - } else if (!$foundRunning && $isDegraded) { + } else if (!$foundRunning && !$isDegraded) { return 'exited'; } return 'exited'; @@ -60,19 +70,25 @@ function saveFileVolumesHelper(ServiceApplication|ServiceDatabase $oneService) $server = $oneService->service->server; $applicationFileVolume = $oneService->fileStorages()->get(); $commands = collect([ - "mkdir -p $workdir", + "mkdir -p $workdir > /dev/null 2>&1 || true", "cd $workdir" ]); foreach ($applicationFileVolume as $fileVolume) { - $content = $fileVolume->content; $path = Str::of($fileVolume->fs_path); + if ($fileVolume->is_directory) { + $commands->push("test -f $path && rm -f $path > /dev/null 2>&1 || true"); + $commands->push("mkdir -p $path > /dev/null 2>&1 || true"); + continue; + } + $content = $fileVolume->content; $dir = $path->beforeLast('/'); if ($dir->startsWith('.')) { $dir = $dir->after('.'); $dir = $workdir . $dir; } $content = base64_encode($content); - $commands->push("mkdir -p $dir"); + $commands->push("test -d $path && rm -rf $path > /dev/null 2>&1 || true"); + $commands->push("mkdir -p $dir > /dev/null 2>&1 || true"); $commands->push("echo '$content' | base64 -d > $path"); } return instant_remote_process($commands, $server); @@ -80,3 +96,18 @@ function saveFileVolumesHelper(ServiceApplication|ServiceDatabase $oneService) return handleError($e); } } +function switchImage($resource) +{ + try { + $name = data_get($resource, 'name'); + $dockerComposeRaw = data_get($resource, 'service.docker_compose_raw'); + $image = data_get($resource, 'image'); + $dockerCompose = Yaml::parse($dockerComposeRaw); + data_set($dockerCompose, "services.{$name}.image", $image); + $dockerComposeRaw = Yaml::dump($dockerCompose, 10, 2); + $resource->service->docker_compose_raw = $dockerComposeRaw; + $resource->service->save(); + } catch (\Throwable $e) { + return handleError($e); + } +} diff --git a/database/migrations/2023_09_23_111811_update_service_applications_table.php b/database/migrations/2023_09_23_111811_update_service_applications_table.php index 68ddab8d5..7e3e5984b 100644 --- a/database/migrations/2023_09_23_111811_update_service_applications_table.php +++ b/database/migrations/2023_09_23_111811_update_service_applications_table.php @@ -12,8 +12,9 @@ return new class extends Migration public function up(): void { Schema::table('service_applications', function (Blueprint $table) { - $table->boolean('ignore_from_status')->default(false); - $table->string('image_tag')->nullable(); + $table->boolean('exclude_from_status')->default(false); + $table->boolean('required_fqdn')->default(false); + $table->string('image')->nullable(); }); } @@ -23,8 +24,9 @@ return new class extends Migration public function down(): void { Schema::table('service_applications', function (Blueprint $table) { - $table->dropColumn('ignore_from_status'); - $table->dropColumn('image_tag'); + $table->dropColumn('exclude_from_status'); + $table->dropColumn('required_fqdn'); + $table->dropColumn('image'); }); } }; diff --git a/database/migrations/2023_09_23_111812_update_service_databases_table.php b/database/migrations/2023_09_23_111812_update_service_databases_table.php index bcf42abfd..2f02b5afe 100644 --- a/database/migrations/2023_09_23_111812_update_service_databases_table.php +++ b/database/migrations/2023_09_23_111812_update_service_databases_table.php @@ -12,8 +12,8 @@ return new class extends Migration public function up(): void { Schema::table('service_databases', function (Blueprint $table) { - $table->boolean('ignore_from_status')->default(false); - $table->string('image_tag')->nullable(); + $table->boolean('exclude_from_status')->default(false); + $table->string('image')->nullable(); }); } @@ -23,8 +23,8 @@ return new class extends Migration public function down(): void { Schema::table('service_databases', function (Blueprint $table) { - $table->dropColumn('ignore_from_status'); - $table->dropColumn('image_tag'); + $table->dropColumn('exclude_from_status'); + $table->dropColumn('image'); }); } }; diff --git a/database/migrations/2023_09_23_111814_update_local_file_volumes_table.php b/database/migrations/2023_09_23_111814_update_local_file_volumes_table.php new file mode 100644 index 000000000..c1ccd8024 --- /dev/null +++ b/database/migrations/2023_09_23_111814_update_local_file_volumes_table.php @@ -0,0 +1,28 @@ +boolean('is_directory')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('local_file_volumes', function (Blueprint $table) { + $table->dropColumn('is_directory'); + }); + } +}; diff --git a/examples/service-templates.json b/examples/service-templates.json index a102b8784..f5f4ab5c2 100644 --- a/examples/service-templates.json +++ b/examples/service-templates.json @@ -5,6 +5,38 @@ }, "uptime-kuma": { "documentation": "https://github.com/louislam/uptime-kuma", + "generateFqdn": [ + { + "name": "uptime-kuma", + "path": "/" + } + ], "compose": "c2VydmljZXM6CiAgdXB0aW1lLWt1bWE6CiAgICBpbWFnZTogbG91aXNsYW0vdXB0aW1lLWt1bWE6MQogICAgdm9sdW1lczoKICAgICAgLSB1cHRpbWUta3VtYTovYXBwL2RhdGEK" + }, + "appsmith": { + "documentation": "https://docs.appsmith.com/", + "generateFqdn": [ + { + "name": "appsmith" + } + ], + "envs": "QVBQU01JVEhfTUFJTF9FTkFCTEVEPWZhbHNlCkFQUFNNSVRIX0RJU0FCTEVfVEVMRU1FVFJZPXRydWUKQVBQU01JVEhfRElTQUJMRV9JTlRFUkNPTT10cnVlCkFQUFNNSVRIX1NFTlRSWV9EU049CkFQUFNNSVRIX1NNQVJUX0xPT0tfSUQ9", + "compose": "c2VydmljZXM6CiAgYXBwc21pdGg6CiAgICBpbWFnZTogaW5kZXguZG9ja2VyLmlvL2FwcHNtaXRoL2FwcHNtaXRoLWNlCiAgICB2b2x1bWVzOgogICAgICAtIHN0YWNrcy1kYXRhOi9hcHBzbWl0aC1zdGFja3M=" + }, + "appwrite": { + "documentation": "https://appwrite.io/docs", + "generateFqdn": [ + { + "name": "appwrite", + "path": "/" + }, + { + "name": "appwrite-realtime", + "customFqdn": "appwrite", + "path": "/v1/realtime" + } + ], + "envs": "X0FQUF9FTlY9cHJvZHVjdGlvbgpfQVBQX0xPQ0FMRT1lbgpfQVBQX09QVElPTlNfQUJVU0U9ZW5hYmxlZApfQVBQX09QVElPTlNfRk9SQ0VfSFRUUFM9ZGlzYWJsZWQKX0FQUF9PUEVOU1NMX0tFWV9WMT0KX0FQUF9ET01BSU5fRlVOQ1RJT05TPQpfQVBQX0NPTlNPTEVfV0hJVEVMSVNUX1JPT1Q9ZW5hYmxlZApfQVBQX0NPTlNPTEVfV0hJVEVMSVNUX0VNQUlMUz0KX0FQUF9DT05TT0xFX1dISVRFTElTVF9JUFM9Cl9BUFBfU1lTVEVNX0VNQUlMX05BTUU9QXBwd3JpdGUKX0FQUF9TWVNURU1fRU1BSUxfQUREUkVTUz10ZWFtQGFwcHdyaXRlLmlvCl9BUFBfU1lTVEVNX1JFU1BPTlNFX0ZPUk1BVD0KX0FQUF9TWVNURU1fU0VDVVJJVFlfRU1BSUxfQUREUkVTUz1jZXJ0c0BhcHB3cml0ZS5pbwpfQVBQX1VTQUdFX1NUQVRTPWVuYWJsZWQKX0FQUF9MT0dHSU5HX1BST1ZJREVSPQpfQVBQX0xPR0dJTkdfQ09ORklHPQpfQVBQX1VTQUdFX0FHR1JFR0FUSU9OX0lOVEVSVkFMPTMwCl9BUFBfVVNBR0VfVElNRVNFUklFU19JTlRFUlZBTD0zMApfQVBQX1VTQUdFX0RBVEFCQVNFX0lOVEVSVkFMPTkwMApfQVBQX1dPUktFUl9QRVJfQ09SRT02Cl9BUFBfUkVESVNfSE9TVD1yZWRpcwpfQVBQX1JFRElTX1BPUlQ9NjM3OQpfQVBQX1JFRElTX1VTRVI9Cl9BUFBfUkVESVNfUEFTUz0KX0FQUF9EQl9IT1NUPW1hcmlhZGIKX0FQUF9EQl9QT1JUPTMzMDYKX0FQUF9EQl9TQ0hFTUE9YXBwd3JpdGUKX0FQUF9EQl9VU0VSPSRTRVJWSUNFX1VTRVJfTVlTUUwKX0FQUF9EQl9QQVNTPSRTRVJWSUNFX1BBU1NXT1JEX01ZU1FMCl9BUFBfREJfUk9PVF9QQVNTPSRTRVJWSUNFX1BBU1NXT1JEX1JPT1RNWVNRTApfQVBQX0lORkxVWERCX0hPU1Q9aW5mbHV4ZGIKX0FQUF9JTkZMVVhEQl9QT1JUPTgwODYKX0FQUF9TVEFUU0RfSE9TVD10ZWxlZ3JhZgpfQVBQX1NUQVRTRF9QT1JUPTgxMjUKX0FQUF9TTVRQX0hPU1Q9Cl9BUFBfU01UUF9QT1JUPQpfQVBQX1NNVFBfU0VDVVJFPQpfQVBQX1NNVFBfVVNFUk5BTUU9Cl9BUFBfU01UUF9QQVNTV09SRD0KX0FQUF9TTVNfUFJPVklERVI9Cl9BUFBfU01TX0ZST009Cl9BUFBfU1RPUkFHRV9MSU1JVD0zMDAwMDAwMApfQVBQX1NUT1JBR0VfUFJFVklFV19MSU1JVD0yMDAwMDAwMApfQVBQX1NUT1JBR0VfQU5USVZJUlVTPWRpc2FibGVkCl9BUFBfU1RPUkFHRV9BTlRJVklSVVNfSE9TVD1jbGFtYXYKX0FQUF9TVE9SQUdFX0FOVElWSVJVU19QT1JUPTMzMTAKX0FQUF9TVE9SQUdFX0RFVklDRT1sb2NhbApfQVBQX1NUT1JBR0VfUzNfQUNDRVNTX0tFWT0KX0FQUF9TVE9SQUdFX1MzX1NFQ1JFVD0KX0FQUF9TVE9SQUdFX1MzX1JFR0lPTj11cy1lYXN0LTEKX0FQUF9TVE9SQUdFX1MzX0JVQ0tFVD0KX0FQUF9TVE9SQUdFX0RPX1NQQUNFU19BQ0NFU1NfS0VZPQpfQVBQX1NUT1JBR0VfRE9fU1BBQ0VTX1NFQ1JFVD0KX0FQUF9TVE9SQUdFX0RPX1NQQUNFU19SRUdJT049dXMtZWFzdC0xCl9BUFBfU1RPUkFHRV9ET19TUEFDRVNfQlVDS0VUPQpfQVBQX1NUT1JBR0VfQkFDS0JMQVpFX0FDQ0VTU19LRVk9Cl9BUFBfU1RPUkFHRV9CQUNLQkxBWkVfU0VDUkVUPQpfQVBQX1NUT1JBR0VfQkFDS0JMQVpFX1JFR0lPTj11cy13ZXN0LTAwNApfQVBQX1NUT1JBR0VfQkFDS0JMQVpFX0JVQ0tFVD0KX0FQUF9TVE9SQUdFX0xJTk9ERV9BQ0NFU1NfS0VZPQpfQVBQX1NUT1JBR0VfTElOT0RFX1NFQ1JFVD0KX0FQUF9TVE9SQUdFX0xJTk9ERV9SRUdJT049ZXUtY2VudHJhbC0xCl9BUFBfU1RPUkFHRV9MSU5PREVfQlVDS0VUPQpfQVBQX1NUT1JBR0VfV0FTQUJJX0FDQ0VTU19LRVk9Cl9BUFBfU1RPUkFHRV9XQVNBQklfU0VDUkVUPQpfQVBQX1NUT1JBR0VfV0FTQUJJX1JFR0lPTj1ldS1jZW50cmFsLTEKX0FQUF9TVE9SQUdFX1dBU0FCSV9CVUNLRVQ9Cl9BUFBfRlVOQ1RJT05TX1NJWkVfTElNSVQ9MzAwMDAwMDAKX0FQUF9GVU5DVElPTlNfVElNRU9VVD05MDAKX0FQUF9GVU5DVElPTlNfQlVJTERfVElNRU9VVD05MDAKX0FQUF9GVU5DVElPTlNfQ09OVEFJTkVSUz0xMApfQVBQX0ZVTkNUSU9OU19DUFVTPTAKX0FQUF9GVU5DVElPTlNfTUVNT1JZPTAKX0FQUF9GVU5DVElPTlNfTUVNT1JZX1NXQVA9MApfQVBQX0ZVTkNUSU9OU19SVU5USU1FUz1ub2RlLTE2LjAscGhwLTguMCxweXRob24tMy45LHJ1YnktMy4wCl9BUFBfRVhFQ1VUT1JfU0VDUkVUPXlvdXItc2VjcmV0LWtleQpfQVBQX0VYRUNVVE9SX0hPU1Q9aHR0cDovL2FwcHdyaXRlLWV4ZWN1dG9yL3YxCl9BUFBfRVhFQ1VUT1JfUlVOVElNRV9ORVRXT1JLPWFwcHdyaXRlX3J1bnRpbWVzCl9BUFBfRlVOQ1RJT05TX0VOVlM9bm9kZS0xNi4wLHBocC03LjQscHl0aG9uLTMuOSxydWJ5LTMuMApfQVBQX0ZVTkNUSU9OU19JTkFDVElWRV9USFJFU0hPTEQ9NjAKRE9DS0VSSFVCX1BVTExfVVNFUk5BTUU9CkRPQ0tFUkhVQl9QVUxMX1BBU1NXT1JEPQpET0NLRVJIVUJfUFVMTF9FTUFJTD0KT1BFTl9SVU5USU1FU19ORVRXT1JLPWFwcHdyaXRlX3J1bnRpbWVzCl9BUFBfRlVOQ1RJT05TX1JVTlRJTUVTX05FVFdPUks9cnVudGltZXMKX0FQUF9ET0NLRVJfSFVCX1VTRVJOQU1FPQpfQVBQX0RPQ0tFUl9IVUJfUEFTU1dPUkQ9Cl9BUFBfRlVOQ1RJT05TX01BSU5URU5BTkNFX0lOVEVSVkFMPTM2MDAKX0FQUF9WQ1NfR0lUSFVCX0FQUF9OQU1FPQpfQVBQX1ZDU19HSVRIVUJfUFJJVkFURV9LRVk9Cl9BUFBfVkNTX0dJVEhVQl9BUFBfSUQ9Cl9BUFBfVkNTX0dJVEhVQl9DTElFTlRfSUQ9Cl9BUFBfVkNTX0dJVEhVQl9DTElFTlRfU0VDUkVUPQpfQVBQX1ZDU19HSVRIVUJfV0VCSE9PS19TRUNSRVQ9Cl9BUFBfTUFJTlRFTkFOQ0VfSU5URVJWQUw9ODY0MDAKX0FQUF9NQUlOVEVOQU5DRV9SRVRFTlRJT05fQ0FDSEU9MjU5MjAwMApfQVBQX01BSU5URU5BTkNFX1JFVEVOVElPTl9FWEVDVVRJT049MTIwOTYwMApfQVBQX01BSU5URU5BTkNFX1JFVEVOVElPTl9BVURJVD0xMjA5NjAwCl9BUFBfTUFJTlRFTkFOQ0VfUkVURU5USU9OX0FCVVNFPTg2NDAwCl9BUFBfTUFJTlRFTkFOQ0VfUkVURU5USU9OX1VTQUdFX0hPVVJMWT04NjQwMDAwCl9BUFBfTUFJTlRFTkFOQ0VfUkVURU5USU9OX1NDSEVEVUxFUz04NjQwMApfQVBQX0dSQVBIUUxfTUFYX0JBVENIX1NJWkU9MTAKX0FQUF9HUkFQSFFMX01BWF9DT01QTEVYSVRZPTI1MApfQVBQX0dSQVBIUUxfTUFYX0RFUFRIPTMKX0FQUF9NSUdSQVRJT05TX0ZJUkVCQVNFX0NMSUVOVF9JRD0KX0FQUF9NSUdSQVRJT05TX0ZJUkVCQVNFX0NMSUVOVF9TRUNSRVQ9Cl9BUFBfQVNTSVNUQU5UX09QRU5BSV9BUElfS0VZPQ==", + "compose": "eC1sb2dnaW5nOiAmeC1sb2dnaW5nCiAgbG9nZ2luZzoKICAgIGRyaXZlcjogJ2pzb24tZmlsZScKICAgIG9wdGlvbnM6CiAgICAgIG1heC1maWxlOiAnNScKICAgICAgbWF4LXNpemU6ICcxMG0nCnZlcnNpb246ICczJwoKc2VydmljZXM6CiAgYXBwd3JpdGU6CiAgICBpbWFnZTogYXBwd3JpdGUvYXBwd3JpdGU6MS40LjMKICAgIDw8OiAqeC1sb2dnaW5nCiAgICBsYWJlbHM6CiAgICAgIC0gdHJhZWZpay5lbmFibGU9dHJ1ZQogICAgICAtIHRyYWVmaWsuY29uc3RyYWludC1sYWJlbC1zdGFjaz1hcHB3cml0ZQogICAgICAtIHRyYWVmaWsuaHR0cC5zZXJ2aWNlcy5hcHB3cml0ZV9hcGkubG9hZGJhbGFuY2VyLnNlcnZlci5wb3J0PTgwCiAgICAgICNodHRwCiAgICAgIC0gdHJhZWZpay5odHRwLnJvdXRlcnMuYXBwd3JpdGVfYXBpX2h0dHAuZW50cnlwb2ludHM9d2ViCiAgICAgIC0gdHJhZWZpay5odHRwLnJvdXRlcnMuYXBwd3JpdGVfYXBpX2h0dHAucnVsZT1QYXRoUHJlZml4KGAvYCkKICAgICAgLSB0cmFlZmlrLmh0dHAucm91dGVycy5hcHB3cml0ZV9hcGlfaHR0cC5zZXJ2aWNlPWFwcHdyaXRlX2FwaQogICAgICAjIGh0dHBzCiAgICAgIC0gdHJhZWZpay5odHRwLnJvdXRlcnMuYXBwd3JpdGVfYXBpX2h0dHBzLmVudHJ5cG9pbnRzPXdlYnNlY3VyZQogICAgICAtIHRyYWVmaWsuaHR0cC5yb3V0ZXJzLmFwcHdyaXRlX2FwaV9odHRwcy5ydWxlPVBhdGhQcmVmaXgoYC9gKQogICAgICAtIHRyYWVmaWsuaHR0cC5yb3V0ZXJzLmFwcHdyaXRlX2FwaV9odHRwcy5zZXJ2aWNlPWFwcHdyaXRlX2FwaQogICAgICAtIHRyYWVmaWsuaHR0cC5yb3V0ZXJzLmFwcHdyaXRlX2FwaV9odHRwcy50bHM9dHJ1ZQogICAgdm9sdW1lczoKICAgICAgLSBhcHB3cml0ZS11cGxvYWRzOi9zdG9yYWdlL3VwbG9hZHM6cncKICAgICAgLSBhcHB3cml0ZS1jYWNoZTovc3RvcmFnZS9jYWNoZTpydwogICAgICAtIGFwcHdyaXRlLWNvbmZpZzovc3RvcmFnZS9jb25maWc6cncKICAgICAgLSBhcHB3cml0ZS1jZXJ0aWZpY2F0ZXM6L3N0b3JhZ2UvY2VydGlmaWNhdGVzOnJ3CiAgICAgIC0gYXBwd3JpdGUtZnVuY3Rpb25zOi9zdG9yYWdlL2Z1bmN0aW9uczpydwogICAgZGVwZW5kc19vbjoKICAgICAgLSBtYXJpYWRiCiAgICAgIC0gcmVkaXMKIyAgICAgIC0gY2xhbWF2CiAgICAgIC0gaW5mbHV4ZGIKCiAgYXBwd3JpdGUtcmVhbHRpbWU6CiAgICBpbWFnZTogYXBwd3JpdGUvYXBwd3JpdGU6MS40LjMKICAgIGVudHJ5cG9pbnQ6IHJlYWx0aW1lCiAgICA8PDogKngtbG9nZ2luZwogICAgbGFiZWxzOgogICAgICAtIHRyYWVmaWsuZW5hYmxlPXRydWUKICAgICAgLSAidHJhZWZpay5jb25zdHJhaW50LWxhYmVsLXN0YWNrPWFwcHdyaXRlIgogICAgICAtICJ0cmFlZmlrLmh0dHAuc2VydmljZXMuYXBwd3JpdGVfcmVhbHRpbWUubG9hZGJhbGFuY2VyLnNlcnZlci5wb3J0PTgwIgogICAgICAjd3MKICAgICAgLSB0cmFlZmlrLmh0dHAucm91dGVycy5hcHB3cml0ZV9yZWFsdGltZV93cy5lbnRyeXBvaW50cz13ZWIKICAgICAgLSB0cmFlZmlrLmh0dHAucm91dGVycy5hcHB3cml0ZV9yZWFsdGltZV93cy5ydWxlPVBhdGhQcmVmaXgoYC92MS9yZWFsdGltZWApCiAgICAgIC0gdHJhZWZpay5odHRwLnJvdXRlcnMuYXBwd3JpdGVfcmVhbHRpbWVfd3Muc2VydmljZT1hcHB3cml0ZV9yZWFsdGltZQogICAgICAjIHdzcwogICAgICAtIHRyYWVmaWsuaHR0cC5yb3V0ZXJzLmFwcHdyaXRlX3JlYWx0aW1lX3dzcy5lbnRyeXBvaW50cz13ZWJzZWN1cmUKICAgICAgLSB0cmFlZmlrLmh0dHAucm91dGVycy5hcHB3cml0ZV9yZWFsdGltZV93c3MucnVsZT1QYXRoUHJlZml4KGAvdjEvcmVhbHRpbWVgKQogICAgICAtIHRyYWVmaWsuaHR0cC5yb3V0ZXJzLmFwcHdyaXRlX3JlYWx0aW1lX3dzcy5zZXJ2aWNlPWFwcHdyaXRlX3JlYWx0aW1lCiAgICAgIC0gdHJhZWZpay5odHRwLnJvdXRlcnMuYXBwd3JpdGVfcmVhbHRpbWVfd3NzLnRscz10cnVlCiAgICAgIC0gdHJhZWZpay5odHRwLnJvdXRlcnMuYXBwd3JpdGVfcmVhbHRpbWVfd3NzLnRscy5jZXJ0cmVzb2x2ZXI9ZG5zCiAgICBkZXBlbmRzX29uOgogICAgICAtIG1hcmlhZGIKICAgICAgLSByZWRpcwoKICBhcHB3cml0ZS13b3JrZXItYXVkaXRzOgogICAgaW1hZ2U6IGFwcHdyaXRlL2FwcHdyaXRlOjEuNC4zCiAgICBlbnRyeXBvaW50OiB3b3JrZXItYXVkaXRzCiAgICA8PDogKngtbG9nZ2luZwogICAgZGVwZW5kc19vbjoKICAgICAgLSByZWRpcwogICAgICAtIG1hcmlhZGIKCiAgYXBwd3JpdGUtd29ya2VyLXdlYmhvb2tzOgogICAgaW1hZ2U6IGFwcHdyaXRlL2FwcHdyaXRlOjEuNC4zCiAgICBlbnRyeXBvaW50OiB3b3JrZXItd2ViaG9va3MKICAgIDw8OiAqeC1sb2dnaW5nCiAgICBkZXBlbmRzX29uOgogICAgICAtIHJlZGlzCiAgICAgIC0gbWFyaWFkYgoKICBhcHB3cml0ZS13b3JrZXItZGVsZXRlczoKICAgIGltYWdlOiBhcHB3cml0ZS9hcHB3cml0ZToxLjQuMwogICAgZW50cnlwb2ludDogd29ya2VyLWRlbGV0ZXMKICAgIDw8OiAqeC1sb2dnaW5nCiAgICBkZXBlbmRzX29uOgogICAgICAtIHJlZGlzCiAgICAgIC0gbWFyaWFkYgogICAgdm9sdW1lczogCiAgICAgIC0gYXBwd3JpdGUtdXBsb2Fkczovc3RvcmFnZS91cGxvYWRzOnJ3CiAgICAgIC0gYXBwd3JpdGUtY2FjaGU6L3N0b3JhZ2UvY2FjaGU6cncKICAgICAgLSBhcHB3cml0ZS1mdW5jdGlvbnM6L3N0b3JhZ2UvZnVuY3Rpb25zOnJ3CiAgICAgIC0gYXBwd3JpdGUtYnVpbGRzOi9zdG9yYWdlL2J1aWxkczpydwogICAgICAtIGFwcHdyaXRlLWNlcnRpZmljYXRlczovc3RvcmFnZS9jZXJ0aWZpY2F0ZXM6cncKCiAgYXBwd3JpdGUtd29ya2VyLWRhdGFiYXNlczoKICAgIGltYWdlOiBhcHB3cml0ZS9hcHB3cml0ZToxLjQuMwogICAgZW50cnlwb2ludDogd29ya2VyLWRhdGFiYXNlcwogICAgPDw6ICp4LWxvZ2dpbmcKICAgIGRlcGVuZHNfb246CiAgICAgIC0gcmVkaXMKICAgICAgLSBtYXJpYWRiCgogIGFwcHdyaXRlLXdvcmtlci1idWlsZHM6CiAgICBpbWFnZTogYXBwd3JpdGUvYXBwd3JpdGU6MS40LjMKICAgIGVudHJ5cG9pbnQ6IHdvcmtlci1idWlsZHMKICAgIDw8OiAqeC1sb2dnaW5nCiAgICBkZXBlbmRzX29uOgogICAgICAtIHJlZGlzCiAgICAgIC0gbWFyaWFkYgogICAgdm9sdW1lczoKICAgICAgLSBhcHB3cml0ZS1mdW5jdGlvbnM6L3N0b3JhZ2UvZnVuY3Rpb25zOnJ3CiAgICAgIC0gYXBwd3JpdGUtYnVpbGRzOi9zdG9yYWdlL2J1aWxkczpydwoKICBhcHB3cml0ZS13b3JrZXItY2VydGlmaWNhdGVzOgogICAgaW1hZ2U6IGFwcHdyaXRlL2FwcHdyaXRlOjEuNC4zCiAgICBlbnRyeXBvaW50OiB3b3JrZXItY2VydGlmaWNhdGVzCiAgICA8PDogKngtbG9nZ2luZwogICAgZGVwZW5kc19vbjoKICAgICAgLSByZWRpcwogICAgICAtIG1hcmlhZGIKICAgIHZvbHVtZXM6IAogICAgICAtIGFwcHdyaXRlLWNvbmZpZzovc3RvcmFnZS9jb25maWc6cncKICAgICAgLSBhcHB3cml0ZS1jZXJ0aWZpY2F0ZXM6L3N0b3JhZ2UvY2VydGlmaWNhdGVzOnJ3CgogIGFwcHdyaXRlLXdvcmtlci1mdW5jdGlvbnM6CiAgICBpbWFnZTogYXBwd3JpdGUvYXBwd3JpdGU6MS40LjMKICAgIGVudHJ5cG9pbnQ6IHdvcmtlci1mdW5jdGlvbnMKICAgIDw8OiAqeC1sb2dnaW5nCiAgICBkZXBlbmRzX29uOgogICAgICAtIHJlZGlzCiAgICAgIC0gbWFyaWFkYgogICAgICAtIG9wZW5ydW50aW1lcy1leGVjdXRvcgoKICBhcHB3cml0ZS13b3JrZXItbWFpbHM6CiAgICBpbWFnZTogYXBwd3JpdGUvYXBwd3JpdGU6MS40LjMKICAgIGVudHJ5cG9pbnQ6IHdvcmtlci1tYWlscwogICAgPDw6ICp4LWxvZ2dpbmcKICAgIGRlcGVuZHNfb246CiAgICAgIC0gcmVkaXMKCiAgYXBwd3JpdGUtd29ya2VyLW1lc3NhZ2luZzoKICAgIGltYWdlOiBhcHB3cml0ZS9hcHB3cml0ZToxLjQuMwogICAgZW50cnlwb2ludDogd29ya2VyLW1lc3NhZ2luZwogICAgPDw6ICp4LWxvZ2dpbmcKICAgIGRlcGVuZHNfb246CiAgICAgIC0gcmVkaXMKCiAgYXBwd3JpdGUtd29ya2VyLW1pZ3JhdGlvbnM6CiAgICBpbWFnZTogYXBwd3JpdGUvYXBwd3JpdGU6MS40LjMKICAgIGVudHJ5cG9pbnQ6IHdvcmtlci1taWdyYXRpb25zCiAgICA8PDogKngtbG9nZ2luZwogICAgZGVwZW5kc19vbjoKICAgICAgLSBtYXJpYWRiCgogIGFwcHdyaXRlLW1haW50ZW5hbmNlOgogICAgaW1hZ2U6IGFwcHdyaXRlL2FwcHdyaXRlOjEuNC4zCiAgICBlbnRyeXBvaW50OiBtYWludGVuYW5jZQogICAgPDw6ICp4LWxvZ2dpbmcKICAgIGRlcGVuZHNfb246CiAgICAgIC0gcmVkaXMKCiAgYXBwd3JpdGUtdXNhZ2U6CiAgICBpbWFnZTogYXBwd3JpdGUvYXBwd3JpdGU6MS40LjMKICAgIGVudHJ5cG9pbnQ6IHVzYWdlCiAgICA8PDogKngtbG9nZ2luZwogICAgZGVwZW5kc19vbjoKICAgICAgLSBpbmZsdXhkYgogICAgICAtIG1hcmlhZGIKCiAgYXBwd3JpdGUtc2NoZWR1bGU6CiAgICBpbWFnZTogYXBwd3JpdGUvYXBwd3JpdGU6MS40LjMKICAgIGVudHJ5cG9pbnQ6IHNjaGVkdWxlCiAgICA8PDogKngtbG9nZ2luZwogICAgZGVwZW5kc19vbjoKICAgICAgLSBtYXJpYWRiCiAgICAgIC0gcmVkaXMKCiAgYXBwd3JpdGUtYXNzaXN0YW50OgogICAgaW1hZ2U6IGFwcHdyaXRlL2Fzc2lzdGFudDowLjIuMQogICAgPDw6ICp4LWxvZ2dpbmcKICAgIHJlc3RhcnQ6IHVubGVzcy1zdG9wcGVkCgogIG9wZW5ydW50aW1lcy1leGVjdXRvcjoKICAgIGhvc3RuYW1lOiBhcHB3cml0ZS1leGVjdXRvcgogICAgPDw6ICp4LWxvZ2dpbmcKICAgIHN0b3Bfc2lnbmFsOiBTSUdJTlQKICAgIGltYWdlOiBvcGVucnVudGltZXMvZXhlY3V0b3I6MC40LjEKICAgIHZvbHVtZXM6CiAgICAgIC0gL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2sKICAgICAgLSBhcHB3cml0ZS1idWlsZHM6L3N0b3JhZ2UvYnVpbGRzOnJ3CiAgICAgIC0gYXBwd3JpdGUtZnVuY3Rpb25zOi9zdG9yYWdlL2Z1bmN0aW9uczpydwogICAgICAjIEhvc3QgbW91bnQgbmVzc2Vzc2FyeSB0byBzaGFyZSBmaWxlcyBiZXR3ZWVuIGV4ZWN1dG9yIGFuZCBydW50aW1lcy4KICAgICAgIyBJdCdzIG5vdCBwb3NzaWJsZSB0byBzaGFyZSBtb3VudCBmaWxlIGJldHdlZW4gMiBjb250YWluZXJzIHdpdGhvdXQgaG9zdCBtb3VudCAoY29weWluZyBpcyB0b28gc2xvdykKICAgICAgLSAvdG1wOi90bXA6cncKICAgIGVudmlyb25tZW50OgogICAgICAtIE9QUl9FWEVDVVRPUl9JTkFDVElWRV9UUkVTSE9MRD0kX0FQUF9GVU5DVElPTlNfSU5BQ1RJVkVfVEhSRVNIT0xECiAgICAgIC0gT1BSX0VYRUNVVE9SX01BSU5URU5BTkNFX0lOVEVSVkFMPSRfQVBQX0ZVTkNUSU9OU19NQUlOVEVOQU5DRV9JTlRFUlZBTAogICAgICAtIE9QUl9FWEVDVVRPUl9ORVRXT1JLPSRfQVBQX0ZVTkNUSU9OU19SVU5USU1FU19ORVRXT1JLCiAgICAgIC0gT1BSX0VYRUNVVE9SX0RPQ0tFUl9IVUJfVVNFUk5BTUU9JF9BUFBfRE9DS0VSX0hVQl9VU0VSTkFNRQogICAgICAtIE9QUl9FWEVDVVRPUl9ET0NLRVJfSFVCX1BBU1NXT1JEPSRfQVBQX0RPQ0tFUl9IVUJfUEFTU1dPUkQKICAgICAgLSBPUFJfRVhFQ1VUT1JfRU5WPSRfQVBQX0VOVgogICAgICAtIE9QUl9FWEVDVVRPUl9SVU5USU1FUz0kX0FQUF9GVU5DVElPTlNfUlVOVElNRVMKICAgICAgLSBPUFJfRVhFQ1VUT1JfU0VDUkVUPSRfQVBQX0VYRUNVVE9SX1NFQ1JFVAogICAgICAtIE9QUl9FWEVDVVRPUl9MT0dHSU5HX1BST1ZJREVSPSRfQVBQX0xPR0dJTkdfUFJPVklERVIKICAgICAgLSBPUFJfRVhFQ1VUT1JfTE9HR0lOR19DT05GSUc9JF9BUFBfTE9HR0lOR19DT05GSUcKICAgICAgLSBPUFJfRVhFQ1VUT1JfU1RPUkFHRV9ERVZJQ0U9JF9BUFBfU1RPUkFHRV9ERVZJQ0UKICAgICAgLSBPUFJfRVhFQ1VUT1JfU1RPUkFHRV9TM19BQ0NFU1NfS0VZPSRfQVBQX1NUT1JBR0VfUzNfQUNDRVNTX0tFWQogICAgICAtIE9QUl9FWEVDVVRPUl9TVE9SQUdFX1MzX1NFQ1JFVD0kX0FQUF9TVE9SQUdFX1MzX1NFQ1JFVAogICAgICAtIE9QUl9FWEVDVVRPUl9TVE9SQUdFX1MzX1JFR0lPTj0kX0FQUF9TVE9SQUdFX1MzX1JFR0lPTgogICAgICAtIE9QUl9FWEVDVVRPUl9TVE9SQUdFX1MzX0JVQ0tFVD0kX0FQUF9TVE9SQUdFX1MzX0JVQ0tFVAogICAgICAtIE9QUl9FWEVDVVRPUl9TVE9SQUdFX0RPX1NQQUNFU19BQ0NFU1NfS0VZPSRfQVBQX1NUT1JBR0VfRE9fU1BBQ0VTX0FDQ0VTU19LRVkKICAgICAgLSBPUFJfRVhFQ1VUT1JfU1RPUkFHRV9ET19TUEFDRVNfU0VDUkVUPSRfQVBQX1NUT1JBR0VfRE9fU1BBQ0VTX1NFQ1JFVAogICAgICAtIE9QUl9FWEVDVVRPUl9TVE9SQUdFX0RPX1NQQUNFU19SRUdJT049JF9BUFBfU1RPUkFHRV9ET19TUEFDRVNfUkVHSU9OCiAgICAgIC0gT1BSX0VYRUNVVE9SX1NUT1JBR0VfRE9fU1BBQ0VTX0JVQ0tFVD0kX0FQUF9TVE9SQUdFX0RPX1NQQUNFU19CVUNLRVQKICAgICAgLSBPUFJfRVhFQ1VUT1JfU1RPUkFHRV9CQUNLQkxBWkVfQUNDRVNTX0tFWT0kX0FQUF9TVE9SQUdFX0JBQ0tCTEFaRV9BQ0NFU1NfS0VZCiAgICAgIC0gT1BSX0VYRUNVVE9SX1NUT1JBR0VfQkFDS0JMQVpFX1NFQ1JFVD0kX0FQUF9TVE9SQUdFX0JBQ0tCTEFaRV9TRUNSRVQKICAgICAgLSBPUFJfRVhFQ1VUT1JfU1RPUkFHRV9CQUNLQkxBWkVfUkVHSU9OPSRfQVBQX1NUT1JBR0VfQkFDS0JMQVpFX1JFR0lPTgogICAgICAtIE9QUl9FWEVDVVRPUl9TVE9SQUdFX0JBQ0tCTEFaRV9CVUNLRVQ9JF9BUFBfU1RPUkFHRV9CQUNLQkxBWkVfQlVDS0VUCiAgICAgIC0gT1BSX0VYRUNVVE9SX1NUT1JBR0VfTElOT0RFX0FDQ0VTU19LRVk9JF9BUFBfU1RPUkFHRV9MSU5PREVfQUNDRVNTX0tFWQogICAgICAtIE9QUl9FWEVDVVRPUl9TVE9SQUdFX0xJTk9ERV9TRUNSRVQ9JF9BUFBfU1RPUkFHRV9MSU5PREVfU0VDUkVUCiAgICAgIC0gT1BSX0VYRUNVVE9SX1NUT1JBR0VfTElOT0RFX1JFR0lPTj0kX0FQUF9TVE9SQUdFX0xJTk9ERV9SRUdJT04KICAgICAgLSBPUFJfRVhFQ1VUT1JfU1RPUkFHRV9MSU5PREVfQlVDS0VUPSRfQVBQX1NUT1JBR0VfTElOT0RFX0JVQ0tFVAogICAgICAtIE9QUl9FWEVDVVRPUl9TVE9SQUdFX1dBU0FCSV9BQ0NFU1NfS0VZPSRfQVBQX1NUT1JBR0VfV0FTQUJJX0FDQ0VTU19LRVkKICAgICAgLSBPUFJfRVhFQ1VUT1JfU1RPUkFHRV9XQVNBQklfU0VDUkVUPSRfQVBQX1NUT1JBR0VfV0FTQUJJX1NFQ1JFVAogICAgICAtIE9QUl9FWEVDVVRPUl9TVE9SQUdFX1dBU0FCSV9SRUdJT049JF9BUFBfU1RPUkFHRV9XQVNBQklfUkVHSU9OCiAgICAgIC0gT1BSX0VYRUNVVE9SX1NUT1JBR0VfV0FTQUJJX0JVQ0tFVD0kX0FQUF9TVE9SQUdFX1dBU0FCSV9CVUNLRVQKCiAgbWFyaWFkYjoKICAgIGltYWdlOiBtYXJpYWRiOjEwLjcgIyBmaXggaXNzdWVzIHdoZW4gdXBncmFkaW5nIHVzaW5nOiBteXNxbF91cGdyYWRlIC11IHJvb3QgLXAKICAgIDw8OiAqeC1sb2dnaW5nCiAgICB2b2x1bWVzOgogICAgICAtIGFwcHdyaXRlLW1hcmlhZGI6L3Zhci9saWIvbXlzcWw6cncKICAgIGVudmlyb25tZW50OgogICAgICAtIE1ZU1FMX1JPT1RfUEFTU1dPUkQ9JHtfQVBQX0RCX1JPT1RfUEFTU30KICAgICAgLSBNWVNRTF9EQVRBQkFTRT0ke19BUFBfREJfU0NIRU1BfQogICAgICAtIE1ZU1FMX1VTRVI9JHtfQVBQX0RCX1VTRVJ9CiAgICAgIC0gTVlTUUxfUEFTU1dPUkQ9JHtfQVBQX0RCX1BBU1N9CiAgICBjb21tYW5kOiAnbXlzcWxkIC0taW5ub2RiLWZsdXNoLW1ldGhvZD1mc3luYycKCiAgcmVkaXM6CiAgICBpbWFnZTogcmVkaXM6Ny4wLjQtYWxwaW5lCiAgICA8PDogKngtbG9nZ2luZwogICAgY29tbWFuZDogPgogICAgICByZWRpcy1zZXJ2ZXIKICAgICAgLS1tYXhtZW1vcnkgICAgICAgICAgICA1MTJtYgogICAgICAtLW1heG1lbW9yeS1wb2xpY3kgICAgIGFsbGtleXMtbHJ1CiAgICAgIC0tbWF4bWVtb3J5LXNhbXBsZXMgICAgNQogICAgdm9sdW1lczoKICAgICAgLSBhcHB3cml0ZS1yZWRpczovZGF0YTpydwoKICBjbGFtYXY6CiAgICBpbWFnZTogYXBwd3JpdGUvY2xhbWF2OjEuMi4wCiAgICB2b2x1bWVzOgogICAgICAtIGFwcHdyaXRlLXVwbG9hZHM6L3N0b3JhZ2UvdXBsb2FkcwoKICBpbmZsdXhkYjoKICAgIGltYWdlOiBhcHB3cml0ZS9pbmZsdXhkYjoxLjUuMAogICAgPDw6ICp4LWxvZ2dpbmcKICAgIHZvbHVtZXM6CiAgICAgIC0gYXBwd3JpdGUtaW5mbHV4ZGI6L3Zhci9saWIvaW5mbHV4ZGI6cncKCiAgdGVsZWdyYWY6CiAgICBpbWFnZTogYXBwd3JpdGUvdGVsZWdyYWY6MS40LjAKICAgIDw8OiAqeC1sb2dnaW5nCg== " } } diff --git a/resources/views/components/navbar-subscription.blade.php b/resources/views/components/navbar-subscription.blade.php index 6269d2008..91629f99e 100644 --- a/resources/views/components/navbar-subscription.blade.php +++ b/resources/views/components/navbar-subscription.blade.php @@ -11,12 +11,13 @@ -
  • -
    - - +
  • +
    + + - Feedback + Feedback
  • diff --git a/resources/views/components/navbar.blade.php b/resources/views/components/navbar.blade.php index c6ed31ef7..4a86f7dd5 100644 --- a/resources/views/components/navbar.blade.php +++ b/resources/views/components/navbar.blade.php @@ -116,8 +116,8 @@ @endif @if (isSubscriptionActive() || isDev())
  • -
    - +
    + diff --git a/resources/views/components/services/navbar.blade.php b/resources/views/components/services/navbar.blade.php index 779e8ccfe..0bf49f3e3 100644 --- a/resources/views/components/services/navbar.blade.php +++ b/resources/views/components/services/navbar.blade.php @@ -1,27 +1,7 @@ diff --git a/resources/views/livewire/activity-monitor.blade.php b/resources/views/livewire/activity-monitor.blade.php index 2b83d863a..97d3bd64c 100644 --- a/resources/views/livewire/activity-monitor.blade.php +++ b/resources/views/livewire/activity-monitor.blade.php @@ -10,7 +10,7 @@
    @endif
    + class="scrollbar flex flex-col-reverse w-full overflow-y-auto border border-solid rounded border-coolgray-300 max-h-[32rem] p-4 pt-6 text-xs text-white">
    {{ RunRemoteProcess::decodeOutput($this->activity) }}
    diff --git a/resources/views/livewire/project/new/docker-compose.blade.php b/resources/views/livewire/project/new/docker-compose.blade.php index 8869dde0b..2ec301b6b 100644 --- a/resources/views/livewire/project/new/docker-compose.blade.php +++ b/resources/views/livewire/project/new/docker-compose.blade.php @@ -44,5 +44,6 @@ - MYSQL_DATABASE=${MYSQL_DATABASE} - MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQL_ROOT} '> +
    diff --git a/resources/views/livewire/project/service/application.blade.php b/resources/views/livewire/project/service/application.blade.php index 69f47fc5d..5c26c950b 100644 --- a/resources/views/livewire/project/service/application.blade.php +++ b/resources/views/livewire/project/service/application.blade.php @@ -17,22 +17,30 @@
    - - + @if ($application->required_fqdn) + + @else + + @endif +
    +

    Advanced

    - + id="application.exclude_from_status">
    @if ($fileStorages->count() > 0) -

    Files

    +

    Mounted Files (binds)

    - @foreach ($fileStorages->get() as $fileStorage) + @foreach ($fileStorages as $fileStorage) @endforeach
    diff --git a/resources/views/livewire/project/service/database.blade.php b/resources/views/livewire/project/service/database.blade.php index 596ef3ec7..fbde06a74 100644 --- a/resources/views/livewire/project/service/database.blade.php +++ b/resources/views/livewire/project/service/database.blade.php @@ -15,21 +15,21 @@
    - +

    Advanced

    - + id="database.exclude_from_status">
    - @if ($database->fileStorages()->get()->count() > 0) + @if ($fileStorages->count() > 0)

    Mounted Files (binds)

    - @foreach ($database->fileStorages()->get() as $fileStorage) + @foreach ($fileStorages as $fileStorage) @endforeach
    diff --git a/resources/views/livewire/project/service/file-storage.blade.php b/resources/views/livewire/project/service/file-storage.blade.php index b70d505dd..ab90058bf 100644 --- a/resources/views/livewire/project/service/file-storage.blade.php +++ b/resources/views/livewire/project/service/file-storage.blade.php @@ -1,20 +1,26 @@
    {{ $fileStorage->mount_path }} - @empty($fileStorage->content) + @if(is_null($fileStorage->content) && !$fileStorage->is_directory) (empty) - @endempty + @endif
    -
    - - +
    +
    - - - Save + + @if (!$fileStorage->is_directory) +
    + + +
    + + + Save + @endif diff --git a/resources/views/livewire/project/service/index.blade.php b/resources/views/livewire/project/service/index.blade.php index bc6cd94cd..b2fc8c422 100644 --- a/resources/views/livewire/project/service/index.blade.php +++ b/resources/views/livewire/project/service/index.blade.php @@ -28,7 +28,10 @@
    -

    Service Stack

    +
    +

    Service Stack

    + Refresh +
    diff --git a/resources/views/livewire/project/shared/environment-variable/all.blade.php b/resources/views/livewire/project/shared/environment-variable/all.blade.php index fe9788fa6..6297f3822 100644 --- a/resources/views/livewire/project/shared/environment-variable/all.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/all.blade.php @@ -28,13 +28,13 @@ @endif @else
    - Save
    @if ($showPreview)
    - Save
    diff --git a/resources/views/livewire/server/proxy/status.blade.php b/resources/views/livewire/server/proxy/status.blade.php index 69007d59f..b477752d7 100644 --- a/resources/views/livewire/server/proxy/status.blade.php +++ b/resources/views/livewire/server/proxy/status.blade.php @@ -8,16 +8,7 @@ @else @endif - + Refresh @endif