diff --git a/app/Actions/Proxy/StartProxy.php b/app/Actions/Proxy/StartProxy.php index 944480ef2..edb84dd01 100644 --- a/app/Actions/Proxy/StartProxy.php +++ b/app/Actions/Proxy/StartProxy.php @@ -14,15 +14,10 @@ class StartProxy use AsAction; public function handle(Server $server, bool $async = true): Activity|string { - $proxyType = data_get($server,'proxy.type'); + $proxyType = $server->proxyType(); if ($proxyType === 'none') { return 'OK'; } - if (is_null($proxyType)) { - $server->proxy->type = ProxyTypes::TRAEFIK_V2->value; - $server->proxy->status = ProxyStatus::EXITED->value; - $server->save(); - } $proxy_path = get_proxy_path(); $networks = collect($server->standaloneDockers)->map(function ($docker) { return $docker['network']; diff --git a/app/Actions/Service/StartService.php b/app/Actions/Service/StartService.php new file mode 100644 index 000000000..1ce7b6470 --- /dev/null +++ b/app/Actions/Service/StartService.php @@ -0,0 +1,30 @@ +uuid}"; + $commands[] = "echo 'Starting service {$service->name} on {$service->server->name}'"; + $commands[] = "mkdir -p $workdir"; + $commands[] = "cd $workdir"; + + $docker_compose_base64 = base64_encode($service->docker_compose); + $commands[] = "echo $docker_compose_base64 | base64 -d > docker-compose.yml"; + $envs = $service->environment_variables()->get(); + foreach ($envs as $env) { + $commands[] = "echo '{$env->key}={$env->value}' >> .env"; + } + $commands[] = "docker compose pull"; + $commands[] = "docker compose up -d"; + $commands[] = "docker network connect $service->uuid coolify-proxy"; + $activity = remote_process($commands, $service->server); + return $activity; + } +} diff --git a/app/Http/Livewire/Project/Application/General.php b/app/Http/Livewire/Project/Application/General.php index f5ecf1fe1..affc4bcab 100644 --- a/app/Http/Livewire/Project/Application/General.php +++ b/app/Http/Livewire/Project/Application/General.php @@ -34,7 +34,6 @@ class General extends Component public bool $is_auto_deploy_enabled; public bool $is_force_https_enabled; - public array $service_configurations = []; protected $rules = [ 'application.name' => 'required', @@ -55,9 +54,6 @@ class General extends Component 'application.dockerfile' => 'nullable', 'application.dockercompose_raw' => 'nullable', 'application.dockercompose' => 'nullable', - 'application.service_configurations.*' => 'nullable', - 'service_configurations.*.fqdn' => 'nullable|url', - 'service_configurations.*.port' => 'integer', ]; protected $validationAttributes = [ 'application.name' => 'name', @@ -78,8 +74,6 @@ class General extends Component 'application.dockerfile' => 'Dockerfile', 'application.dockercompose_raw' => 'Docker Compose (raw)', 'application.dockercompose' => 'Docker Compose', - 'service_configurations.*.fqdn' => 'FQDN', - 'service_configurations.*.port' => 'Port', ]; @@ -115,7 +109,6 @@ class General extends Component public function mount() { - $this->services = $this->application->services(); $this->is_static = $this->application->settings->is_static; $this->is_git_submodules_enabled = $this->application->settings->is_git_submodules_enabled; $this->is_git_lfs_enabled = $this->application->settings->is_git_lfs_enabled; @@ -124,9 +117,6 @@ class General extends Component $this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled; $this->is_force_https_enabled = $this->application->settings->is_force_https_enabled; $this->checkWildCardDomain(); - if (data_get($this->application, 'service_configurations')) { - $this->service_configurations = $this->application->service_configurations; - } } public function generateGlobalRandomDomain() @@ -156,7 +146,6 @@ class General extends Component public function submit() { try { - $this->application->service_configurations = $this->service_configurations; $this->validate(); if (data_get($this->application, 'fqdn')) { $domains = Str::of($this->application->fqdn)->trim()->explode(',')->map(function ($domain) { diff --git a/app/Http/Livewire/Project/New/DockerCompose.php b/app/Http/Livewire/Project/New/DockerCompose.php index d74cdb368..00faa4863 100644 --- a/app/Http/Livewire/Project/New/DockerCompose.php +++ b/app/Http/Livewire/Project/New/DockerCompose.php @@ -40,8 +40,6 @@ class DockerCompose extends Component - database__connection__user=$SERVICE_USER_MYSQL - database__connection__password=$SERVICE_PASSWORD_MYSQL - database__connection__database=${MYSQL_DATABASE-ghost} - ports: - - "2368" depends_on: - mysql mysql: @@ -62,93 +60,25 @@ class DockerCompose extends Component $this->validate([ 'dockercompose' => 'required' ]); - $destination_uuid = $this->query['destination']; - $destination = StandaloneDocker::where('uuid', $destination_uuid)->first(); - if (!$destination) { - $destination = SwarmDocker::where('uuid', $destination_uuid)->first(); - } - if (!$destination) { - throw new \Exception('Destination not found. What?!'); - } - $destination_class = $destination->getMorphClass(); + $server_id = $this->query['server_id']; $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); $environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first(); - $service = new Service(); - $service->uuid = (string) new Cuid2(7); - $service->name = 'service-' . new Cuid2(7); - $service->docker_compose_raw = $this->dockercompose; - $service->environment_id = $environment->id; - $service->destination_id = $destination->id; - $service->destination_type = $destination_class; - $service->save(); - $service->parse(saveIt: true); + + $service = Service::create([ + 'name' => 'service' . Str::random(10), + 'docker_compose_raw' => $this->dockercompose, + 'environment_id' => $environment->id, + 'server_id' => (int) $server_id, + ]); + $service->name = "service-$service->uuid"; + + $service->parse(isNew: true); return redirect()->route('project.service', [ 'service_uuid' => $service->uuid, 'environment_name' => $environment->name, 'project_uuid' => $project->uuid, ]); - // $compose = data_get($parsedService, 'docker_compose'); - // $service->docker_compose = $compose; - // $shouldDefine = data_get($parsedService, 'should_define', collect([])); - // if ($shouldDefine->count() > 0) { - // $envs = data_get($shouldDefine, 'envs', []); - // foreach($envs as $env) { - // ray($env); - // $variableName = Str::of($env)->before('='); - // $variableValue = Str::of($env)->after('='); - // ray($variableName, $variableValue); - // } - // } - // foreach ($services as $serviceName => $serviceDetails) { - // if (data_get($serviceDetails,'is_database')) { - // $serviceDatabase = new ServiceDatabase(); - // $serviceDatabase->name = $serviceName . '-' . $service->uuid; - // $serviceDatabase->service_id = $service->id; - // $serviceDatabase->save(); - // } else { - // $serviceApplication = new ServiceApplication(); - // $serviceApplication->name = $serviceName . '-' . $service->uuid; - // $serviceApplication->fqdn = - // $serviceApplication->service_id = $service->id; - // $serviceApplication->save(); - // } - // } - - // ray($details); - // $envs = data_get($details, 'envs', []); - // if ($envs->count() > 0) { - // foreach ($envs as $env) { - // $key = Str::of($env)->before('='); - // $value = Str::of($env)->after('='); - // EnvironmentVariable::create([ - // 'key' => $key, - // 'value' => $value, - // 'is_build_time' => false, - // 'service_id' => $service->id, - // 'is_preview' => false, - // ]); - // } - // } - // $volumes = data_get($details, 'volumes', []); - // if ($volumes->count() > 0) { - // foreach ($volumes as $volume => $mount_path) { - // LocalPersistentVolume::create([ - // 'name' => $volume, - // 'mount_path' => $mount_path, - // 'resource_id' => $service->id, - // 'resource_type' => $service->getMorphClass(), - // 'is_readonly' => false - // ]); - // } - // } - // $dockercompose_coolified = data_get($details, 'dockercompose', ''); - // $service->update([ - // 'docker_compose' => $dockercompose_coolified, - // ]); - - - } } diff --git a/app/Http/Livewire/Project/New/Select.php b/app/Http/Livewire/Project/New/Select.php index 89a58d20e..53fb6c9fd 100644 --- a/app/Http/Livewire/Project/New/Select.php +++ b/app/Http/Livewire/Project/New/Select.php @@ -83,6 +83,7 @@ class Select extends Component 'environment_name' => $this->parameters['environment_name'], 'type' => $this->type, 'destination' => $this->destination_uuid, + 'server_id' => $this->server_id, ]); } diff --git a/app/Http/Livewire/Project/Service/Index.php b/app/Http/Livewire/Project/Service/Index.php index 17429d315..348f16f8b 100644 --- a/app/Http/Livewire/Project/Service/Index.php +++ b/app/Http/Livewire/Project/Service/Index.php @@ -2,7 +2,10 @@ namespace App\Http\Livewire\Project\Service; +use App\Actions\Service\StartService; +use App\Jobs\ContainerStatusJob; use App\Models\Service; +use Illuminate\Support\Collection; use Livewire\Component; class Index extends Component @@ -11,14 +14,66 @@ class Index extends Component public array $parameters; public array $query; + public Collection $services; - public function mount() { + protected $rules = [ + 'services.*.fqdn' => 'nullable', + ]; + + 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'); } + 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() + { + $this->service->parse(); + $activity = StartService::run($this->service); + $this->emit('newMonitorActivity', $activity->id); + } } diff --git a/app/Http/Livewire/Service/Index.php b/app/Http/Livewire/Service/Index.php deleted file mode 100644 index fbf651084..000000000 --- a/app/Http/Livewire/Service/Index.php +++ /dev/null @@ -1,25 +0,0 @@ -parameters = get_route_parameters(); - $this->query = request()->query(); - $this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail(); - ray($this->service->docker_compose); - } - public function render() - { - return view('livewire.project.service.index')->layout('layouts.app'); - } -} diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 816742d8f..7e59d99bd 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -73,7 +73,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $this->log_model = $this->application_deployment_queue; $this->application = Application::find($this->application_deployment_queue->application_id); - $isService = $this->application->services()->count() > 0; $this->application_deployment_queue_id = $application_deployment_queue_id; $this->deployment_uuid = $this->application_deployment_queue->deployment_uuid; $this->pull_request_id = $this->application_deployment_queue->pull_request_id; @@ -129,8 +128,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted try { if ($this->application->dockerfile) { $this->deploy_simple_dockerfile(); - } else if ($this->application->services()->count() > 0) { - $this->deploy_docker_compose(); } else { if ($this->pull_request_id !== 0) { $this->deploy_pull_request(); diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php index 234f8e911..6da47121b 100644 --- a/app/Jobs/ContainerStatusJob.php +++ b/app/Jobs/ContainerStatusJob.php @@ -74,6 +74,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted $containers = format_docker_command_output_to_json($containers); $applications = $this->server->applications(); $databases = $this->server->databases(); + $services = $this->server->services(); $previews = $this->server->previews(); /// Check if proxy is running @@ -92,6 +93,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted $foundApplications = []; $foundApplicationPreviews = []; $foundDatabases = []; + $foundServices = []; foreach ($containers as $container) { $containerStatus = data_get($container, 'State.Status'); $labels = data_get($container, 'Config.Labels'); @@ -138,7 +140,69 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted } } } + $serviceLabelId = data_get($labels, 'coolify.serviceId'); + if ($serviceLabelId) { + $coolifyName = data_get($labels, 'coolify.name'); + $serviceName = Str::of($coolifyName)->before('-'); + $serviceUuid = Str::of($coolifyName)->after('-'); + $service = $services->where('uuid', $serviceUuid)->first(); + if ($service) { + $foundService = $service->byName($serviceName); + if ($foundService) { + $foundServices[] = "$foundService->id-$serviceName"; + $statusFromDb = $foundService->status; + if ($statusFromDb !== $containerStatus) { + ray('Updating status: ' . $containerStatus); + $foundService->update(['status' => $containerStatus]); + } + } + } + } } + $exitedServices = collect([]); + foreach ($services->get() as $service) { + $apps = $service->applications()->get(); + $dbs = $service->databases()->get(); + foreach ($apps as $app) { + if (in_array("$service->id-$app->name", $foundServices)) { + continue; + } else { + $exitedServices->push($service); + $app->update(['status' => 'exited']); + } + } + foreach ($dbs as $db) { + if (in_array("$service->id-$db->name", $foundServices)) { + continue; + } else { + $exitedServices->push($service); + $db->update(['status' => 'exited']); + } + } + } + $exitedServices = $exitedServices->unique('id'); + ray($exitedServices); + // ray($exitedServices); + // foreach ($serviceIds as $serviceId) { + // $service = $services->where('id', $serviceId)->first(); + // if ($service->status === 'exited') { + // continue; + // } + + // $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)); + // } + + $notRunningApplications = $applications->pluck('id')->diff($foundApplications); foreach ($notRunningApplications as $applicationId) { $application = $applications->where('id', $applicationId)->first(); diff --git a/app/Models/Application.php b/app/Models/Application.php index 2286ace22..8f518efc9 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -12,9 +12,7 @@ use Illuminate\Support\Str; class Application extends BaseModel { protected $guarded = []; - protected $casts = [ - 'service_configurations' => 'array', - ]; + protected static function booted() { static::created(function ($application) { diff --git a/app/Models/Server.php b/app/Models/Server.php index 13df241d6..89f104199 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -2,6 +2,8 @@ namespace App\Models; +use App\Enums\ProxyStatus; +use App\Enums\ProxyTypes; use Illuminate\Database\Eloquent\Builder; use Spatie\SchemalessAttributes\Casts\SchemalessAttributes; use Spatie\SchemalessAttributes\SchemalessAttributesTrait; @@ -77,6 +79,12 @@ class Server extends BaseModel } public function proxyType() { + $type = $this->proxy->get('type'); + if (is_null($type)) { + $this->proxy->type = ProxyTypes::TRAEFIK_V2->value; + $this->proxy->status = ProxyStatus::EXITED->value; + $this->save(); + } return $this->proxy->get('type'); } public function scopeWithProxy(): Builder @@ -107,6 +115,9 @@ class Server extends BaseModel return $standaloneDocker->applications; })->flatten(); } + public function services() { + return $this->hasMany(Service::class); + } public function previews() { return $this->destinations()->map(function ($standaloneDocker) { diff --git a/app/Models/Service.php b/app/Models/Service.php index c4d492948..e713d5130 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -2,6 +2,7 @@ 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; @@ -13,10 +14,6 @@ class Service extends BaseModel { use HasFactory; protected $guarded = []; - public function destination() - { - return $this->morphTo(); - } public function persistentStorages() { return $this->morphMany(LocalPersistentVolume::class, 'resource'); @@ -46,26 +43,49 @@ class Service extends BaseModel { return $this->hasMany(ServiceDatabase::class); } + public function environment() + { + return $this->belongsTo(Environment::class); + } + public function server() { + return $this->belongsTo(Server::class); + } + + public function byName(string $name) + { + $app = $this->applications()->whereName($name)->first(); + if ($app) { + return $app; + } + $db = $this->databases()->whereName($name)->first(); + if ($db) { + return $db; + } + return null; + } public function environment_variables(): HasMany { return $this->hasMany(EnvironmentVariable::class)->orderBy('key', 'asc'); } - public function parse(bool $saveIt = false): Collection + public function parse(bool $isNew = false): Collection { + // ray()->clearAll(); + ray('Service parse'); if ($this->docker_compose_raw) { - ray()->clearAll(); $yaml = Yaml::parse($this->docker_compose_raw); $composeVolumes = collect(data_get($yaml, 'volumes', [])); $composeNetworks = collect(data_get($yaml, 'networks', [])); + $dockerComposeVersion = data_get($yaml, 'version') ?? '3.8'; $services = data_get($yaml, 'services'); - $definedNetwork = data_get($this, 'destination.network'); + $definedNetwork = $this->uuid; $volumes = collect([]); $envs = collect([]); $ports = collect([]); - $services = collect($services)->map(function ($service, $serviceName) use ($composeVolumes, $composeNetworks, $definedNetwork, $envs, $volumes, $ports, $saveIt) { + $services = collect($services)->map(function ($service, $serviceName) use ($composeVolumes, $composeNetworks, $definedNetwork, $envs, $volumes, $ports, $isNew) { + $container_name = "$serviceName-{$this->uuid}"; $isDatabase = false; // Decide if the service is a database $image = data_get($service, 'image'); @@ -76,24 +96,49 @@ class Service extends BaseModel data_set($service, 'is_database', true); } } - if ($saveIt) { + if ($isNew) { if ($isDatabase) { $savedService = ServiceDatabase::create([ 'name' => $serviceName, 'service_id' => $this->id ]); } else { + $defaultUsableFqdn = "http://$serviceName-{$this->uuid}.{$this->server->ip}.sslip.io"; + if (isDev()) { + $defaultUsableFqdn = "http://$serviceName-{$this->uuid}.127.0.0.1.sslip.io"; + } $savedService = ServiceApplication::create([ 'name' => $serviceName, + 'fqdn' => $defaultUsableFqdn, 'service_id' => $this->id ]); } + } else { + if ($isDatabase) { + $savedService = $this->databases()->whereName($serviceName)->first(); + } else { + $savedService = $this->applications()->whereName($serviceName)->first(); + } } + $fqdn = data_get($savedService, 'fqdn'); // Collect ports $servicePorts = collect(data_get($service, 'ports', [])); $ports->put($serviceName, $servicePorts); - if ($saveIt) { - $savedService->ports_exposes = $servicePorts->implode(','); + if ($isNew) { + $ports = collect([]); + if ($servicePorts->count() > 0) { + foreach ($servicePorts as $sport) { + if (is_string($sport)) { + $ports->push($sport); + } + if (is_array($sport)) { + $target = data_get($sport, 'target'); + $published = data_get($sport, 'published'); + $ports->push("$target:$published"); + } + } + } + $savedService->ports_exposes = $ports->implode(','); $savedService->save(); } // Collect volumes @@ -117,7 +162,7 @@ class Service extends BaseModel $composeVolumes->put($volumeName, null); } $volumes->put($volumeName, $volumePath); - if ($saveIt) { + if ($isNew) { LocalPersistentVolume::create([ 'name' => $volumeName, 'mount_path' => $volumePath, @@ -137,7 +182,7 @@ class Service extends BaseModel return $value == $networkName || $key == $networkName; }); if (!$networkExists) { - $composeNetworks->put($networkName, null); + $composeNetworks->put($networkDetails, null); } } } @@ -147,11 +192,16 @@ class Service extends BaseModel }); if (!$definedNetworkExists) { $composeNetworks->put($definedNetwork, [ - 'external' => true + 'name' => $definedNetwork, + 'external' => false ]); } + $networks = $serviceNetworks->toArray(); + $networks = array_merge($networks, [$definedNetwork]); + data_set($service, 'networks', $networks); - // Get variables from the service that does not start with SERVICE_* + + // Get variables from the service $serviceVariables = collect(data_get($service, 'environment', [])); foreach ($serviceVariables as $variable) { $value = Str::after($variable, '='); @@ -179,7 +229,7 @@ class Service extends BaseModel } if (!$envs->has($nakedName->value())) { $envs->put($nakedName->value(), $nakedValue->value()); - if ($saveIt) { + if ($isNew) { EnvironmentVariable::create([ 'key' => $nakedName->value(), 'value' => $nakedValue->value(), @@ -192,7 +242,7 @@ class Service extends BaseModel } else { if (!$envs->has($nakedName->value())) { $envs->put($nakedName->value(), null); - if ($saveIt) { + if ($isNew) { EnvironmentVariable::create([ 'key' => $nakedName->value(), 'value' => null, @@ -205,15 +255,15 @@ class Service extends BaseModel } } } else { - $value = Str::of(replaceVariables(Str::of($value))); + $variableName = Str::of(replaceVariables(Str::of($value))); $generatedValue = null; - if ($value->startsWith('SERVICE_USER')) { + if ($variableName->startsWith('SERVICE_USER')) { $generatedValue = Str::random(10); - if ($saveIt) { - if (!$envs->has($value->value())) { - $envs->put($value->value(), $generatedValue); + if ($isNew) { + if (!$envs->has($variableName->value())) { + $envs->put($variableName->value(), $generatedValue); EnvironmentVariable::create([ - 'key' => $value->value(), + 'key' => $variableName->value(), 'value' => $generatedValue, 'is_build_time' => false, 'service_id' => $this->id, @@ -221,13 +271,13 @@ class Service extends BaseModel ]); } } - } else if ($value->startsWith('SERVICE_PASSWORD')) { + } else if ($variableName->startsWith('SERVICE_PASSWORD')) { $generatedValue = Str::password(symbols: false); - if ($saveIt) { - if (!$envs->has($value->value())) { - $envs->put($value->value(), $generatedValue); + if ($isNew) { + if (!$envs->has($variableName->value())) { + $envs->put($variableName->value(), $generatedValue); EnvironmentVariable::create([ - 'key' => $value->value(), + 'key' => $variableName->value(), 'value' => $generatedValue, 'is_build_time' => false, 'service_id' => $this->id, @@ -235,28 +285,74 @@ class Service extends BaseModel ]); } } + } else if ($variableName->startsWith('SERVICE_FQDN')) { + if ($fqdn) { + $environments = collect(data_get($service, 'environment')); + $environments = $environments->map(function ($envValue) use ($value, $fqdn) { + $envValue = Str::of($envValue)->replace($value, $fqdn); + return $envValue->value(); + }); + $service['environment'] = $environments->toArray(); + } + } else if ($variableName->startsWith('SERVICE_URL')) { + if ($fqdn) { + $url = Str::of($fqdn)->after('https://')->before('/'); + $environments = collect(data_get($service, 'environment')); + $environments = $environments->map(function ($envValue) use ($value, $url) { + $envValue = Str::of($envValue)->replace($value, $url); + return $envValue->value(); + }); + $service['environment'] = $environments->toArray(); + } } } } - + if ($this->server->proxyType() === ProxyTypes::TRAEFIK_V2->value) { + $labels = collect(data_get($service, 'labels', [])); + $labels = collect([]); + $labels = $labels->merge(defaultLabels($this->id, $container_name, type: 'service')); + if (!$isDatabase) { + if ($fqdn) { + $labels = $labels->merge(fqdnLabelsForTraefik($fqdn, $container_name, true)); + } + } + data_set($service, 'labels', $labels->toArray()); + } data_forget($service, 'is_database'); + data_set($service, 'restart', RESTART_MODE); + data_set($service, 'container_name', $container_name); data_forget($service, 'documentation'); return $service; }); - data_set($services, 'volumes', $composeVolumes->toArray()); - data_set($services, 'networks', $composeNetworks->toArray()); - $this->docker_compose = Yaml::parse($services); - // $compose = Str::of(Yaml::dump($services, 10, 2)); - // TODO: Replace SERVICE_FQDN_* with the actual FQDN - // TODO: Replace SERVICE_URL_* + // $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(), + 'volumes' => $composeVolumes->toArray(), + 'networks' => $composeNetworks->toArray(), + ]; + $this->docker_compose = Yaml::dump($finalServices, 10, 2); + $this->save(); $shouldBeDefined = collect([ 'envs' => $envs, 'volumes' => $volumes, 'ports' => $ports ]); $parsedCompose = collect([ - 'dockerCompose' => $services, + 'dockerCompose' => $finalServices, 'shouldBeDefined' => $shouldBeDefined ]); return $parsedCompose; diff --git a/app/Models/StandaloneDocker.php b/app/Models/StandaloneDocker.php index ad401f12d..16d85d956 100644 --- a/app/Models/StandaloneDocker.php +++ b/app/Models/StandaloneDocker.php @@ -21,9 +21,9 @@ class StandaloneDocker extends BaseModel return $this->belongsTo(Server::class); } - public function service() + public function services() { - return $this->belongsTo(Service::class, 'destination'); + return $this->morphMany(Service::class, 'destination'); } public function attachedTo() diff --git a/app/Traits/ExecuteRemoteCommand.php b/app/Traits/ExecuteRemoteCommand.php index 67761a69d..1244fde28 100644 --- a/app/Traits/ExecuteRemoteCommand.php +++ b/app/Traits/ExecuteRemoteCommand.php @@ -36,7 +36,7 @@ trait ExecuteRemoteCommand $this->save = data_get($single_command, 'save'); $remote_command = generateSshCommand($this->server, $command); - $process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden) { + $process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden) { $output = Str::of($output)->trim(); $new_log_entry = [ 'command' => $command, diff --git a/app/View/Components/Status/Index.php b/app/View/Components/Status/Index.php new file mode 100644 index 000000000..a7128c7fd --- /dev/null +++ b/app/View/Components/Status/Index.php @@ -0,0 +1,28 @@ +applications; + $databases = $service->databases; + foreach ($applications as $application) { + if ($application->status === 'running') { + $foundRunning = true; + } else { + $isDegraded = true; + } + } + foreach ($databases as $database) { + if ($database->status === 'running') { + $foundRunning = true; + } else { + $isDegraded = true; + } + } + if ($foundRunning && !$isDegraded) { + $this->complexStatus = 'running'; + } else if ($foundRunning && $isDegraded) { + $this->complexStatus = 'degraded'; + } else if (!$foundRunning && $isDegraded) { + $this->complexStatus = 'exited'; + } + } + + /** + * Get the view / contents that represent the component. + */ + public function render(): View|Closure|string + { + return view('components.status.services'); + } +} diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 7925bbacc..123e9fb9f 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -130,13 +130,14 @@ function get_port_from_dockerfile($dockerfile): int return 80; } -function defaultLabels($id, $name, $pull_request_id = 0) +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')); - $labels->push('coolify.applicationId=' . $id); - $labels->push('coolify.type=application'); + $labels->push("coolify." . $type . "Id=" . $id); + $labels->push("coolify.type=$type"); $labels->push('coolify.name=' . $name); if ($pull_request_id !== 0) { $labels->push('coolify.pullRequestId=' . $pull_request_id); diff --git a/bootstrap/helpers/remoteProcess.php b/bootstrap/helpers/remoteProcess.php index 7bf194711..7936ca62b 100644 --- a/bootstrap/helpers/remoteProcess.php +++ b/bootstrap/helpers/remoteProcess.php @@ -33,7 +33,6 @@ function remote_process( throw new \Exception("User is not part of the team that owns this server"); } } - return resolve(PrepareCoolifyTask::class, [ 'remoteProcessArgs' => new CoolifyTaskArgs( server_uuid: $server->uuid, diff --git a/bootstrap/helpers/services.php b/bootstrap/helpers/services.php index eebeae72c..4448d08ac 100644 --- a/bootstrap/helpers/services.php +++ b/bootstrap/helpers/services.php @@ -13,188 +13,188 @@ use Illuminate\Support\Str; # SERVICE_PASSWORD_*: Generated by your application, password (encrypted) -function generateServiceFromTemplate(Service $service) -{ - // ray()->clearAll(); - $template = data_get($service, 'docker_compose_raw'); - $network = data_get($service, 'destination.network'); - $yaml = Yaml::parse($template); +// function generateServiceFromTemplate(Service $service) +// { +// // ray()->clearAll(); +// $template = data_get($service, 'docker_compose_raw'); +// $network = data_get($service, 'destination.network'); +// $yaml = Yaml::parse($template); - $services = $service->parse(); - $volumes = collect(data_get($yaml, 'volumes', [])); - $composeVolumes = collect([]); - $env = collect([]); - $ports = collect([]); +// $services = $service->parse(); +// $volumes = collect(data_get($yaml, 'volumes', [])); +// $composeVolumes = collect([]); +// $env = collect([]); +// $ports = collect([]); - foreach ($services as $serviceName => $service) { - $container_name = generateApplicationContainerName($application); - $domain = data_get($application, "service_configurations.{$serviceName}.fqdn", null); - if ($domain === '') { - $domain = null; - } - data_forget($service, 'documentation'); - // Some default things - data_set($service, 'restart', RESTART_MODE); - data_set($service, 'container_name', $container_name); - $healthcheck = data_get($service, 'healthcheck'); - if (is_null($healthcheck)) { - $healthcheck = [ - 'test' => [ - 'CMD-SHELL', - 'exit 0' - ], - 'interval' => $application->health_check_interval . 's', - 'timeout' => $application->health_check_timeout . 's', - 'retries' => $application->health_check_retries, - 'start_period' => $application->health_check_start_period . 's' - ]; - data_set($service, 'healthcheck', $healthcheck); - } - // Labels - $server = data_get($application, 'destination.server'); - if ($server->proxyType() === ProxyTypes::TRAEFIK_V2->value) { - $labels = collect(data_get($service, 'labels', [])); - $labels = collect([]); - $labels = $labels->merge(defaultLabels($application->id, $container_name)); - if (!data_get($service, 'is_database')) { - if ($domain) { - $labels = $labels->merge(fqdnLabelsForTraefik($domain, $container_name, $application->settings->is_force_https_enabled)); - } +// foreach ($services as $serviceName => $service) { +// $container_name = generateApplicationContainerName($application); +// $domain = data_get($application, "service_configurations.{$serviceName}.fqdn", null); +// if ($domain === '') { +// $domain = null; +// } +// data_forget($service, 'documentation'); +// // Some default things +// data_set($service, 'restart', RESTART_MODE); +// data_set($service, 'container_name', $container_name); +// $healthcheck = data_get($service, 'healthcheck'); +// if (is_null($healthcheck)) { +// $healthcheck = [ +// 'test' => [ +// 'CMD-SHELL', +// 'exit 0' +// ], +// 'interval' => $application->health_check_interval . 's', +// 'timeout' => $application->health_check_timeout . 's', +// 'retries' => $application->health_check_retries, +// 'start_period' => $application->health_check_start_period . 's' +// ]; +// data_set($service, 'healthcheck', $healthcheck); +// } +// // Labels +// $server = data_get($application, 'destination.server'); +// if ($server->proxyType() === ProxyTypes::TRAEFIK_V2->value) { +// $labels = collect(data_get($service, 'labels', [])); +// $labels = collect([]); +// $labels = $labels->merge(defaultLabels($application->id, $container_name)); +// if (!data_get($service, 'is_database')) { +// if ($domain) { +// $labels = $labels->merge(fqdnLabelsForTraefik($domain, $container_name, $application->settings->is_force_https_enabled)); +// } - } - data_set($service, 'labels', $labels->toArray()); - } +// } +// data_set($service, 'labels', $labels->toArray()); +// } - data_forget($service, 'is_database'); +// data_forget($service, 'is_database'); - // Add volumes to the volumes collection if they don't already exist - $serviceVolumes = collect(data_get($service, 'volumes', [])); - if ($serviceVolumes->count() > 0) { - foreach ($serviceVolumes as $volume) { - $volumeName = Str::before($volume, ':'); - $volumePath = Str::after($volume, ':'); - if (Str::startsWith($volumeName, '/')) { - continue; - } - $volumeExists = $volumes->contains(function ($_, $key) use ($volumeName) { - return $key == $volumeName; - }); - if ($volumeExists) { - ray('Volume already exists'); - } else { - $composeVolumes->put($volumeName, null); - $volumes->put($volumeName, $volumePath); - } - } - } - // Add networks to the networks collection if they don't already exist - $serviceNetworks = collect(data_get($service, 'networks', [])); - $networkExists = $serviceNetworks->contains(function ($_, $key) use ($network) { - return $key == $network; - }); - if (is_null($networkExists) || !$networkExists) { - $serviceNetworks->push($network); - } - data_set($service, 'networks', $serviceNetworks->toArray()); - data_set($yaml, "services.{$serviceName}", $service); +// // Add volumes to the volumes collection if they don't already exist +// $serviceVolumes = collect(data_get($service, 'volumes', [])); +// if ($serviceVolumes->count() > 0) { +// foreach ($serviceVolumes as $volume) { +// $volumeName = Str::before($volume, ':'); +// $volumePath = Str::after($volume, ':'); +// if (Str::startsWith($volumeName, '/')) { +// continue; +// } +// $volumeExists = $volumes->contains(function ($_, $key) use ($volumeName) { +// return $key == $volumeName; +// }); +// if ($volumeExists) { +// ray('Volume already exists'); +// } else { +// $composeVolumes->put($volumeName, null); +// $volumes->put($volumeName, $volumePath); +// } +// } +// } +// // Add networks to the networks collection if they don't already exist +// $serviceNetworks = collect(data_get($service, 'networks', [])); +// $networkExists = $serviceNetworks->contains(function ($_, $key) use ($network) { +// return $key == $network; +// }); +// if (is_null($networkExists) || !$networkExists) { +// $serviceNetworks->push($network); +// } +// data_set($service, 'networks', $serviceNetworks->toArray()); +// data_set($yaml, "services.{$serviceName}", $service); - // Get variables from the service that does not start with SERVICE_* - $serviceVariables = collect(data_get($service, 'environment', [])); - foreach ($serviceVariables as $variable) { - // $key = Str::before($variable, '='); - $value = Str::after($variable, '='); - if (!Str::startsWith($value, '$SERVICE_') && !Str::startsWith($value, '${SERVICE_') && Str::startsWith($value, '$')) { - if (Str::of($value)->contains(':')) { - $nakedName = replaceVariables(Str::of($value)->before(':')); - $nakedValue = replaceVariables(Str::of($value)->after(':')); - } - if (Str::of($value)->contains('-')) { - $nakedName = replaceVariables(Str::of($value)->before('-')); - $nakedValue = replaceVariables(Str::of($value)->after('-')); - } - if (Str::of($value)->contains('+')) { - $nakedName = replaceVariables(Str::of($value)->before('+')); - $nakedValue = replaceVariables(Str::of($value)->after('+')); - } - if ($nakedValue->startsWith('-')) { - $nakedValue = Str::of($nakedValue)->after('-'); - } - if ($nakedValue->startsWith('+')) { - $nakedValue = Str::of($nakedValue)->after('+'); - } - if (!$env->contains("{$nakedName->value()}={$nakedValue->value()}")) { - $env->push("$nakedName=$nakedValue"); - } - } - } - // Get ports from the service - $servicePorts = collect(data_get($service, 'ports', [])); - foreach ($servicePorts as $port) { - $port = Str::of($port)->before(':'); - $ports->push($port); - } - } - data_set($yaml, 'networks', [ - $network => [ - 'name' => $network - ], - ]); - data_set($yaml, 'volumes', $composeVolumes->toArray()); - $compose = Str::of(Yaml::dump($yaml, 10, 2)); +// // Get variables from the service that does not start with SERVICE_* +// $serviceVariables = collect(data_get($service, 'environment', [])); +// foreach ($serviceVariables as $variable) { +// // $key = Str::before($variable, '='); +// $value = Str::after($variable, '='); +// if (!Str::startsWith($value, '$SERVICE_') && !Str::startsWith($value, '${SERVICE_') && Str::startsWith($value, '$')) { +// if (Str::of($value)->contains(':')) { +// $nakedName = replaceVariables(Str::of($value)->before(':')); +// $nakedValue = replaceVariables(Str::of($value)->after(':')); +// } +// if (Str::of($value)->contains('-')) { +// $nakedName = replaceVariables(Str::of($value)->before('-')); +// $nakedValue = replaceVariables(Str::of($value)->after('-')); +// } +// if (Str::of($value)->contains('+')) { +// $nakedName = replaceVariables(Str::of($value)->before('+')); +// $nakedValue = replaceVariables(Str::of($value)->after('+')); +// } +// if ($nakedValue->startsWith('-')) { +// $nakedValue = Str::of($nakedValue)->after('-'); +// } +// if ($nakedValue->startsWith('+')) { +// $nakedValue = Str::of($nakedValue)->after('+'); +// } +// if (!$env->contains("{$nakedName->value()}={$nakedValue->value()}")) { +// $env->push("$nakedName=$nakedValue"); +// } +// } +// } +// // Get ports from the service +// $servicePorts = collect(data_get($service, 'ports', [])); +// foreach ($servicePorts as $port) { +// $port = Str::of($port)->before(':'); +// $ports->push($port); +// } +// } +// data_set($yaml, 'networks', [ +// $network => [ +// 'name' => $network +// ], +// ]); +// data_set($yaml, 'volumes', $composeVolumes->toArray()); +// $compose = Str::of(Yaml::dump($yaml, 10, 2)); - // Replace SERVICE_FQDN_* with the actual FQDN - preg_match_all(collectRegex('SERVICE_FQDN_'), $compose, $fqdns); - $fqdns = collect($fqdns)->flatten()->unique()->values(); - $generatedFqdns = collect([]); - foreach ($fqdns as $fqdn) { - $generatedFqdns->put("$fqdn", data_get($application, 'fqdn')); - } +// // Replace SERVICE_FQDN_* with the actual FQDN +// preg_match_all(collectRegex('SERVICE_FQDN_'), $compose, $fqdns); +// $fqdns = collect($fqdns)->flatten()->unique()->values(); +// $generatedFqdns = collect([]); +// foreach ($fqdns as $fqdn) { +// $generatedFqdns->put("$fqdn", data_get($application, 'fqdn')); +// } - // Replace SERVICE_URL_* - preg_match_all(collectRegex('SERVICE_URL_'), $compose, $urls); - $urls = collect($urls)->flatten()->unique()->values(); - $generatedUrls = collect([]); - foreach ($urls as $url) { - $generatedUrls->put("$url", data_get($application, 'url')); - } +// // Replace SERVICE_URL_* +// preg_match_all(collectRegex('SERVICE_URL_'), $compose, $urls); +// $urls = collect($urls)->flatten()->unique()->values(); +// $generatedUrls = collect([]); +// foreach ($urls as $url) { +// $generatedUrls->put("$url", data_get($application, 'url')); +// } - // Generate SERVICE_USER_* - preg_match_all(collectRegex('SERVICE_USER_'), $compose, $users); - $users = collect($users)->flatten()->unique()->values(); - $generatedUsers = collect([]); - foreach ($users as $user) { - $generatedUsers->put("$user", Str::random(10)); - } +// // Generate SERVICE_USER_* +// preg_match_all(collectRegex('SERVICE_USER_'), $compose, $users); +// $users = collect($users)->flatten()->unique()->values(); +// $generatedUsers = collect([]); +// foreach ($users as $user) { +// $generatedUsers->put("$user", Str::random(10)); +// } - // Generate SERVICE_PASSWORD_* - preg_match_all(collectRegex('SERVICE_PASSWORD_'), $compose, $passwords); - $passwords = collect($passwords)->flatten()->unique()->values(); - $generatedPasswords = collect([]); - foreach ($passwords as $password) { - $generatedPasswords->put("$password", Str::password(symbols: false)); - } +// // Generate SERVICE_PASSWORD_* +// preg_match_all(collectRegex('SERVICE_PASSWORD_'), $compose, $passwords); +// $passwords = collect($passwords)->flatten()->unique()->values(); +// $generatedPasswords = collect([]); +// foreach ($passwords as $password) { +// $generatedPasswords->put("$password", Str::password(symbols: false)); +// } - // Save .env file - foreach ($generatedFqdns as $key => $value) { - $env->push("$key=$value"); - } - foreach ($generatedUrls as $key => $value) { - $env->push("$key=$value"); - } - foreach ($generatedUsers as $key => $value) { - $env->push("$key=$value"); - } - foreach ($generatedPasswords as $key => $value) { - $env->push("$key=$value"); - } - return [ - 'dockercompose' => $compose, - 'yaml' => Yaml::parse($compose), - 'envs' => $env, - 'volumes' => $volumes, - 'ports' => $ports->values(), - ]; -} +// // Save .env file +// foreach ($generatedFqdns as $key => $value) { +// $env->push("$key=$value"); +// } +// foreach ($generatedUrls as $key => $value) { +// $env->push("$key=$value"); +// } +// foreach ($generatedUsers as $key => $value) { +// $env->push("$key=$value"); +// } +// foreach ($generatedPasswords as $key => $value) { +// $env->push("$key=$value"); +// } +// return [ +// 'dockercompose' => $compose, +// 'yaml' => Yaml::parse($compose), +// 'envs' => $env, +// 'volumes' => $volumes, +// 'ports' => $ports->values(), +// ]; +// } function replaceRegex(?string $name = null) { diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index ce41d19db..11ded0542 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -21,23 +21,29 @@ use Poliander\Cron\CronExpression; use Visus\Cuid2\Cuid2; use phpseclib3\Crypt\RSA; +function base_configuration_dir(): string +{ + return '/data/coolify'; +} function application_configuration_dir(): string { - return '/data/coolify/applications'; + return base_configuration_dir() . "/applications"; +} +function service_configuration_dir(): string +{ + return base_configuration_dir() . "/services"; } - function database_configuration_dir(): string { - return '/data/coolify/databases'; + return base_configuration_dir() . "/databases"; } function database_proxy_dir($uuid): string { - return "/data/coolify/databases/$uuid/proxy"; + return base_configuration_dir() . "/databases/$uuid/proxy"; } - function backup_dir(): string { - return '/data/coolify/backups'; + return base_configuration_dir() . "/backups"; } function generate_readme_file(string $name, string $updated_at): string diff --git a/database/migrations/2023_03_27_083621_create_services_table.php b/database/migrations/2023_03_27_083621_create_services_table.php index 12bb77b1b..a363d2119 100644 --- a/database/migrations/2023_03_27_083621_create_services_table.php +++ b/database/migrations/2023_03_27_083621_create_services_table.php @@ -16,8 +16,7 @@ return new class extends Migration $table->string('uuid')->unique(); $table->string('name'); - $table->morphs('destination'); - + $table->foreignId('server_id')->nullable(); $table->foreignId('environment_id'); $table->timestamps(); }); 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 841888349..0b7a06dd9 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 @@ -16,6 +16,8 @@ return new class extends Migration $table->string('uuid')->unique(); $table->string('name'); + $table->string('status')->default('exited'); + $table->string('ports_exposes')->nullable(); $table->string('ports_mappings')->nullable(); diff --git a/resources/views/components/resources/breadcrumbs.blade.php b/resources/views/components/resources/breadcrumbs.blade.php index cb1878581..ae46964a6 100644 --- a/resources/views/components/resources/breadcrumbs.blade.php +++ b/resources/views/components/resources/breadcrumbs.blade.php @@ -38,12 +38,10 @@ - @if ($resource->status === 'running') - - @elseif($resource->status === 'restarting') - + @if ($resource->getMorphClass() == 'App\Models\Service') + @else - + @endif diff --git a/resources/views/components/status/degraded.blade.php b/resources/views/components/status/degraded.blade.php new file mode 100644 index 000000000..2220a6d0e --- /dev/null +++ b/resources/views/components/status/degraded.blade.php @@ -0,0 +1,8 @@ +@props([ + 'text' => 'Degraded', +]) + +
+
+
{{ $text }}
+
diff --git a/resources/views/components/status/index.blade.php b/resources/views/components/status/index.blade.php new file mode 100644 index 000000000..7848cee02 --- /dev/null +++ b/resources/views/components/status/index.blade.php @@ -0,0 +1,7 @@ +@if ($status === 'running') + +@elseif($status === 'restarting') + +@else + +@endif diff --git a/resources/views/components/status/services.blade.php b/resources/views/components/status/services.blade.php new file mode 100644 index 000000000..bafa2954e --- /dev/null +++ b/resources/views/components/status/services.blade.php @@ -0,0 +1,9 @@ +@if ($complexStatus === 'running') + +@elseif($complexStatus === 'restarting') + +@elseif($complexStatus === 'degraded') + +@else + +@endif diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index 5f0069583..e8f94ab64 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -12,40 +12,6 @@ - @if ($services->count() === 0) -
- - @if ($wildcard_domain) - @if ($global_wildcard_domain) - Set Global Wildcard - - @endif - @if ($server_wildcard_domain) - Set Server Wildcard - - @endif - @endif -
-
- @if ($application->build_pack === 'dockerfile') - - - - @elseif ($application->build_pack === 'dockercompose') - - - - @else - - - - - - @endif - -
- @endif @if ($application->settings->is_static) @@ -74,38 +40,17 @@ @if ($application->dockerfile) @endif - @if ($services->count() > 0) -

Services

- @foreach ($services as $serviceName => $service) - @if (!data_get($service, 'is_database')) -

{{ Str::headline($serviceName) }}

-
- - - {{-- - --}} -
- @endif - @endforeach - - - - - @else -

Network

-
- @if ($application->settings->is_static) - - @else - - @endif - -
- @endif +

Network

+
+ @if ($application->settings->is_static) + + @else + + @endif + +

Advanced

diff --git a/resources/views/livewire/project/service/index.blade.php b/resources/views/livewire/project/service/index.blade.php index 51007cd96..26d73bb3d 100644 --- a/resources/views/livewire/project/service/index.blade.php +++ b/resources/views/livewire/project/service/index.blade.php @@ -1,16 +1,26 @@ -
+

Configuration

+

Applications

@foreach ($service->applications as $application) +

{{ $application->name }}

+

{{ $application->status }}

+ + Save +
@endforeach

Databases

@foreach ($service->databases as $database)

{{ $database->name }}

+

{{ $database->status }}

@endforeach

Variables

@foreach ($service->environment_variables as $variable)

{{ $variable->key }}={{ $variable->value }}

@endforeach - + Deploy +
+ +
diff --git a/routes/web.php b/routes/web.php index 665f5d3ee..82c760a08 100644 --- a/routes/web.php +++ b/routes/web.php @@ -7,7 +7,7 @@ use App\Http\Controllers\MagicController; use App\Http\Controllers\ProjectController; use App\Http\Controllers\ServerController; use App\Http\Livewire\Boarding\Index as BoardingIndex; -use App\Http\Livewire\Service\Index as ServiceIndex; +use App\Http\Livewire\Project\Service\Index as ServiceIndex; use App\Http\Livewire\Dashboard; use App\Http\Livewire\Server\All; use App\Http\Livewire\Server\Show; @@ -22,7 +22,6 @@ use App\Models\SwarmDocker; use Illuminate\Http\Request; use Illuminate\Support\Facades\Password; use Illuminate\Support\Facades\Route; -use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Laravel\Fortify\Contracts\FailedPasswordResetLinkRequestResponse; use Laravel\Fortify\Contracts\SuccessfulPasswordResetLinkRequestResponse;