diff --git a/app/Console/Commands/ResourcesDelete.php b/app/Console/Commands/ResourcesDelete.php index d230aa927..c9ad0f0e9 100644 --- a/app/Console/Commands/ResourcesDelete.php +++ b/app/Console/Commands/ResourcesDelete.php @@ -8,6 +8,7 @@ use App\Models\StandalonePostgresql; use Illuminate\Console\Command; use function Laravel\Prompts\confirm; +use function Laravel\Prompts\multiselect; use function Laravel\Prompts\select; class ResourcesDelete extends Command @@ -50,16 +51,18 @@ class ResourcesDelete extends Command $this->error('There are no applications to delete.'); return; } - $application = select( + $applicationsToDelete = multiselect( 'What application do you want to delete?', $applications->pluck('name')->toArray(), ); - $application = $applications->where('name', $application)->first(); - $confirmed = confirm("Are you sure you want to delete {$application->name}?"); + $confirmed = confirm("Are you sure you want to delete all selected resources?"); if (!$confirmed) { return; } - $application->delete(); + foreach ($applicationsToDelete as $application) { + $toDelete = $applications->where('name', $application)->first(); + $toDelete->delete(); + } } private function deleteDatabase() { @@ -68,16 +71,19 @@ class ResourcesDelete extends Command $this->error('There are no databases to delete.'); return; } - $database = select( + $databasesToDelete = multiselect( 'What database do you want to delete?', $databases->pluck('name')->toArray(), ); - $database = $databases->where('name', $database)->first(); - $confirmed = confirm("Are you sure you want to delete {$database->name}?"); + $confirmed = confirm("Are you sure you want to delete all selected resources?"); if (!$confirmed) { return; } - $database->delete(); + foreach ($databasesToDelete as $database) { + $toDelete = $databases->where('name', $database)->first(); + $toDelete->delete(); + } + } private function deleteService() { @@ -86,15 +92,17 @@ class ResourcesDelete extends Command $this->error('There are no services to delete.'); return; } - $service = select( + $servicesToDelete = multiselect( 'What service do you want to delete?', $services->pluck('name')->toArray(), ); - $service = $services->where('name', $service)->first(); - $confirmed = confirm("Are you sure you want to delete {$service->name}?"); + $confirmed = confirm("Are you sure you want to delete all selected resources?"); if (!$confirmed) { return; } - $service->delete(); + foreach ($servicesToDelete as $service) { + $toDelete = $services->where('name', $service)->first(); + $toDelete->delete(); + } } } diff --git a/app/Http/Livewire/Boarding/Index.php b/app/Http/Livewire/Boarding/Index.php index d124e6a89..a0c5038e0 100644 --- a/app/Http/Livewire/Boarding/Index.php +++ b/app/Http/Livewire/Boarding/Index.php @@ -76,7 +76,6 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== Team::find(currentTeam()->id)->update([ 'show_boarding' => false ]); - ray(currentTeam()); refreshSession(); return redirect()->route('dashboard'); } diff --git a/app/Http/Livewire/Dev/Compose.php b/app/Http/Livewire/Dev/Compose.php new file mode 100644 index 000000000..d3a0ee4e5 --- /dev/null +++ b/app/Http/Livewire/Dev/Compose.php @@ -0,0 +1,28 @@ +services = getServiceTemplates(); + } + public function setService(string $selected) { + $this->base64 = data_get($this->services, $selected . '.compose'); + if ($this->base64) { + $this->compose = base64_decode($this->base64); + } + } + public function updatedCompose($value) { + $this->base64 = base64_encode($value); + } + public function render() + { + return view('livewire.dev.compose'); + } +} diff --git a/app/Models/Project.php b/app/Models/Project.php index 9bcd2a0fe..13f86bf66 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -22,7 +22,7 @@ class Project extends BaseModel 'project_id' => $project->id, ]); }); - static::deleted(function ($project) { + static::deleting(function ($project) { $project->environments()->delete(); $project->settings()->delete(); }); diff --git a/app/Models/Service.php b/app/Models/Service.php index 8440dee6a..30e292945 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -16,7 +16,7 @@ class Service extends BaseModel protected static function booted() { - static::deleted(function ($service) { + static::deleting(function ($service) { $storagesToDelete = collect([]); foreach ($service->applications()->get() as $application) { instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server, false); @@ -266,7 +266,7 @@ class Service extends BaseModel // Collect/create/update volumes if ($serviceVolumes->count() > 0) { - $serviceVolumes = $serviceVolumes->map(function ($volume) use ($savedService, $topLevelVolumes, $isNew) { + $serviceVolumes = $serviceVolumes->map(function ($volume) use ($savedService, $topLevelVolumes) { $type = null; $source = null; $target = null; @@ -384,9 +384,20 @@ class Service extends BaseModel $value = Str::of($variable); } if ($key->startsWith('SERVICE_FQDN')) { - if (is_null(data_get($savedService, 'fqdn'))) { - $fqdn = generateFqdn($this->server, $containerName); - if (substr_count($key->value(), '_') === 2) { + if ($isNew) { + $name = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower(); + $fqdn = generateFqdn($this->server, "{$name->value()}-{$this->uuid}"); + if (substr_count($key->value(), '_') === 3) { + // SERVICE_FQDN_UMAMI_1000 + $port = $key->afterLast('_'); + } else { + // SERVICE_FQDN_UMAMI + $port = null; + } + if ($port) { + $fqdn = "$fqdn:$port"; + } + if (substr_count($key->value(), '_') >= 2) { if (is_null($value)) { $value = Str::of('/'); } @@ -403,11 +414,22 @@ class Service extends BaseModel } $fqdn = "$fqdn$path"; } + if (!$isDatabase) { + if ($savedService->fqdn) { + $fqdn = $savedService->fqdn . ',' . $fqdn; + } else { + $fqdn = $fqdn; + } $savedService->fqdn = $fqdn; $savedService->save(); } } + // data_forget($service, "environment.$variableName"); + // $yaml = data_forget($yaml, "services.$serviceName.environment.$variableName"); + // if (count(data_get($yaml, 'services.' . $serviceName . '.environment')) === 0) { + // $yaml = data_forget($yaml, "services.$serviceName.environment"); + // } continue; } if ($value?->startsWith('$')) { @@ -422,10 +444,17 @@ class Service extends BaseModel $forService = $value->afterLast('_'); $generatedValue = null; if ($command->value() === 'FQDN' || $command->value() === 'URL') { - $fqdn = generateFqdn($this->server, $containerName); + if (Str::lower($forService) === $serviceName) { + $fqdn = generateFqdn($this->server, $containerName); + } else { + $fqdn = generateFqdn($this->server, Str::lower($forService) . '-' . $this->uuid); + } if ($foundEnv) { $fqdn = data_get($foundEnv, 'value'); } else { + if ($command->value() === 'URL') { + $fqdn = Str::of($fqdn)->after('://')->value(); + } EnvironmentVariable::create([ 'key' => $key, 'value' => $fqdn, @@ -434,10 +463,11 @@ class Service extends BaseModel 'is_preview' => false, ]); } - if (!$isDatabase) { - $savedService->fqdn = $fqdn; - $savedService->save(); + if ($command->value() === 'FQDN') { + $savedService->fqdn = $fqdn; + $savedService->save(); + } } } else { switch ($command) { @@ -513,19 +543,19 @@ class Service extends BaseModel $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($fqdns, $containerName, true)); } } - data_set($service, 'labels', $serviceLabels->toArray()); data_forget($service, 'is_database'); data_set($service, 'restart', RESTART_MODE); data_set($service, 'container_name', $containerName); data_forget($service, 'volumes.*.content'); data_forget($service, 'volumes.*.isDirectory'); - // Remove unnecessary variables from service.environment $withoutServiceEnvs = collect([]); collect(data_get($service, 'environment'))->each(function ($value, $key) use ($withoutServiceEnvs) { - if (!Str::of($key)->startsWith('$SERVICE_')) { - $withoutServiceEnvs->put($key, $value); + if (!Str::of($key)->startsWith('$SERVICE_') && !Str::of($value)->startsWith('SERVICE_')) { + $k = Str::of($value)->before("="); + $v = Str::of($value)->after("="); + $withoutServiceEnvs->put($k->value(), $v->value()); } }); data_set($service, 'environment', $withoutServiceEnvs->toArray()); diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php index ed270862b..d177e9ab9 100644 --- a/app/Models/StandalonePostgresql.php +++ b/app/Models/StandalonePostgresql.php @@ -28,7 +28,7 @@ class StandalonePostgresql extends BaseModel 'is_readonly' => true ]); }); - static::deleted(function ($database) { + static::deleting(function ($database) { // Stop Container instant_remote_process( ["docker rm -f {$database->uuid}"], diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index e22a3596c..142754801 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -157,11 +157,13 @@ function fqdnLabelsForTraefik(Collection $domains, $container_name, $is_force_ht $path = $url->getPath(); $schema = $url->getScheme(); $port = $url->getPort(); - $slug = Str::slug($host . $path); - - $http_label = "{$container_name}-{$slug}-http"; - $https_label = "{$container_name}-{$slug}-https"; + $http_label = "{$container_name}-http"; + $https_label = "{$container_name}-https"; + if ($port) { + $http_label = "{$http_label}-{$port}"; + $https_label = "{$https_label}-{$port}"; + } if ($schema === 'https') { // Set labels for https $labels->push("traefik.http.routers.{$https_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)"); diff --git a/bootstrap/helpers/services.php b/bootstrap/helpers/services.php index 2590049ad..762b83563 100644 --- a/bootstrap/helpers/services.php +++ b/bootstrap/helpers/services.php @@ -137,13 +137,18 @@ function updateCompose($resource) // Update FQDN $variableName = "SERVICE_FQDN_" . Str::of($resource->name)->upper(); - ray($variableName); $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); if ($generatedEnv) { $generatedEnv->value = $resource->fqdn; $generatedEnv->save(); } - + $variableName = "SERVICE_URL_" . Str::of($resource->name)->upper(); + $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); + if ($generatedEnv) { + $url = Str::of($resource->fqdn)->after('://'); + $generatedEnv->value = $url; + $generatedEnv->save(); + } $dockerComposeRaw = Yaml::dump($dockerCompose, 10, 2); $resource->service->docker_compose_raw = $dockerComposeRaw; diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 01f9d55ae..0d4971a5f 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -14,6 +14,7 @@ use Illuminate\Mail\Message; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Route; @@ -425,6 +426,16 @@ function getServiceTemplates() if (isDev()) { $services = File::get(base_path('templates/service-templates.json')); $services = collect(json_decode($services))->sortKeys(); + $deprecated = File::get(base_path('templates/deprecated.json')); + $deprecated = collect(json_decode($deprecated))->sortKeys(); + $services = $services->merge($deprecated); + $version = config('version'); + $services = $services->map(function ($service) use ($version) { + if (version_compare($version, data_get($service,'minVersion', '0.0.0'), '<')) { + $service->disabled = true; + } + return $service; + }); } else { $services = Http::get(config('constants.services.official')); if ($services->failed()) { diff --git a/config/sentry.php b/config/sentry.php index 338151754..abbd727b5 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ return [ // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) - 'release' => '4.0.0-beta.64', + 'release' => '4.0.0-beta.65', // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/version.php b/config/version.php index 765ef0e18..e13d2a071 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ merge(['class' => 'text-xs cursor-pointer opacity-20 hover:opacity-100 hover:text-white z-50']) }} +merge(['class' => 'text-xs cursor-pointer opacity-60 hover:opacity-100 hover:text-white z-50']) }} href="https://github.com/coollabsio/coolify/releases/tag/v{{ config('version') }}">v{{ config('version') }} diff --git a/resources/views/livewire/dev/compose.blade.php b/resources/views/livewire/dev/compose.blade.php new file mode 100644 index 000000000..5d7ad1be1 --- /dev/null +++ b/resources/views/livewire/dev/compose.blade.php @@ -0,0 +1,13 @@ +
+

Compose

+
All kinds of compose files.
+

Services

+ @foreach ($services as $serviceName => $value) + {{ Str::headline($serviceName) }} + @endforeach +

Base64 En/Decode

+ Copy Base64 Compose +
+ +
+
diff --git a/resources/views/livewire/project/new/select.blade.php b/resources/views/livewire/project/new/select.blade.php index a673b693a..8e4b9d54c 100644 --- a/resources/views/livewire/project/new/select.blade.php +++ b/resources/views/livewire/project/new/select.blade.php @@ -95,19 +95,31 @@ @else @foreach ($services as $serviceName => $service) - + You need to upgrade to {{ data_get($service, 'minVersion') }} to use this service. + + + @else + + @endif @endforeach @endif diff --git a/routes/web.php b/routes/web.php index 8fe8bd1db..1ebbb46fd 100644 --- a/routes/web.php +++ b/routes/web.php @@ -9,6 +9,7 @@ use App\Http\Controllers\ServerController; use App\Http\Livewire\Boarding\Index as BoardingIndex; use App\Http\Livewire\Project\Service\Index as ServiceIndex; use App\Http\Livewire\Project\Service\Show as ServiceShow; +use App\Http\Livewire\Dev\Compose as Compose; use App\Http\Livewire\Dashboard; use App\Http\Livewire\Project\Shared\Logs; use App\Http\Livewire\Server\All; @@ -29,6 +30,9 @@ use Laravel\Fortify\Contracts\FailedPasswordResetLinkRequestResponse; use Laravel\Fortify\Contracts\SuccessfulPasswordResetLinkRequestResponse; use Laravel\Fortify\Fortify; +if (isDev()) { + Route::get('/dev/compose', Compose::class)->name('dev.compose'); +} Route::post('/forgot-password', function (Request $request) { if (is_transactional_emails_active()) { $arrayOfRequest = $request->only(Fortify::email()); @@ -94,7 +98,6 @@ Route::middleware(['auth'])->group(function () { Route::get('/project/{project_uuid}/{environment_name}/service/{service_uuid}', ServiceIndex::class)->name('project.service'); Route::get('/project/{project_uuid}/{environment_name}/service/{service_uuid}/{service_name}', ServiceShow::class)->name('project.service.show'); Route::get('/project/{project_uuid}/{environment_name}/service/{service_uuid}/{service_name}/logs', Logs::class)->name('project.service.logs'); - }); Route::middleware(['auth'])->group(function () { diff --git a/scripts/run b/scripts/run index 85eed74d4..33c5da621 100755 --- a/scripts/run +++ b/scripts/run @@ -21,8 +21,8 @@ function help { } function setup:dev { - docker exec coolify bash -c "composer install" docker exec coolify bash -c "php artisan key:generate" + docker exec coolify bash -c "composer install" docker exec coolify bash -c "php artisan migrate:fresh --seed" } function sync:v3 { diff --git a/templates/deprecated.json b/templates/deprecated.json index 59c1be455..5ed9d92a0 100644 --- a/templates/deprecated.json +++ b/templates/deprecated.json @@ -3,5 +3,5 @@ "documentation": "https://plausible.io/docs", "slogan": "A lighweight and open-source website analytics tool.", "compose": "dmVyc2lvbjogJzMuMycKc2VydmljZXM6CiAgcGxhdXNpYmxlX2RiOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNC1hbHBpbmUnCiAgICByZXN0YXJ0OiBhbHdheXMKICAgIHZvbHVtZXM6CiAgICAgIC0gJy9ldGMvZGF0YS9wbGF1c2libGUvZGItZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBQT1NUR1JFU19QQVNTV09SRD0kUE9TVEdSRVNfUEFTU1dPUkQKICBwbGF1c2libGVfZXZlbnRzX2RiOgogICAgaW1hZ2U6ICdjbGlja2hvdXNlL2NsaWNraG91c2Utc2VydmVyOjIzLjMuNy41LWFscGluZScKICAgIHJlc3RhcnQ6IGFsd2F5cwogICAgdm9sdW1lczoKICAgICAgLSAnL2V0Yy9kYXRhL3BsYXVzaWJsZS9ldmVudC1kYXRhOi92YXIvbGliL2NsaWNraG91c2UnCiAgICAgIC0gdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogL2V0Yy9kYXRhL3BsYXVzaWJsZS9jbGlja2hvdXNlL2NsaWNraG91c2UtY29uZmlnLnhtbAogICAgICAgIHRhcmdldDogL2V0Yy9jbGlja2hvdXNlLXNlcnZlci9jb25maWcuZC9sb2dnaW5nLnhtbAogICAgICAgIHJlYWRfb25seTogdHJ1ZQogICAgICAgIGNvbnRlbnQ6ID4tCiAgICAgICAgICA8Y2xpY2tob3VzZT48cHJvZmlsZXM+PGRlZmF1bHQ+PGxvZ19xdWVyaWVzPjA8L2xvZ19xdWVyaWVzPjxsb2dfcXVlcnlfdGhyZWFkcz4wPC9sb2dfcXVlcnlfdGhyZWFkcz48L2RlZmF1bHQ+PC9wcm9maWxlcz48L2NsaWNraG91c2U+CiAgICAgIC0gdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogL2V0Yy9kYXRhL3BsYXVzaWJsZS9jbGlja2hvdXNlL2NsaWNraG91c2UtdXNlci1jb25maWcueG1sCiAgICAgICAgdGFyZ2V0OiAvZXRjL2NsaWNraG91c2Utc2VydmVyL3VzZXJzLmQvbG9nZ2luZy54bWwKICAgICAgICByZWFkX29ubHk6IHRydWUKICAgICAgICBjb250ZW50OiA+LQogICAgICAgICAgPGNsaWNraG91c2U+PGxvZ2dlcj48bGV2ZWw+d2FybmluZzwvbGV2ZWw+PGNvbnNvbGU+dHJ1ZTwvY29uc29sZT48L2xvZ2dlcj48cXVlcnlfdGhyZWFkX2xvZwogICAgICAgICAgcmVtb3ZlPSJyZW1vdmUiLz48cXVlcnlfbG9nIHJlbW92ZT0icmVtb3ZlIi8+PHRleHRfbG9nCiAgICAgICAgICByZW1vdmU9InJlbW92ZSIvPjx0cmFjZV9sb2cgcmVtb3ZlPSJyZW1vdmUiLz48bWV0cmljX2xvZwogICAgICAgICAgcmVtb3ZlPSJyZW1vdmUiLz48YXN5bmNocm9ub3VzX21ldHJpY19sb2cKICAgICAgICAgIHJlbW92ZT0icmVtb3ZlIi8+PHNlc3Npb25fbG9nIHJlbW92ZT0icmVtb3ZlIi8+PHBhcnRfbG9nCiAgICAgICAgICByZW1vdmU9InJlbW92ZSIvPjwvY2xpY2tob3VzZT4KICAgIHVsaW1pdHM6CiAgICAgICAgbm9maWxlOgogICAgICAgICAgc29mdDogMjYyMTQ0CiAgICAgICAgICBoYXJkOiAyNjIxNDQKICBwbGF1c2libGU6CiAgICBpbWFnZTogJ3BsYXVzaWJsZS9hbmFseXRpY3M6djIuMCcKICAgIHJlc3RhcnQ6IGFsd2F5cwogICAgY29tbWFuZDogJ3NoIC1jICJzbGVlcCAxMCAmJiAvZW50cnlwb2ludC5zaCBkYiBjcmVhdGVkYiAmJiAvZW50cnlwb2ludC5zaCBkYiBtaWdyYXRlICYmIC9lbnRyeXBvaW50LnNoIHJ1biInCiAgICBkZXBlbmRzX29uOgogICAgICAtIHBsYXVzaWJsZV9kYgogICAgICAtIHBsYXVzaWJsZV9ldmVudHNfZGIKICAgIHBvcnRzOgogICAgICAtICc4MDAwOjgwMDAnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRUNSRVRfS0VZX0JBU0U9JFNFQ1JFVF9LRVlfQkFTRQogICAgICAtIERBVEFCQVNFX1VSTD0kREFUQUJBU0VfVVJMCiAgICAgIC0gJ0NMSUNLSE9VU0VfREFUQUJBU0VfVVJMPWh0dHA6Ly9wbGF1c2libGVfZXZlbnRzX2RiOjgxMjMvcGxhdXNpYmxlX2V2ZW50c19kYicKICAgICAgLSBNQUlMRVJfQURBUFRFUj0kTUFJTEVSX0FEQVBURVIKICAgICAgLSBTRU5ER1JJRF9BUElfS0VZPSRTRU5ER1JJRF9BUElfS0VZCiAgICAgIC0gR09PR0xFX0NMSUVOVF9JRD0kR09PR0xFX0NMSUVOVF9JRAogICAgICAtIEdPT0dMRV9DTElFTlRfU0VDUkVUPSRHT09HTEVfQ0xJRU5UX1NFQ1JFVAogICAgICAtIERJU0FCTEVfUkVHSVNUUkFUSU9OPSRESVNBQkxFX1JFR0lTVFJBVElPTgogICAgICAtIEJBU0VfVVJMPSRCQVNFX1VSTAogICAgICAtIExPR19GQUlMRURfTE9HSU5fQVRURU1QVFM9JExPR19GQUlMRURfTE9HSU5fQVRURU1QVFMK" - }, + } } diff --git a/versions.json b/versions.json index 7c148102a..f8daa5396 100644 --- a/versions.json +++ b/versions.json @@ -4,7 +4,7 @@ "version": "3.12.36" }, "v4": { - "version": "4.0.0-beta.64" + "version": "4.0.0-beta.65" } } }