diff --git a/app/Actions/Service/StartService.php b/app/Actions/Service/StartService.php index c89759bf7..1dbabc6a5 100644 --- a/app/Actions/Service/StartService.php +++ b/app/Actions/Service/StartService.php @@ -18,6 +18,7 @@ class StartService $docker_compose_base64 = base64_encode($service->docker_compose); $commands[] = "echo $docker_compose_base64 | base64 -d > docker-compose.yml"; $envs = $service->environment_variables()->get(); + $commands[] = "rm -f .env || true"; foreach ($envs as $env) { $commands[] = "echo '{$env->key}={$env->value}' >> .env"; } diff --git a/app/Http/Livewire/Project/Service/Application.php b/app/Http/Livewire/Project/Service/Application.php new file mode 100644 index 000000000..0177de30c --- /dev/null +++ b/app/Http/Livewire/Project/Service/Application.php @@ -0,0 +1,30 @@ + 'nullable', + 'application.fqdn' => 'nullable', + ]; + public function render() + { + return view('livewire.project.service.application'); + } + public function submit() + { + try { + $this->validate(); + $this->application->save(); + } catch (\Throwable $e) { + ray($e); + } finally { + $this->emit('generateDockerCompose'); + } + } +} diff --git a/app/Http/Livewire/Project/Service/Database.php b/app/Http/Livewire/Project/Service/Database.php new file mode 100644 index 000000000..709eb2c35 --- /dev/null +++ b/app/Http/Livewire/Project/Service/Database.php @@ -0,0 +1,30 @@ + 'nullable', + ]; + public function render() + { + return view('livewire.project.service.database'); + } + public function submit() + { + try { + $this->validate(); + $this->database->save(); + } catch (\Throwable $e) { + ray($e); + } finally { + $this->emit('generateDockerCompose'); + } + } +} diff --git a/app/Http/Livewire/Project/Service/Index.php b/app/Http/Livewire/Project/Service/Index.php index 09a0c059f..8b70f369f 100644 --- a/app/Http/Livewire/Project/Service/Index.php +++ b/app/Http/Livewire/Project/Service/Index.php @@ -15,73 +15,25 @@ class Index extends Component public array $parameters; public array $query; - public Collection $services; - protected $listeners = ['serviceStatusUpdated']; protected $rules = [ - 'services.*.fqdn' => 'nullable', + 'service.docker_compose_raw' => 'required', + 'service.docker_compose' => 'required', ]; public function mount() { - $this->services = collect([]); $this->parameters = get_route_parameters(); $this->query = request()->query(); $this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail(); - foreach ($this->service->applications as $application) { - $this->services->put($application->name, [ - 'fqdn' => $application->fqdn, - ]); - } - // foreach ($this->service->databases as $database) { - // $this->services->put($database->name, $database->fqdn); - // } } public function render() { - return view('livewire.project.service.index')->layout('layouts.app'); + return view('livewire.project.service.index'); } - public function serviceStatusUpdated() { - ray('serviceStatusUpdated'); - $this->check_status(); - } - public function check_status() - { - dispatch_sync(new ContainerStatusJob($this->service->server)); - $this->service->refresh(); - } - public function submit() - { - try { - if ($this->services->count() === 0) { - return; - } - foreach ($this->services as $name => $value) { - $foundService = $this->service->applications()->whereName($name)->first(); - if ($foundService) { - $foundService->fqdn = data_get($value, 'fqdn'); - $foundService->save(); - return; - } - $foundService = $this->service->databases()->whereName($name)->first(); - if ($foundService) { - // $foundService->save(); - return; - } - } - } catch (\Throwable $e) { - ray($e); - } finally { - $this->service->parse(); - } - } - public function deploy() - { + public function save() { + $this->service->save(); $this->service->parse(); - $activity = StartService::run($this->service); - $this->emit('newMonitorActivity', $activity->id); - } - public function stop() { - StopService::run($this->service); - $this->service->refresh(); + $this->emit('refreshEnvs'); } + } diff --git a/app/Http/Livewire/Project/Service/Navbar.php b/app/Http/Livewire/Project/Service/Navbar.php new file mode 100644 index 000000000..b7f2730b6 --- /dev/null +++ b/app/Http/Livewire/Project/Service/Navbar.php @@ -0,0 +1,43 @@ +check_status(); + } + public function check_status() + { + dispatch_sync(new ContainerStatusJob($this->service->server)); + $this->service->refresh(); + } + public function deploy() + { + $this->service->parse(); + $activity = StartService::run($this->service); + $this->emit('newMonitorActivity', $activity->id); + } + public function stop() + { + StopService::run($this->service); + $this->service->refresh(); + } +} diff --git a/app/Http/Livewire/Project/Service/Show.php b/app/Http/Livewire/Project/Service/Show.php new file mode 100644 index 000000000..7a9e62df2 --- /dev/null +++ b/app/Http/Livewire/Project/Service/Show.php @@ -0,0 +1,42 @@ +services = collect([]); + $this->parameters = get_route_parameters(); + $this->query = request()->query(); + $this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail(); + $service = $this->service->applications()->whereName($this->parameters['service_name'])->first(); + if ($service) { + $this->serviceApplication = $service; + } else { + $this->serviceDatabase = $this->service->databases()->whereName($this->parameters['service_name'])->first(); + } + } + public function generateDockerCompose() + { + $this->service->parse(); + } + public function render() + { + return view('livewire.project.service.show'); + } +} diff --git a/app/Http/Livewire/Project/Shared/Danger.php b/app/Http/Livewire/Project/Shared/Danger.php index 911192a2a..c88ade583 100644 --- a/app/Http/Livewire/Project/Shared/Danger.php +++ b/app/Http/Livewire/Project/Shared/Danger.php @@ -19,13 +19,24 @@ class Danger extends Component public function delete() { - $destination = $this->resource->destination->getMorphClass()::where('id', $this->resource->destination->id)->first(); - - instant_remote_process(["docker rm -f {$this->resource->uuid}"], $destination->server); - $this->resource->delete(); - return redirect()->route('project.resources', [ - 'project_uuid' => $this->parameters['project_uuid'], - 'environment_name' => $this->parameters['environment_name'] - ]); + try { + if ($this->resource->type() === 'service') { + $server = $this->resource->server; + } else { + $destination = data_get($this->resource, 'destination'); + if ($destination) { + $destination = $this->resource->destination->getMorphClass()::where('id', $this->resource->destination->id)->first(); + $server = $destination->server; + } + } + instant_remote_process(["docker rm -f {$this->resource->uuid}"], $server); + $this->resource->delete(); + return redirect()->route('project.resources', [ + 'project_uuid' => $this->parameters['project_uuid'], + 'environment_name' => $this->parameters['environment_name'] + ]); + } catch (\Throwable $e) { + return handleError($e); + } } } diff --git a/app/Http/Livewire/Project/Shared/EnvironmentVariable/Add.php b/app/Http/Livewire/Project/Shared/EnvironmentVariable/Add.php index cf14a894e..af94ed77f 100644 --- a/app/Http/Livewire/Project/Shared/EnvironmentVariable/Add.php +++ b/app/Http/Livewire/Project/Shared/EnvironmentVariable/Add.php @@ -31,7 +31,6 @@ class Add extends Component public function submit() { - ray('submitting'); $this->validate(); $this->emitUp('submit', [ 'key' => $this->key, diff --git a/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php index bdb6ab50b..6d494e293 100644 --- a/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -53,6 +53,7 @@ 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(); } @@ -68,11 +69,16 @@ class All extends Component $environment->value = $variable; $environment->is_build_time = false; $environment->is_preview = $isPreview ? true : false; - 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(); } diff --git a/app/Http/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Http/Livewire/Project/Shared/EnvironmentVariable/Show.php index cfc94af22..4eaa1231c 100644 --- a/app/Http/Livewire/Project/Shared/EnvironmentVariable/Show.php +++ b/app/Http/Livewire/Project/Shared/EnvironmentVariable/Show.php @@ -10,7 +10,9 @@ class Show extends Component { public $parameters; public ModelsEnvironmentVariable $env; - public string|null $modalId = null; + public ?string $modalId = null; + public string $type; + protected $rules = [ 'env.key' => 'required|string', 'env.value' => 'required|string', @@ -37,6 +39,7 @@ class Show extends Component $this->validate(); $this->env->save(); $this->emit('success', 'Environment variable updated successfully.'); + $this->emit('refreshEnvs'); } public function delete() diff --git a/app/Http/Livewire/Project/Shared/Storages/Show.php b/app/Http/Livewire/Project/Shared/Storages/Show.php index 7cbf322f7..84593aef3 100644 --- a/app/Http/Livewire/Project/Shared/Storages/Show.php +++ b/app/Http/Livewire/Project/Shared/Storages/Show.php @@ -2,13 +2,16 @@ namespace App\Http\Livewire\Project\Shared\Storages; +use App\Models\LocalPersistentVolume; use Livewire\Component; use Visus\Cuid2\Cuid2; class Show extends Component { - public $storage; - public string|null $modalId = null; + public LocalPersistentVolume $storage; + public bool $isReadOnly = false; + public ?string $modalId = null; + protected $rules = [ 'storage.name' => 'required|string', 'storage.mount_path' => 'required|string', diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php index e407dabdf..5b0ca1683 100644 --- a/app/Jobs/ContainerStatusJob.php +++ b/app/Jobs/ContainerStatusJob.php @@ -170,40 +170,32 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted if (in_array("$service->id-$app->name", $foundServices)) { continue; } else { - $exitedServices->push($service); - $app->update(['status' => 'exited']); + $exitedServices->push($app); } } foreach ($dbs as $db) { if (in_array("$service->id-$db->name", $foundServices)) { continue; } else { - $exitedServices->push($service); - $db->update(['status' => 'exited']); + $exitedServices->push($db); } } - } + } $exitedServices = $exitedServices->unique('id'); - ray($exitedServices); - // ray($exitedServices); - // foreach ($serviceIds as $serviceId) { - // $service = $services->where('id', $serviceId)->first(); - // if ($service->status === 'exited') { - // continue; - // } + foreach ($exitedServices as $exitedService) { + if ($exitedService->status === 'exited') { + continue; + } + $name = data_get($exitedService, 'name'); + $fqdn = data_get($exitedService, 'fqdn'); + $containerName = $name ? "$name ($fqdn)" : $fqdn; + $project = data_get($service, 'environment.project'); + $environment = data_get($service, 'environment'); - // $name = data_get($service, 'name'); - // $fqdn = data_get($service, 'fqdn'); - - // $containerName = $name ? "$name ($fqdn)" : $fqdn; - - // $project = data_get($service, 'environment.project'); - // $environment = data_get($service, 'environment'); - - // $url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/service/" . $service->uuid; - - // $this->server->team->notify(new ContainerStopped($containerName, $this->server, $url)); - // } + $url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/service/" . $service->uuid; + $this->server->team->notify(new ContainerStopped($containerName, $this->server, $url)); + $exitedService->update(['status' => 'exited']); + } $notRunningApplications = $applications->pluck('id')->diff($foundApplications); foreach ($notRunningApplications as $applicationId) { diff --git a/app/Models/Service.php b/app/Models/Service.php index e713d5130..fb04608b8 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -3,7 +3,6 @@ namespace App\Models; use App\Enums\ProxyTypes; -use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Support\Collection; @@ -14,27 +13,26 @@ class Service extends BaseModel { use HasFactory; protected $guarded = []; - public function persistentStorages() - { - return $this->morphMany(LocalPersistentVolume::class, 'resource'); - } - public function portsMappingsArray(): Attribute - { - return Attribute::make( - get: fn () => is_null($this->ports_mappings) - ? [] - : explode(',', $this->ports_mappings), - ); - } - public function portsExposesArray(): Attribute + protected static function booted() { - return Attribute::make( - get: fn () => is_null($this->ports_exposes) - ? [] - : explode(',', $this->ports_exposes) - ); + static::deleted(function ($service) { + foreach($service->applications()->get() as $application) { + $application->persistentStorages()->delete(); + } + foreach($service->databases()->get() as $database) { + $database->persistentStorages()->delete(); + } + $service->environment_variables()->delete(); + $service->applications()->delete(); + $service->databases()->delete(); + }); } + public function type() + { + return 'service'; + } + public function applications() { return $this->hasMany(ServiceApplication::class); @@ -47,10 +45,10 @@ class Service extends BaseModel { return $this->belongsTo(Environment::class); } - public function server() { + public function server() + { return $this->belongsTo(Server::class); } - public function byName(string $name) { $app = $this->applications()->whereName($name)->first(); @@ -70,7 +68,6 @@ class Service extends BaseModel public function parse(bool $isNew = false): Collection { // ray()->clearAll(); - ray('Service parse'); if ($this->docker_compose_raw) { $yaml = Yaml::parse($this->docker_compose_raw); @@ -138,8 +135,8 @@ class Service extends BaseModel } } } - $savedService->ports_exposes = $ports->implode(','); - $savedService->save(); + // $savedService->ports_exposes = $ports->implode(','); + // $savedService->save(); } // Collect volumes $serviceVolumes = collect(data_get($service, 'volumes', [])); @@ -158,17 +155,38 @@ class Service extends BaseModel return $key == $volumeName; }); if (!$volumeExists) { - if (!Str::startsWith($volumeName, '/')) { + if (Str::startsWith($volumeName, '/')) { + $volumes->put($volumeName, $volumePath); + LocalPersistentVolume::updateOrCreate( + [ + 'mount_path' => $volumePath, + 'resource_id' => $savedService->id, + 'resource_type' => get_class($savedService) + ], + [ + 'name' => Str::slug($volumeName, '-'), + 'mount_path' => $volumePath, + 'host_path' => $volumeName, + 'resource_id' => $savedService->id, + 'resource_type' => get_class($savedService) + ] + ); + } else { $composeVolumes->put($volumeName, null); - } - $volumes->put($volumeName, $volumePath); - if ($isNew) { - LocalPersistentVolume::create([ - 'name' => $volumeName, - 'mount_path' => $volumePath, - 'resource_id' => $savedService->id, - 'resource_type' => get_class($savedService) - ]); + LocalPersistentVolume::updateOrCreate( + [ + 'mount_path' => $volumePath, + 'resource_id' => $savedService->id, + 'resource_type' => get_class($savedService) + ], + [ + 'name' => $volumeName, + 'mount_path' => $volumePath, + 'host_path' => null, + 'resource_id' => $savedService->id, + 'resource_type' => get_class($savedService) + ] + ); } } } @@ -229,25 +247,26 @@ class Service extends BaseModel } if (!$envs->has($nakedName->value())) { $envs->put($nakedName->value(), $nakedValue->value()); - if ($isNew) { - EnvironmentVariable::create([ - 'key' => $nakedName->value(), - 'value' => $nakedValue->value(), - 'is_build_time' => false, - 'service_id' => $this->id, - 'is_preview' => false, - ]); - } + EnvironmentVariable::updateOrCreate([ + 'key' => $nakedName->value(), + 'service_id' => $this->id, + ], [ + 'value' => $nakedValue->value(), + 'is_build_time' => false, + 'service_id' => $this->id, + 'is_preview' => false, + ]); } } else { if (!$envs->has($nakedName->value())) { $envs->put($nakedName->value(), null); - if ($isNew) { + $envExists = EnvironmentVariable::where('service_id', $this->id)->where('key', $nakedName->value())->exists(); + if (!$envExists) { EnvironmentVariable::create([ 'key' => $nakedName->value(), 'value' => null, - 'is_build_time' => false, 'service_id' => $this->id, + 'is_build_time' => false, 'is_preview' => false, ]); } @@ -259,31 +278,31 @@ class Service extends BaseModel $generatedValue = null; if ($variableName->startsWith('SERVICE_USER')) { $generatedValue = Str::random(10); - if ($isNew) { - if (!$envs->has($variableName->value())) { - $envs->put($variableName->value(), $generatedValue); - EnvironmentVariable::create([ - 'key' => $variableName->value(), - 'value' => $generatedValue, - 'is_build_time' => false, - 'service_id' => $this->id, - 'is_preview' => false, - ]); - } + if (!$envs->has($variableName->value())) { + $envs->put($variableName->value(), $generatedValue); + EnvironmentVariable::updateOrCreate([ + 'key' => $variableName->value(), + 'service_id' => $this->id, + ], [ + 'value' => $generatedValue, + 'is_build_time' => false, + 'service_id' => $this->id, + 'is_preview' => false, + ]); } } else if ($variableName->startsWith('SERVICE_PASSWORD')) { $generatedValue = Str::password(symbols: false); - if ($isNew) { - if (!$envs->has($variableName->value())) { - $envs->put($variableName->value(), $generatedValue); - EnvironmentVariable::create([ - 'key' => $variableName->value(), - 'value' => $generatedValue, - 'is_build_time' => false, - 'service_id' => $this->id, - 'is_preview' => false, - ]); - } + if (!$envs->has($variableName->value())) { + $envs->put($variableName->value(), $generatedValue); + EnvironmentVariable::updateOrCreate([ + 'key' => $variableName->value(), + 'service_id' => $this->id, + ], [ + 'value' => $generatedValue, + 'is_build_time' => false, + 'service_id' => $this->id, + 'is_preview' => false, + ]); } } else if ($variableName->startsWith('SERVICE_FQDN')) { if ($fqdn) { @@ -324,20 +343,6 @@ class Service extends BaseModel data_forget($service, 'documentation'); return $service; }); - // $services = $services->map(function ($service, $serviceName) { - // $dependsOn = collect(data_get($service, 'depends_on', [])); - // $dependsOn = $dependsOn->map(function ($value) { - // return "$value-{$this->uuid}"; - // }); - // data_set($service, 'depends_on', $dependsOn->toArray()); - // return $service; - // }); - // $renamedServices = collect([]); - // collect($services)->map(function ($service, $serviceName) use ($renamedServices) { - // $newServiceName = "$serviceName-$this->uuid"; - // $renamedServices->put($newServiceName, $service); - // }); - $finalServices = [ 'version' => $dockerComposeVersion, 'services' => $services->toArray(), diff --git a/app/Models/ServiceApplication.php b/app/Models/ServiceApplication.php index df506468c..79b96c0b2 100644 --- a/app/Models/ServiceApplication.php +++ b/app/Models/ServiceApplication.php @@ -2,6 +2,7 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; class ServiceApplication extends BaseModel @@ -9,4 +10,12 @@ class ServiceApplication extends BaseModel use HasFactory; protected $guarded = []; + public function type() + { + return 'service'; + } + public function persistentStorages() + { + return $this->morphMany(LocalPersistentVolume::class, 'resource'); + } } diff --git a/app/Models/ServiceDatabase.php b/app/Models/ServiceDatabase.php index 774753eeb..dc0d85742 100644 --- a/app/Models/ServiceDatabase.php +++ b/app/Models/ServiceDatabase.php @@ -9,4 +9,12 @@ class ServiceDatabase extends BaseModel use HasFactory; protected $guarded = []; + public function type() + { + return 'service'; + } + public function persistentStorages() + { + return $this->morphMany(LocalPersistentVolume::class, 'resource'); + } } diff --git a/app/View/Components/Services/Links.php b/app/View/Components/Services/Links.php new file mode 100644 index 000000000..e36d52d57 --- /dev/null +++ b/app/View/Components/Services/Links.php @@ -0,0 +1,29 @@ +links = collect([]); + $service->applications()->get()->map(function ($application) { + $this->links->push($application->fqdn); + }); + } + + /** + * Get the view / contents that represent the component. + */ + public function render(): View|Closure|string + { + return view('components.services.links'); + } +} diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 123e9fb9f..90609af23 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -132,7 +132,6 @@ function get_port_from_dockerfile($dockerfile): int function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'application') { - ray($type); $labels = collect([]); $labels->push('coolify.managed=true'); $labels->push('coolify.version=' . config('version')); diff --git a/database/migrations/2023_09_20_082733_create_service_databases_table.php b/database/migrations/2023_09_20_082733_create_service_databases_table.php index 0b7a06dd9..924c663ba 100644 --- a/database/migrations/2023_09_20_082733_create_service_databases_table.php +++ b/database/migrations/2023_09_20_082733_create_service_databases_table.php @@ -15,12 +15,10 @@ return new class extends Migration $table->id(); $table->string('uuid')->unique(); $table->string('name'); + $table->string('human_name')->nullable(); $table->string('status')->default('exited'); - $table->string('ports_exposes')->nullable(); - $table->string('ports_mappings')->nullable(); - $table->foreignId('service_id'); $table->timestamps(); }); diff --git a/database/migrations/2023_09_20_082737_create_service_applications_table.php b/database/migrations/2023_09_20_082737_create_service_applications_table.php index 1c1f0edb7..c4c363ed5 100644 --- a/database/migrations/2023_09_20_082737_create_service_applications_table.php +++ b/database/migrations/2023_09_20_082737_create_service_applications_table.php @@ -15,24 +15,10 @@ return new class extends Migration $table->id(); $table->string('uuid')->unique(); $table->string('name'); + $table->string('human_name')->nullable(); $table->string('fqdn')->unique()->nullable(); - $table->string('ports_exposes')->nullable(); - $table->string('ports_mappings')->nullable(); - - $table->string('health_check_path')->default('/'); - $table->string('health_check_port')->nullable(); - $table->string('health_check_host')->default('localhost'); - $table->string('health_check_method')->default('GET'); - $table->integer('health_check_return_code')->default(200); - $table->string('health_check_scheme')->default('http'); - $table->string('health_check_response_text')->nullable(); - $table->integer('health_check_interval')->default(5); - $table->integer('health_check_timeout')->default(5); - $table->integer('health_check_retries')->default(10); - $table->integer('health_check_start_period')->default(5); - $table->string('status')->default('exited'); $table->foreignId('service_id'); diff --git a/resources/views/components/applications/links.blade.php b/resources/views/components/applications/links.blade.php index 9e90c9b2b..0519d339e 100644 --- a/resources/views/components/applications/links.blade.php +++ b/resources/views/components/applications/links.blade.php @@ -1,18 +1,20 @@
-