Merge remote-tracking branch 'origin/next' into improve-application-url-handling
This commit is contained in:
commit
cc4f666ba2
5 changed files with 99 additions and 27 deletions
|
|
@ -112,14 +112,17 @@ public function loadServices()
|
||||||
$default_logo = 'images/default.webp';
|
$default_logo = 'images/default.webp';
|
||||||
$logo = data_get($service, 'logo', $default_logo);
|
$logo = data_get($service, 'logo', $default_logo);
|
||||||
$local_logo_path = public_path($logo);
|
$local_logo_path = public_path($logo);
|
||||||
|
$serviceKey = (string) $key;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'name' => str($key)->headline(),
|
'id' => $serviceKey,
|
||||||
|
'name' => str($serviceKey)->headline(),
|
||||||
|
'docsSlug' => str($serviceKey)->lower()->value(),
|
||||||
'logo' => asset($logo),
|
'logo' => asset($logo),
|
||||||
'logo_github_url' => file_exists($local_logo_path)
|
'logo_github_url' => file_exists($local_logo_path)
|
||||||
? 'https://raw.githubusercontent.com/coollabsio/coolify/refs/heads/main/public/'.$logo
|
? 'https://raw.githubusercontent.com/coollabsio/coolify/refs/heads/main/public/'.$logo
|
||||||
: asset($default_logo),
|
: asset($default_logo),
|
||||||
'templateLastUpdated' => $templateLastUpdatedMap[(string) $key] ?? null,
|
'templateLastUpdated' => $templateLastUpdatedMap[$serviceKey] ?? null,
|
||||||
] + (array) $service;
|
] + (array) $service;
|
||||||
})->all();
|
})->all();
|
||||||
|
|
||||||
|
|
@ -336,7 +339,10 @@ private function formatLastModified(string $path): ?string
|
||||||
|
|
||||||
public function setType(string $type)
|
public function setType(string $type)
|
||||||
{
|
{
|
||||||
$type = str($type)->lower()->slug()->value();
|
if (! str($type)->startsWith('one-click-service-')) {
|
||||||
|
$type = str($type)->lower()->slug()->value();
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->loading) {
|
if ($this->loading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,10 @@ public function getEnvironmentVariablesPreviewProperty()
|
||||||
|
|
||||||
private function getEnvironmentVariables(bool $isPreview, bool $withSearch = true): Collection
|
private function getEnvironmentVariables(bool $isPreview, bool $withSearch = true): Collection
|
||||||
{
|
{
|
||||||
|
if ($isPreview && ! $this->supportsPreviewEnvironmentVariables()) {
|
||||||
|
return collect();
|
||||||
|
}
|
||||||
|
|
||||||
$query = $isPreview
|
$query = $isPreview
|
||||||
? $this->resource->environment_variables_preview()
|
? $this->resource->environment_variables_preview()
|
||||||
: $this->resource->environment_variables();
|
: $this->resource->environment_variables();
|
||||||
|
|
@ -119,12 +123,21 @@ private function searchTerm(): string
|
||||||
return trim($this->search);
|
return trim($this->search);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function supportsPreviewEnvironmentVariables(): bool
|
||||||
|
{
|
||||||
|
return $this->showPreview && $this->resource instanceof Application;
|
||||||
|
}
|
||||||
|
|
||||||
public function getHasEnvironmentVariablesProperty(): bool
|
public function getHasEnvironmentVariablesProperty(): bool
|
||||||
{
|
{
|
||||||
return $this->environmentVariables->isNotEmpty() ||
|
$hasPreviewEnvironmentVariables = $this->supportsPreviewEnvironmentVariables() && (
|
||||||
$this->environmentVariablesPreview->isNotEmpty() ||
|
$this->environmentVariablesPreview->isNotEmpty() ||
|
||||||
|
$this->hardcodedEnvironmentVariablesPreview->isNotEmpty()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->environmentVariables->isNotEmpty() ||
|
||||||
$this->hardcodedEnvironmentVariables->isNotEmpty() ||
|
$this->hardcodedEnvironmentVariables->isNotEmpty() ||
|
||||||
$this->hardcodedEnvironmentVariablesPreview->isNotEmpty();
|
$hasPreviewEnvironmentVariables;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function nullLockedValues($envs)
|
private function nullLockedValues($envs)
|
||||||
|
|
@ -158,6 +171,10 @@ public function getHardcodedEnvironmentVariablesPreviewProperty()
|
||||||
|
|
||||||
protected function getHardcodedVariables(bool $isPreview)
|
protected function getHardcodedVariables(bool $isPreview)
|
||||||
{
|
{
|
||||||
|
if ($isPreview && ! $this->supportsPreviewEnvironmentVariables()) {
|
||||||
|
return collect([]);
|
||||||
|
}
|
||||||
|
|
||||||
// Only for services and docker-compose applications
|
// Only for services and docker-compose applications
|
||||||
if ($this->resource->type() !== 'service' &&
|
if ($this->resource->type() !== 'service' &&
|
||||||
($this->resourceClass !== 'App\Models\Application' ||
|
($this->resourceClass !== 'App\Models\Application' ||
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,7 @@ class="text-xs text-neutral-500 dark:text-neutral-400">
|
||||||
|
|
||||||
<div class="grid justify-start grid-cols-1 gap-4 text-left xl:grid-cols-3">
|
<div class="grid justify-start grid-cols-1 gap-4 text-left xl:grid-cols-3">
|
||||||
<template x-for="service in filteredServices" :key="service.name">
|
<template x-for="service in filteredServices" :key="service.name">
|
||||||
<div class="relative" x-on:click="setType('one-click-service-' + service.name)"
|
<div class="relative" x-on:click="setType('one-click-service-' + service.id)"
|
||||||
:class="{ 'cursor-pointer': !selecting, 'cursor-not-allowed opacity-50': selecting }">
|
:class="{ 'cursor-pointer': !selecting, 'cursor-not-allowed opacity-50': selecting }">
|
||||||
<x-resource-view>
|
<x-resource-view>
|
||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
|
|
@ -214,7 +214,7 @@ class="px-2 py-0.5 text-xs rounded-full bg-amber-100 text-amber-800 dark:bg-ambe
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template x-if="shouldShowDocIcon(service)">
|
<template x-if="shouldShowDocIcon(service)">
|
||||||
<a :href="getDocLink(service) || coolifyDocsUrl(service.name)" target="_blank"
|
<a :href="getDocLink(service) || coolifyDocsUrl(service)" target="_blank"
|
||||||
@click.stop @mouseenter="resolveDocLink(service)"
|
@click.stop @mouseenter="resolveDocLink(service)"
|
||||||
class="absolute top-2 right-2 p-1.5 rounded hover:bg-neutral-200 dark:hover:bg-coolgray-300 transition-colors"
|
class="absolute top-2 right-2 p-1.5 rounded hover:bg-neutral-200 dark:hover:bg-coolgray-300 transition-colors"
|
||||||
:class="{ 'opacity-50': docCheckInProgress[service.name] }"
|
:class="{ 'opacity-50': docCheckInProgress[service.name] }"
|
||||||
|
|
@ -287,8 +287,8 @@ function searchResources() {
|
||||||
// Remove flavor suffixes: -with-*, -without-*
|
// Remove flavor suffixes: -with-*, -without-*
|
||||||
return normalized.replace(/-(with|without)-.+$/, '');
|
return normalized.replace(/-(with|without)-.+$/, '');
|
||||||
},
|
},
|
||||||
coolifyDocsUrl(serviceName) {
|
coolifyDocsUrl(service) {
|
||||||
const baseName = this.extractBaseServiceName(serviceName);
|
const baseName = service.docsSlug || this.extractBaseServiceName(service.name);
|
||||||
return 'https://coolify.io/docs/services/' + baseName;
|
return 'https://coolify.io/docs/services/' + baseName;
|
||||||
},
|
},
|
||||||
officialDocsUrl(service) {
|
officialDocsUrl(service) {
|
||||||
|
|
@ -322,7 +322,7 @@ function searchResources() {
|
||||||
this.docCheckInProgress[serviceName] = true;
|
this.docCheckInProgress[serviceName] = true;
|
||||||
|
|
||||||
// 1. Try Coolify docs first
|
// 1. Try Coolify docs first
|
||||||
const coolifyUrl = this.coolifyDocsUrl(serviceName);
|
const coolifyUrl = this.coolifyDocsUrl(service);
|
||||||
const coolifyExists = await this.checkUrlExists(coolifyUrl);
|
const coolifyExists = await this.checkUrlExists(coolifyUrl);
|
||||||
|
|
||||||
if (coolifyExists) {
|
if (coolifyExists) {
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@
|
||||||
->toBe(['API_KEY']);
|
->toBe(['API_KEY']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('treats production environment variable search wildcards literally', function () {
|
it('treats production environment variable search underscore wildcards literally', function () {
|
||||||
$application = Application::factory()->create([
|
$application = Application::factory()->create([
|
||||||
'environment_id' => $this->environment->id,
|
'environment_id' => $this->environment->id,
|
||||||
]);
|
]);
|
||||||
|
|
@ -75,23 +75,11 @@
|
||||||
'resourceable_id' => $application->id,
|
'resourceable_id' => $application->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
EnvironmentVariable::create([
|
|
||||||
'key' => 'PERCENT%KEY',
|
|
||||||
'value' => 'percent-secret',
|
|
||||||
'resourceable_type' => Application::class,
|
|
||||||
'resourceable_id' => $application->id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$component = Livewire::test(All::class, ['resource' => $application])
|
$component = Livewire::test(All::class, ['resource' => $application])
|
||||||
->set('search', 'api_key');
|
->set('search', 'api_key');
|
||||||
|
|
||||||
expect($component->instance()->environmentVariables->pluck('key')->all())
|
expect($component->instance()->environmentVariables->pluck('key')->all())
|
||||||
->toBe(['API_KEY']);
|
->toBe(['API_KEY']);
|
||||||
|
|
||||||
$component->set('search', '%KEY');
|
|
||||||
|
|
||||||
expect($component->instance()->environmentVariables->pluck('key')->all())
|
|
||||||
->toBe(['PERCENT%KEY']);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('filters preview environment variables by key case-insensitively', function () {
|
it('filters preview environment variables by key case-insensitively', function () {
|
||||||
|
|
@ -142,6 +130,34 @@
|
||||||
->toBe(['API_TOKEN']);
|
->toBe(['API_TOKEN']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('searches service environment variables without requiring preview variables', function () {
|
||||||
|
$service = Service::factory()->create([
|
||||||
|
'environment_id' => $this->environment->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
EnvironmentVariable::create([
|
||||||
|
'key' => 'API_KEY',
|
||||||
|
'value' => 'secret',
|
||||||
|
'resourceable_type' => Service::class,
|
||||||
|
'resourceable_id' => $service->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
EnvironmentVariable::create([
|
||||||
|
'key' => 'DATABASE_URL',
|
||||||
|
'value' => 'postgres://example',
|
||||||
|
'resourceable_type' => Service::class,
|
||||||
|
'resourceable_id' => $service->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$component = Livewire::test(All::class, ['resource' => $service])
|
||||||
|
->set('search', 'api')
|
||||||
|
->assertSee('Production Environment Variables')
|
||||||
|
->assertDontSee('Preview Deployments Environment Variables');
|
||||||
|
|
||||||
|
expect($component->instance()->environmentVariables->pluck('key')->all())
|
||||||
|
->toBe(['API_KEY']);
|
||||||
|
});
|
||||||
|
|
||||||
it('does not show the empty production message when search only matches hardcoded variables', function () {
|
it('does not show the empty production message when search only matches hardcoded variables', function () {
|
||||||
$service = Service::factory()->create([
|
$service = Service::factory()->create([
|
||||||
'environment_id' => $this->environment->id,
|
'environment_id' => $this->environment->id,
|
||||||
|
|
@ -155,11 +171,13 @@
|
||||||
YAML,
|
YAML,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Livewire::test(All::class, ['resource' => $service])
|
$component = Livewire::test(All::class, ['resource' => $service])
|
||||||
->set('search', 'api')
|
->set('search', 'api')
|
||||||
->assertSee('Production Environment Variables')
|
->assertSee('Production Environment Variables')
|
||||||
->assertSee('API_TOKEN')
|
|
||||||
->assertDontSee('No environment variables found.');
|
->assertDontSee('No environment variables found.');
|
||||||
|
|
||||||
|
expect($component->instance()->hardcodedEnvironmentVariables->pluck('key')->all())
|
||||||
|
->toBe(['API_TOKEN']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('keeps developer view unfiltered after searching', function () {
|
it('keeps developer view unfiltered after searching', function () {
|
||||||
|
|
@ -242,10 +260,12 @@
|
||||||
'resourceable_id' => $application->id,
|
'resourceable_id' => $application->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Livewire::test(All::class, ['resource' => $application])
|
$component = Livewire::test(All::class, ['resource' => $application])
|
||||||
->set('search', 'api')
|
->set('search', 'api')
|
||||||
->assertSee('Production Environment Variables')
|
->assertSee('Production Environment Variables')
|
||||||
->assertSee('API_KEY')
|
|
||||||
->assertDontSee('Preview Deployments Environment Variables')
|
->assertDontSee('Preview Deployments Environment Variables')
|
||||||
->assertDontSee('PREVIEW_TOKEN');
|
->assertDontSee('PREVIEW_TOKEN');
|
||||||
|
|
||||||
|
expect($component->instance()->environmentVariables->pluck('key')->all())
|
||||||
|
->toBe(['API_KEY']);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -87,3 +87,32 @@
|
||||||
$view->assertSee('serviceTemplatesLastUpdated');
|
$view->assertSee('serviceTemplatesLastUpdated');
|
||||||
$view->assertSee('service.templateLastUpdated');
|
$view->assertSee('service.templateLastUpdated');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('keeps service template keys for service selection and docs links', function () {
|
||||||
|
$services = collect((new Select)->loadServices()['services']);
|
||||||
|
$denoKv = $services->firstWhere('id', 'denoKV');
|
||||||
|
|
||||||
|
expect($denoKv)
|
||||||
|
->not->toBeNull()
|
||||||
|
->and($denoKv['docsSlug'])->toBe('denokv');
|
||||||
|
|
||||||
|
View::share('errors', new ViewErrorBag);
|
||||||
|
|
||||||
|
$view = $this->view('livewire.project.new.select', [
|
||||||
|
'current_step' => 'type',
|
||||||
|
'environments' => collect(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$view->assertSee("setType('one-click-service-' + service.id)", false);
|
||||||
|
$view->assertSee('service.docsSlug || this.extractBaseServiceName(service.name)', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves one click service key casing when selecting a service template', function () {
|
||||||
|
$component = new Select;
|
||||||
|
$component->servers = collect();
|
||||||
|
$component->allServers = collect();
|
||||||
|
|
||||||
|
$component->setType('one-click-service-denoKV');
|
||||||
|
|
||||||
|
expect($component->type)->toBe('one-click-service-denoKV');
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue