mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-13 00:14:24 +00:00
feat(servers): separate connection and provisioning options
Group existing server connections separately from cloud provisioning and use a select for dedicated build server roles.
This commit is contained in:
1 parent
faf527d554
commit
4ba6aa3500
5 files changed
+148
-105
No files matched your search
@@ -95,7 +95,11 @@
|
||||
Select where to deploy your applications and databases. You can add more servers later.
|
||||
</x-slot:question>
|
||||
<x-slot:actions>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 w-full">
|
||||
<div class="w-full space-y-6">
|
||||
<section>
|
||||
<h3 class="text-base font-semibold">Add a server</h3>
|
||||
<p class="mb-3 text-sm text-neutral-500 dark:text-neutral-400">Use this machine or connect a server you already manage.</p>
|
||||
<div class="grid w-full grid-cols-1 gap-4 lg:grid-cols-2">
|
||||
<button
|
||||
class="group relative cursor-pointer min-h-36 rounded-[10px] border border-neutral-200 bg-white p-4 text-left shadow-sm transition-all hover:-translate-y-px hover:border-neutral-300 hover:shadow-md dark:border-white/[0.08] dark:bg-white/[0.025] dark:hover:border-white/[0.14]"
|
||||
wire:target="setServerType('localhost')" wire:click="setServerType('localhost')">
|
||||
@@ -134,26 +138,29 @@
|
||||
d="M2.25 15a4.5 4.5 0 004.5 4.5H18a3.75 3.75 0 001.332-7.257 3 3 0 00-3.758-3.848 5.25 5.25 0 00-10.233 2.33A4.502 4.502 0 002.25 15z" />
|
||||
</svg>
|
||||
<div>
|
||||
<h3 class="mb-1 text-[14px] font-semibold">Remote server</h3>
|
||||
<h3 class="mb-1 text-[14px] font-semibold">IP address or domain</h3>
|
||||
<p class="text-sm dark:text-neutral-400">
|
||||
Connect via SSH to any server: cloud VPS, bare metal, or home infrastructure.
|
||||
Connect via SSH using a server IP address or domain.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@can('viewAny', App\Models\CloudProviderToken::class)
|
||||
<section>
|
||||
<h3 class="text-base font-semibold">Provision a server</h3>
|
||||
<p class="mb-3 text-sm text-neutral-500 dark:text-neutral-400">Create a server with a cloud provider.</p>
|
||||
<div class="grid w-full grid-cols-1 gap-4 lg:grid-cols-2">
|
||||
@if ($currentState === 'select-server-type')
|
||||
<x-modal-input title="Connect a Hetzner Server" isFullWidth>
|
||||
<x-slot:content>
|
||||
<div
|
||||
class="group relative cursor-pointer flex h-full min-h-36 flex-col rounded-[10px] border border-neutral-200 bg-white p-4 text-left shadow-sm transition-all hover:-translate-y-px hover:border-neutral-300 hover:shadow-md dark:border-white/[0.08] dark:bg-white/[0.025] dark:hover:border-white/[0.14]">
|
||||
<div class="flex h-full flex-col gap-4 text-left">
|
||||
<svg class="size-10 shrink-0" viewBox="0 0 200 200"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="200" height="200" fill="#D50C2D" rx="8" />
|
||||
<path d="M40 40 H60 V90 H140 V40 H160 V160 H140 V110 H60 V160 H40 Z"
|
||||
fill="white" />
|
||||
</svg>
|
||||
<img src="{{ asset('svgs/hetzner.svg') }}" alt="Hetzner"
|
||||
class="size-10 shrink-0">
|
||||
<div class="min-h-0 flex-1">
|
||||
<h3 class="mb-1 text-[14px] font-semibold">Hetzner Cloud</h3>
|
||||
<p class="text-sm dark:text-neutral-400">
|
||||
@@ -170,12 +177,8 @@
|
||||
<div
|
||||
class="group relative cursor-pointer flex h-full min-h-36 flex-col rounded-[10px] border border-neutral-200 bg-white p-4 text-left shadow-sm transition-all hover:-translate-y-px hover:border-neutral-300 hover:shadow-md dark:border-white/[0.08] dark:bg-white/[0.025] dark:hover:border-white/[0.14]">
|
||||
<div class="flex h-full flex-col gap-4 text-left">
|
||||
<svg class="size-10 shrink-0" viewBox="0 0 200 200"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="200" height="200" fill="#007BFC" rx="8" />
|
||||
<path d="M42 46 H73 L100 127 L127 46 H158 L114 154 H86 Z"
|
||||
fill="white" />
|
||||
</svg>
|
||||
<img src="https://www.vultr.com/media/logo_ondark.svg" alt="Vultr"
|
||||
class="h-10 w-28 shrink-0 object-contain object-left">
|
||||
<div class="min-h-0 flex-1">
|
||||
<h3 class="mb-1 text-[14px] font-semibold">Vultr Cloud</h3>
|
||||
<p class="text-sm dark:text-neutral-400">
|
||||
@@ -205,6 +208,8 @@
|
||||
<livewire:server.new.by-digital-ocean :limit_reached="false" :from_onboarding="true" />
|
||||
</x-modal-input>
|
||||
@endif
|
||||
</div>
|
||||
</section>
|
||||
@endcan
|
||||
</div>
|
||||
|
||||
@@ -421,19 +426,13 @@
|
||||
<x-forms.input placeholder="Optional: Note what this server hosts" label="Description"
|
||||
id="remoteServerDescription" wire:model="remoteServerDescription" />
|
||||
|
||||
<x-forms.collapsible title="Advanced Connection Settings"
|
||||
<x-forms.collapsible title="Advanced Settings"
|
||||
content-class="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||
<x-forms.input placeholder="Default: 22" label="SSH Port" type="number"
|
||||
id="remoteServerPort" wire:model="remoteServerPort" />
|
||||
<div>
|
||||
<x-forms.input placeholder="Default: root" label="SSH User" id="remoteServerUser"
|
||||
wire:model="remoteServerUser" />
|
||||
<p class="mt-1 text-xs text-black dark:text-white">
|
||||
Non-root user support is experimental.
|
||||
<a class="font-bold underline hover:text-coollabs" target="_blank"
|
||||
href="https://coolify.io/docs/knowledge-base/server/non-root-user">Learn
|
||||
more</a>
|
||||
</p>
|
||||
</div>
|
||||
</x-forms.collapsible>
|
||||
<x-forms.button type="submit" class="w-full lg:w-auto">Validate Connection</x-forms.button>
|
||||
|
||||
@@ -15,91 +15,89 @@
|
||||
</div>
|
||||
|
||||
@if (!$selectedType)
|
||||
<div class="application-settings-form">
|
||||
<div class="application-settings-form flex flex-col gap-6">
|
||||
<section class="application-settings-section">
|
||||
<div class="application-settings-section-body is-flush">
|
||||
<div class="grid grid-cols-1 gap-3 p-3 sm:grid-cols-2 lg:grid-cols-4">
|
||||
@can('viewAny', App\Models\CloudProviderToken::class)
|
||||
<a href="{{ route('server.create.type', ['type' => 'hetzner']) }}"
|
||||
class="group flex min-h-32 flex-col rounded-xl border border-neutral-200 bg-white p-3 shadow-sm transition-all hover:-translate-y-px hover:border-neutral-300 hover:no-underline hover:shadow-md dark:border-white/[0.08] dark:bg-white/[0.025] dark:hover:border-white/[0.14]"
|
||||
{{ wireNavigate() }}>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<span
|
||||
class="flex size-8 items-center justify-center rounded-lg bg-[#D50C2D] text-[12px] font-bold text-white">H</span>
|
||||
<span
|
||||
class="rounded-full bg-neutral-100 px-2 py-0.5 text-[10px] font-medium text-neutral-600 dark:bg-white/[0.06] dark:text-fg-dim">
|
||||
Provider
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-auto pt-5">
|
||||
<h3 class="text-[13px]! font-semibold! text-black dark:text-fg">Hetzner</h3>
|
||||
<p class="mt-1 text-[11px] leading-4 text-neutral-500 dark:text-fg-faint">
|
||||
Provision from Hetzner Cloud.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('server.create.type', ['type' => 'vultr']) }}"
|
||||
class="group flex min-h-32 flex-col rounded-xl border border-neutral-200 bg-white p-3 shadow-sm transition-all hover:-translate-y-px hover:border-neutral-300 hover:no-underline hover:shadow-md dark:border-white/[0.08] dark:bg-white/[0.025] dark:hover:border-white/[0.14]"
|
||||
{{ wireNavigate() }}>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<span
|
||||
class="flex size-8 items-center justify-center rounded-lg bg-[#007BFC] text-[12px] font-bold text-white">V</span>
|
||||
<span
|
||||
class="rounded-full bg-neutral-100 px-2 py-0.5 text-[10px] font-medium text-neutral-600 dark:bg-white/[0.06] dark:text-fg-dim">
|
||||
Provider
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-auto pt-5">
|
||||
<h3 class="text-[13px]! font-semibold! text-black dark:text-fg">Vultr</h3>
|
||||
<p class="mt-1 text-[11px] leading-4 text-neutral-500 dark:text-fg-faint">
|
||||
Provision from Vultr Cloud.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('server.create.type', ['type' => 'digital-ocean']) }}"
|
||||
class="group flex min-h-32 flex-col rounded-xl border border-neutral-200 bg-white p-3 shadow-sm transition-all hover:-translate-y-px hover:border-neutral-300 hover:no-underline hover:shadow-md dark:border-white/[0.08] dark:bg-white/[0.025] dark:hover:border-white/[0.14]"
|
||||
{{ wireNavigate() }}>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<x-digital-ocean-icon class="size-8" />
|
||||
<span
|
||||
class="rounded-full bg-neutral-100 px-2 py-0.5 text-[10px] font-medium text-neutral-600 dark:bg-white/[0.06] dark:text-fg-dim">
|
||||
Provider
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-auto pt-5">
|
||||
<h3 class="text-[13px]! font-semibold! text-black dark:text-fg">DigitalOcean</h3>
|
||||
<p class="mt-1 text-[11px] leading-4 text-neutral-500 dark:text-fg-faint">
|
||||
Provision a new Droplet.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
@endcan
|
||||
|
||||
<a href="{{ route('server.create.type', ['type' => 'manual']) }}"
|
||||
class="group flex min-h-32 flex-col rounded-xl border border-neutral-200 bg-white p-3 shadow-sm transition-all hover:-translate-y-px hover:border-neutral-300 hover:no-underline hover:shadow-md dark:border-white/[0.08] dark:bg-white/[0.025] dark:hover:border-white/[0.14]"
|
||||
{{ wireNavigate() }}>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<span
|
||||
class="flex size-8 items-center justify-center rounded-lg border border-neutral-200 bg-neutral-50 text-neutral-500 dark:border-white/[0.08] dark:bg-white/[0.04] dark:text-fg-dim">
|
||||
<x-reicon name="servers" class="size-4" />
|
||||
</span>
|
||||
<span
|
||||
class="rounded-full bg-neutral-100 px-2 py-0.5 text-[10px] font-medium text-neutral-600 dark:bg-white/[0.06] dark:text-fg-dim">
|
||||
Manual
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-auto pt-5">
|
||||
<h3 class="text-[13px]! font-semibold! text-black dark:text-fg">IP address</h3>
|
||||
<p class="mt-1 text-[11px] leading-4 text-neutral-500 dark:text-fg-faint">
|
||||
Connect an existing server over SSH.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
<div class="application-settings-section-header">
|
||||
<h2 class="application-settings-section-title">Add a server</h2>
|
||||
<p class="application-settings-section-description">Connect a server you already manage.</p>
|
||||
</div>
|
||||
<div class="application-settings-section-body is-flush">
|
||||
<div class="grid grid-cols-1 gap-3 p-3 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<a href="{{ route('server.create.type', ['type' => 'manual']) }}"
|
||||
class="group flex min-h-32 flex-col rounded-xl border border-neutral-200 bg-white p-3 shadow-sm transition-all hover:-translate-y-px hover:border-neutral-300 hover:no-underline hover:shadow-md dark:border-white/[0.08] dark:bg-white/[0.025] dark:hover:border-white/[0.14]"
|
||||
{{ wireNavigate() }}>
|
||||
<div class="flex items-start">
|
||||
<span
|
||||
class="flex size-8 items-center justify-center rounded-lg border border-neutral-200 bg-neutral-50 text-neutral-500 dark:border-white/[0.08] dark:bg-white/[0.04] dark:text-fg-dim">
|
||||
<x-reicon name="servers" class="size-4" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-auto pt-5">
|
||||
<h3 class="text-[13px]! font-semibold! text-black dark:text-fg">IP address or domain</h3>
|
||||
<p class="mt-1 text-[11px] leading-4 text-neutral-500 dark:text-fg-faint">
|
||||
Connect an existing server over SSH.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@can('viewAny', App\Models\CloudProviderToken::class)
|
||||
<section class="application-settings-section">
|
||||
<div class="application-settings-section-header">
|
||||
<h2 class="application-settings-section-title">Provision a server</h2>
|
||||
<p class="application-settings-section-description">Create a server with a cloud provider.</p>
|
||||
</div>
|
||||
<div class="application-settings-section-body is-flush">
|
||||
<div class="grid grid-cols-1 gap-3 p-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<a href="{{ route('server.create.type', ['type' => 'hetzner']) }}"
|
||||
class="group flex min-h-32 flex-col rounded-xl border border-neutral-200 bg-white p-3 shadow-sm transition-all hover:-translate-y-px hover:border-neutral-300 hover:no-underline hover:shadow-md dark:border-white/[0.08] dark:bg-white/[0.025] dark:hover:border-white/[0.14]"
|
||||
{{ wireNavigate() }}>
|
||||
<div class="flex items-start">
|
||||
<img src="{{ asset('svgs/hetzner.svg') }}" alt="Hetzner" class="size-8">
|
||||
</div>
|
||||
<div class="mt-auto pt-5">
|
||||
<h3 class="text-[13px]! font-semibold! text-black dark:text-fg">Hetzner</h3>
|
||||
<p class="mt-1 text-[11px] leading-4 text-neutral-500 dark:text-fg-faint">
|
||||
Provision from Hetzner Cloud.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('server.create.type', ['type' => 'vultr']) }}"
|
||||
class="group flex min-h-32 flex-col rounded-xl border border-neutral-200 bg-white p-3 shadow-sm transition-all hover:-translate-y-px hover:border-neutral-300 hover:no-underline hover:shadow-md dark:border-white/[0.08] dark:bg-white/[0.025] dark:hover:border-white/[0.14]"
|
||||
{{ wireNavigate() }}>
|
||||
<div class="flex items-start">
|
||||
<img src="https://www.vultr.com/media/logo_ondark.svg" alt="Vultr"
|
||||
class="h-8 w-20 object-contain object-left">
|
||||
</div>
|
||||
<div class="mt-auto pt-5">
|
||||
<h3 class="text-[13px]! font-semibold! text-black dark:text-fg">Vultr</h3>
|
||||
<p class="mt-1 text-[11px] leading-4 text-neutral-500 dark:text-fg-faint">
|
||||
Provision from Vultr Cloud.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('server.create.type', ['type' => 'digital-ocean']) }}"
|
||||
class="group flex min-h-32 flex-col rounded-xl border border-neutral-200 bg-white p-3 shadow-sm transition-all hover:-translate-y-px hover:border-neutral-300 hover:no-underline hover:shadow-md dark:border-white/[0.08] dark:bg-white/[0.025] dark:hover:border-white/[0.14]"
|
||||
{{ wireNavigate() }}>
|
||||
<div class="flex items-start">
|
||||
<x-digital-ocean-icon class="size-8" />
|
||||
</div>
|
||||
<div class="mt-auto pt-5">
|
||||
<h3 class="text-[13px]! font-semibold! text-black dark:text-fg">DigitalOcean</h3>
|
||||
<p class="mt-1 text-[11px] leading-4 text-neutral-500 dark:text-fg-faint">
|
||||
Provision a new Droplet.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@endcan
|
||||
</div>
|
||||
@else
|
||||
<div class="application-settings-form">
|
||||
|
||||
@@ -83,9 +83,12 @@
|
||||
helper="Non-root SSH users are experimental." />
|
||||
<x-forms.input type="number" id="port" label="Port" required />
|
||||
</div>
|
||||
<x-forms.checkbox id="is_build_server"
|
||||
<x-forms.select id="is_build_server"
|
||||
helper="Build servers compile applications but do not host deployments. Enabling this makes the server build-only."
|
||||
label="Use as a dedicated build server" />
|
||||
label="Use as a dedicated build server">
|
||||
<option value="0">No</option>
|
||||
<option value="1">Yes</option>
|
||||
</x-forms.select>
|
||||
</x-forms.collapsible>
|
||||
</x-application.settings-section>
|
||||
</form>
|
||||
|
||||
@@ -18,3 +18,42 @@ test('provider pages only show the token action in the account panel', function
|
||||
->not->toContain('new-server-token-')
|
||||
->not->toContain('tokenProviderName');
|
||||
});
|
||||
|
||||
test('server selection separates existing servers from cloud provisioning', function (string $viewPath) {
|
||||
$view = file_get_contents(resource_path($viewPath));
|
||||
|
||||
expect($view)
|
||||
->toContain('Add a server')
|
||||
->toContain('IP address or domain')
|
||||
->toContain('Provision a server')
|
||||
->and(strpos($view, 'Add a server'))->toBeLessThan(strpos($view, 'Provision a server'));
|
||||
})->with([
|
||||
'new server page' => 'views/livewire/server/create.blade.php',
|
||||
'onboarding' => 'views/livewire/boarding/index.blade.php',
|
||||
]);
|
||||
|
||||
test('new server sections have vertical spacing', function () {
|
||||
$view = file_get_contents(resource_path('views/livewire/server/create.blade.php'));
|
||||
|
||||
expect($view)->toContain('<div class="application-settings-form flex flex-col gap-6">');
|
||||
});
|
||||
|
||||
test('server selection uses the provider logos', function () {
|
||||
$newServerView = file_get_contents(resource_path('views/livewire/server/create.blade.php'));
|
||||
$onboardingView = file_get_contents(resource_path('views/livewire/boarding/index.blade.php'));
|
||||
|
||||
expect($newServerView)
|
||||
->toContain('src="https://www.vultr.com/media/logo_ondark.svg"')
|
||||
->toContain("src=\"{{ asset('svgs/hetzner.svg') }}\"")
|
||||
->and($onboardingView)
|
||||
->toContain('src="https://www.vultr.com/media/logo_ondark.svg"')
|
||||
->toContain("src=\"{{ asset('svgs/hetzner.svg') }}\"");
|
||||
});
|
||||
|
||||
test('new server cards do not show method badges', function () {
|
||||
$view = file_get_contents(resource_path('views/livewire/server/create.blade.php'));
|
||||
|
||||
expect($view)
|
||||
->not->toContain("\n Manual\n")
|
||||
->not->toContain("\n Provider\n");
|
||||
});
|
||||
@@ -6,7 +6,11 @@ test('server creation keeps private key actions together and advanced options co
|
||||
expect($view)
|
||||
->toContain('class="flex items-end gap-3"')
|
||||
->toContain('<x-forms.collapsible class="mt-5 border-t border-neutral-200 pt-4 dark:border-white/[0.08]"')
|
||||
->toContain('<x-forms.select id="is_build_server"')
|
||||
->toContain('label="Use as a dedicated build server"')
|
||||
->toContain('<option value="0">No</option>')
|
||||
->toContain('<option value="1">Yes</option>')
|
||||
->not->toContain('<x-forms.checkbox id="is_build_server"')
|
||||
->toContain('helper="Build servers compile applications but do not host deployments. Enabling this makes the server build-only."');
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user