+ class="group flex cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-accent"
+ :class="viewClasses.item">
+
+ class="flex shrink-0 items-center justify-center overflow-hidden border border-neutral-200 bg-neutral-50 dark:border-white/[0.08] dark:bg-white/[0.04]"
+ :class="viewClasses.logo">
@@ -329,23 +357,23 @@
x-text="service.arm_only ? 'ARM only' : 'AMD only'">
-
@@ -385,7 +413,34 @@
label: 'Services'
}
],
- filterOpen: false,
+ viewMode: localStorage.getItem('new-resource-view') === 'grid' ? 'grid' : 'list',
+ setViewMode(mode) {
+ this.viewMode = mode;
+ localStorage.setItem('new-resource-view', mode);
+ },
+ get viewClasses() {
+ if (this.viewMode === 'list') {
+ return {
+ container: 'flex flex-col divide-y divide-neutral-200 overflow-hidden rounded-[calc(0.5rem-1px)] dark:divide-white/[0.07]',
+ item: 'items-center gap-4 px-4 py-2.5 hover:bg-neutral-50 dark:hover:bg-white/[0.025]',
+ logo: 'size-8 rounded-lg',
+ header: 'flex-1 items-center! lg:w-64 lg:flex-none',
+ description: 'hidden min-w-0 flex-1 truncate lg:block',
+ footer: 'w-44 shrink-0 justify-end',
+ action: '',
+ };
+ }
+
+ return {
+ container: 'grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3',
+ item: 'min-h-48 flex-col rounded-xl border border-neutral-200 bg-white p-4 hover:border-neutral-300 dark:border-white/[0.08] dark:bg-white/[0.05] dark:hover:border-white/[0.14]',
+ logo: 'size-11 rounded-xl',
+ header: '',
+ description: 'mt-3 line-clamp-2',
+ footer: 'mt-auto border-t border-neutral-200 pt-3 dark:border-white/[0.07]',
+ action: 'ml-auto',
+ };
+ },
categoryOpen: false,
categorySearch: '',
selectedCategory: '',
@@ -412,39 +467,39 @@
databaseLinks: {
postgresql: {
docs: 'https://www.postgresql.org/docs/',
- website: 'https://www.postgresql.org/'
+ website: 'https://www.postgresql.org/?ref=coolify.io'
},
mysql: {
docs: 'https://dev.mysql.com/doc/',
- website: 'https://www.mysql.com/'
+ website: 'https://www.mysql.com/?ref=coolify.io'
},
mariadb: {
docs: 'https://mariadb.com/kb/en/documentation/',
- website: 'https://mariadb.org/'
+ website: 'https://mariadb.org/?ref=coolify.io'
},
redis: {
docs: 'https://redis.io/docs/latest/',
- website: 'https://redis.io/'
+ website: 'https://redis.io/?ref=coolify.io'
},
keydb: {
docs: 'https://docs.keydb.dev/',
- website: 'https://keydb.dev/'
+ website: 'https://keydb.dev/?ref=coolify.io'
},
dragonfly: {
docs: 'https://www.dragonflydb.io/docs',
- website: 'https://www.dragonflydb.io/'
+ website: 'https://www.dragonflydb.io/?ref=coolify.io'
},
mongodb: {
docs: 'https://www.mongodb.com/docs/',
- website: 'https://www.mongodb.com/'
+ website: 'https://www.mongodb.com/?ref=coolify.io'
},
clickhouse: {
docs: 'https://clickhouse.com/docs',
- website: 'https://clickhouse.com/'
+ website: 'https://clickhouse.com/?ref=coolify.io'
},
sqlite: {
docs: 'https://sqlite.org/docs.html',
- website: 'https://sqlite.org/'
+ website: 'https://sqlite.org/?ref=coolify.io'
}
},
docLinkCache: {}, // Cache resolved doc URLs: { serviceName: url | null }
diff --git a/tests/Feature/Authorization/RenderedComposeLockedValueRedactionTest.php b/tests/Feature/Authorization/RenderedComposeLockedValueRedactionTest.php
index 9406a80c16..2cda4c8224 100644
--- a/tests/Feature/Authorization/RenderedComposeLockedValueRedactionTest.php
+++ b/tests/Feature/Authorization/RenderedComposeLockedValueRedactionTest.php
@@ -28,6 +28,7 @@ services:
LOCKED_TOKEN: ""
PLAIN_TOKEN: ""
TEAM_SECRET: '{{team.TEAM_SECRET}}'
+ SERVER_SECRET: '{{server.SERVER_SECRET}}'
YAML;
beforeEach(function () {
@@ -52,6 +53,15 @@ beforeEach(function () {
'is_shown_once' => true,
]);
+ SharedEnvironmentVariable::create([
+ 'type' => 'server',
+ 'key' => 'SERVER_SECRET',
+ 'value' => 'server-shared-locked-secret',
+ 'team_id' => $this->team->id,
+ 'server_id' => $server->id,
+ 'is_shown_once' => true,
+ ]);
+
$this->service = Service::factory()->create([
'environment_id' => $this->environment->id,
'server_id' => $server->id,
@@ -92,6 +102,7 @@ function expectLockedValuesRedacted(?string $text): void
{
expect($text)->not->toContain('env-locked-secret-value')
->not->toContain('team-shared-locked-secret')
+ ->not->toContain('server-shared-locked-secret')
->toContain('
')
->toContain('plain-visible-value');
}
@@ -101,7 +112,8 @@ function expectStoredComposeKeepsRealValues(Application|Service $resource): void
expect($resource->fresh()->docker_compose)
->not->toContain('')
->toContain('env-locked-secret-value')
- ->toContain('team-shared-locked-secret');
+ ->toContain('team-shared-locked-secret')
+ ->toContain('server-shared-locked-secret');
}
it('writes the locked values into the generated compose that deployments use', function (string $resource) {
@@ -117,7 +129,8 @@ it('hides locked values in the service compose that an owner sees', function (st
expectLockedValuesRedacted($livewire->get('dockerCompose'));
expect(json_encode($livewire->snapshot).$livewire->html())
->not->toContain('env-locked-secret-value')
- ->not->toContain('team-shared-locked-secret');
+ ->not->toContain('team-shared-locked-secret')
+ ->not->toContain('server-shared-locked-secret');
})->with(['stack form' => [StackForm::class], 'compose editor' => [EditCompose::class]]);
it('hides locked values in the application compose that an owner sees', function () {
@@ -129,7 +142,8 @@ it('hides locked values in the application compose that an owner sees', function
expectLockedValuesRedacted($livewire->get('dockerCompose'));
expect(json_encode($livewire->snapshot).$livewire->html())
->not->toContain('env-locked-secret-value')
- ->not->toContain('team-shared-locked-secret');
+ ->not->toContain('team-shared-locked-secret')
+ ->not->toContain('server-shared-locked-secret');
});
it('keeps the real generated compose when an owner saves the service stack form', function () {
diff --git a/tests/Feature/ServiceTemplatesLastUpdatedHintTest.php b/tests/Feature/ServiceTemplatesLastUpdatedHintTest.php
index d6519cbb39..75ec0e5f9e 100644
--- a/tests/Feature/ServiceTemplatesLastUpdatedHintTest.php
+++ b/tests/Feature/ServiceTemplatesLastUpdatedHintTest.php
@@ -156,9 +156,9 @@ it('links service websites to the project page instead of a shared host root', f
$services = (new Select)->loadServices()['services'];
- expect($services['uptime-kuma']['website'])->toBe('https://github.com/louislam/uptime-kuma')
- ->and($services['alexandrie']['website'])->toBe('https://www.github.com/Smaug6739/Alexandrie')
- ->and($services['radarr']['website'])->toBe('https://hub.docker.com/r/linuxserver/radarr')
- ->and($services['n8n']['website'])->toBe('https://n8n.io')
+ expect($services['uptime-kuma']['website'])->toBe('https://github.com/louislam/uptime-kuma?ref=coolify.io')
+ ->and($services['alexandrie']['website'])->toBe('https://www.github.com/Smaug6739/Alexandrie?ref=coolify.io')
+ ->and($services['radarr']['website'])->toBe('https://hub.docker.com/r/linuxserver/radarr?ref=coolify.io')
+ ->and($services['n8n']['website'])->toBe('https://n8n.io?ref=coolify.io')
->and($services['broken']['website'])->toBeNull();
});
diff --git a/tests/Feature/SharedVariableComposeResolutionTest.php b/tests/Feature/SharedVariableComposeResolutionTest.php
index 5ffb027f0e..d97b0cd913 100644
--- a/tests/Feature/SharedVariableComposeResolutionTest.php
+++ b/tests/Feature/SharedVariableComposeResolutionTest.php
@@ -4,6 +4,7 @@ use App\Models\Application;
use App\Models\Environment;
use App\Models\EnvironmentVariable;
use App\Models\Project;
+use App\Models\Server;
use App\Models\SharedEnvironmentVariable;
use App\Models\Team;
use App\Models\User;
@@ -21,8 +22,13 @@ beforeEach(function () {
'project_id' => $this->project->id,
]);
+ $this->server = Server::factory()->create(['team_id' => $this->team->id]);
+ $destination = $this->server->standaloneDockers()->firstOrFail();
+
$this->application = Application::factory()->create([
'environment_id' => $this->environment->id,
+ 'destination_id' => $destination->id,
+ 'destination_type' => $destination->getMorphClass(),
]);
});
@@ -64,6 +70,19 @@ test('resolveSharedEnvironmentVariables resolves team-scoped variable', function
expect($resolved)->toBe('sk-123456');
});
+test('resolveSharedEnvironmentVariables resolves server-scoped variable', function () {
+ SharedEnvironmentVariable::create([
+ 'key' => 'COMMON_PG_HOST',
+ 'value' => 'postgres.coolify',
+ 'type' => 'server',
+ 'server_id' => $this->server->id,
+ 'team_id' => $this->team->id,
+ ]);
+
+ $resolved = resolveSharedEnvironmentVariables('{{server.COMMON_PG_HOST}}', $this->application);
+ expect($resolved)->toBe('postgres.coolify');
+});
+
test('resolveSharedEnvironmentVariables returns original when no match found', function () {
$resolved = resolveSharedEnvironmentVariables('{{environment.NONEXISTENT}}', $this->application);
expect($resolved)->toBe('{{environment.NONEXISTENT}}');