mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-14 06:17:36 +00:00
feat(server): fall back Sentinel URL to request host
Use the current request scheme/host when no FQDN or public IP is set, skipping loopback hosts. Polish boarding/server create cards, optional listbox tooltips, and cover the layout and Sentinel URL behavior in tests.
This commit is contained in:
1 parent
28f57aafb8
commit
de4e74b4cd
14 files changed
+203
-89
No files matched your search
@@ -14,3 +14,61 @@ test('server validation opens in the centered process dialog', function () {
|
||||
->toContain('@click="processDialogOpen = true"')
|
||||
->not->toContain('<x-slide-over closeWithX fullScreen>');
|
||||
});
|
||||
|
||||
test('DigitalOcean is available as an onboarding server provider', function () {
|
||||
$view = file_get_contents(resource_path('views/livewire/boarding/index.blade.php'));
|
||||
|
||||
expect($view)
|
||||
->toContain('<x-modal-input title="Connect a DigitalOcean Server" isFullWidth>')
|
||||
->toContain('<x-digital-ocean-icon class="size-10 shrink-0" />')
|
||||
->toContain('Deploy servers directly from your DigitalOcean account.')
|
||||
->toContain('<livewire:server.new.by-digital-ocean :limit_reached="false" :from_onboarding="true" />');
|
||||
});
|
||||
|
||||
test('server type details are shown on the relevant cards instead of a technical details panel', function () {
|
||||
$view = file_get_contents(resource_path('views/livewire/boarding/index.blade.php'));
|
||||
|
||||
expect($view)
|
||||
->toContain('aria-label="About this machine"')
|
||||
->toContain('aria-label="About remote servers"')
|
||||
->toContain('Not recommended for production workloads due to resource contention.')
|
||||
->toContain('Any SSH-accessible server, including cloud VPS, bare metal, and self-hosted infrastructure.')
|
||||
->not->toContain('<x-highlighted text="Servers" />')
|
||||
->not->toContain('<x-highlighted text="Localhost:" />')
|
||||
->not->toContain('<x-highlighted text="Remote Server:" />');
|
||||
});
|
||||
|
||||
test('server type cards use the standard card hover treatment', function () {
|
||||
$view = file_get_contents(resource_path('views/livewire/boarding/index.blade.php'));
|
||||
$hoverClasses = 'shadow-sm transition-all hover:-translate-y-px hover:border-neutral-300 hover:shadow-md';
|
||||
preg_match_all('/min-h-36[^\"]*'.preg_quote($hoverClasses, '/').'/', $view, $matches);
|
||||
|
||||
expect($matches[0])->toHaveCount(5)
|
||||
->and(substr_count($view, 'group relative cursor-pointer'))->toBe(5)
|
||||
->and($view)->not->toContain('hover:border-coollabs/35 hover:bg-coollabs/[0.03]')
|
||||
->and($view)->not->toContain('dark:hover:border-warning/25 dark:hover:bg-warning/[0.04]');
|
||||
});
|
||||
|
||||
test('existing SSH key selection does not show a redundant value tooltip', function () {
|
||||
$view = file_get_contents(resource_path('views/livewire/boarding/index.blade.php'));
|
||||
|
||||
expect($view)->toContain('label="Existing SSH key" :options="$privateKeyOptions" :tooltip="false"');
|
||||
});
|
||||
|
||||
test('server connection step does not render a technical details panel', function () {
|
||||
$view = file_get_contents(resource_path('views/livewire/boarding/index.blade.php'));
|
||||
|
||||
expect($view)
|
||||
->not->toContain('<x-highlighted text="Connection Requirements:" />')
|
||||
->not->toContain('<x-highlighted text="Hostname Resolution:" />')
|
||||
->not->toContain('<x-highlighted text="User Permissions:" />');
|
||||
});
|
||||
|
||||
test('project step does not render a technical details panel', function () {
|
||||
$view = file_get_contents(resource_path('views/livewire/boarding/index.blade.php'));
|
||||
|
||||
expect($view)
|
||||
->not->toContain('<x-highlighted text="Project Organization:" />')
|
||||
->not->toContain('<x-highlighted text="Environments:" />')
|
||||
->not->toContain('<x-highlighted text="Team Access:" />');
|
||||
});
|
||||
@@ -1,14 +1,18 @@
|
||||
<?php
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use App\Models\ServerSetting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Once;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
DB::table('instance_settings')->insert(['id' => 0]);
|
||||
$user = User::factory()->create();
|
||||
$this->team = $user->teams()->first();
|
||||
|
||||
@@ -146,6 +150,38 @@ describe('ServerSetting::ensureValidSentinelToken', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('ServerSetting::ensureSentinelUrl', function () {
|
||||
it('uses the current private instance URL when no public address is configured', function () {
|
||||
InstanceSettings::query()->whereKey(0)->update([
|
||||
'fqdn' => null,
|
||||
'public_ipv4' => null,
|
||||
'public_ipv6' => null,
|
||||
]);
|
||||
Once::flush();
|
||||
DB::table('server_settings')->where('id', $this->server->settings->id)->update(['sentinel_custom_url' => null]);
|
||||
app()->instance('request', Request::create('http://192.168.1.50:8000/server'));
|
||||
|
||||
$url = $this->server->settings->fresh()->ensureSentinelUrl();
|
||||
|
||||
expect($url)->toBe('http://192.168.1.50:8000')
|
||||
->and($this->server->settings->fresh()->sentinel_custom_url)->toBe($url);
|
||||
});
|
||||
|
||||
it('does not use a loopback request URL for a remote server', function () {
|
||||
InstanceSettings::query()->whereKey(0)->update([
|
||||
'fqdn' => null,
|
||||
'public_ipv4' => null,
|
||||
'public_ipv6' => null,
|
||||
]);
|
||||
Once::flush();
|
||||
DB::table('server_settings')->where('id', $this->server->settings->id)->update(['sentinel_custom_url' => null]);
|
||||
app()->instance('request', Request::create('http://localhost:8000/server'));
|
||||
|
||||
expect(fn () => $this->server->settings->fresh()->ensureSentinelUrl())
|
||||
->toThrow(RuntimeException::class, 'Set an instance FQDN, public IP, or reachable Coolify URL before enabling Sentinel.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('generated sentinel tokens are valid', function () {
|
||||
it('generates tokens that pass format validation', function () {
|
||||
$settings = $this->server->settings;
|
||||
|
||||
@@ -5,6 +5,16 @@ test('server creation uses the standard page title without redundant navigation'
|
||||
|
||||
expect($view)
|
||||
->toContain('<h1 class="min-w-0 text-[24px]! leading-7! font-semibold! tracking-tight!">New server</h1>')
|
||||
->toContain('class="mb-5 flex min-h-9 flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"')
|
||||
->not->toContain('Back to servers')
|
||||
->not->toContain('title="Add a server"');
|
||||
});
|
||||
|
||||
test('provider pages only show the token action in the account panel', function () {
|
||||
$view = file_get_contents(resource_path('views/livewire/server/create.blade.php'));
|
||||
|
||||
expect($view)
|
||||
->not->toContain('New token')
|
||||
->not->toContain('new-server-token-')
|
||||
->not->toContain('tokenProviderName');
|
||||
});
|
||||
@@ -8,6 +8,7 @@ test('server creation keeps private key actions together and advanced options co
|
||||
->toContain('x-data="{ advancedOpen: false }"')
|
||||
->toContain('x-show="advancedOpen" x-cloak')
|
||||
->toContain('Advanced settings')
|
||||
->toContain('class="flex w-full cursor-pointer items-center justify-between gap-3')
|
||||
->toContain('label="Use as a dedicated build server"')
|
||||
->toContain('helper="Build servers compile applications but do not host deployments. Enabling this makes the server build-only."');
|
||||
});
|
||||
|
||||
@@ -20,3 +20,13 @@ test('view switchers use the shared coollabs selected state on every page', func
|
||||
->toContain('@utility control-selected')
|
||||
->toContain('@apply bg-coollabs text-white dark:bg-coollabs dark:text-white;');
|
||||
});
|
||||
|
||||
test('server index does not expose server IP addresses', function () {
|
||||
$view = file_get_contents(resource_path('views/livewire/server/index.blade.php'));
|
||||
|
||||
expect($view)
|
||||
->not->toContain("'address' => \$server->ip")
|
||||
->not->toContain('<div>Address</div>')
|
||||
->not->toContain('x-text="server.address"')
|
||||
->not->toContain('server.address,');
|
||||
});
|
||||
@@ -10,7 +10,8 @@ test('sentinel startup regenerates an empty endpoint from instance settings', fu
|
||||
->toContain('$this->sentinelCustomUrl = $this->server->settings->sentinel_custom_url;')
|
||||
->and(file_get_contents(dirname(__DIR__, 2).'/app/Models/ServerSetting.php'))
|
||||
->toContain('generateSentinelUrl(ignoreEvent: true)')
|
||||
->toContain('Set an instance FQDN or public IP before enabling Sentinel.')
|
||||
->toContain('sentinelUrlFromCurrentRequest()')
|
||||
->toContain('Set an instance FQDN, public IP, or reachable Coolify URL before enabling Sentinel.')
|
||||
->and($component)
|
||||
->toContain('$this->sentinelCustomUrl = $this->server->settings->sentinel_custom_url;');
|
||||
});
|
||||
Reference in new issue
Block a user