improve boarding

This commit is contained in:
Andras Bacsai 2023-08-30 11:06:44 +02:00
parent 5eb41e1a15
commit 97d48823dd
7 changed files with 353 additions and 200 deletions

View File

@ -43,6 +43,7 @@ class ProjectController extends Controller
{ {
$type = request()->query('type'); $type = request()->query('type');
$destination_uuid = request()->query('destination'); $destination_uuid = request()->query('destination');
$server = requesT()->query('server');
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); $project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) { if (!$project) {
@ -59,6 +60,9 @@ class ProjectController extends Controller
'environment_name' => $environment->name, 'environment_name' => $environment->name,
'database_uuid' => $standalone_postgresql->uuid, 'database_uuid' => $standalone_postgresql->uuid,
]); ]);
}
if ($server) {
} }
return view('project.new', [ return view('project.new', [
'type' => $type 'type' => $type

View File

@ -6,7 +6,7 @@ use App\Actions\Server\InstallDocker;
use App\Models\PrivateKey; use App\Models\PrivateKey;
use App\Models\Project; use App\Models\Project;
use App\Models\Server; use App\Models\Server;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Collection;
use Livewire\Component; use Livewire\Component;
class Index extends Component class Index extends Component
@ -22,6 +22,8 @@ class Index extends Component
public ?string $privateKeyDescription = null; public ?string $privateKeyDescription = null;
public ?PrivateKey $createdPrivateKey = null; public ?PrivateKey $createdPrivateKey = null;
public ?Collection $servers = null;
public ?int $selectedExistingServer = null;
public ?string $remoteServerName = null; public ?string $remoteServerName = null;
public ?string $remoteServerDescription = null; public ?string $remoteServerDescription = null;
public ?string $remoteServerHost = null; public ?string $remoteServerHost = null;
@ -29,6 +31,8 @@ class Index extends Component
public ?string $remoteServerUser = 'root'; public ?string $remoteServerUser = 'root';
public ?Server $createdServer = null; public ?Server $createdServer = null;
public Collection|array $projects = [];
public ?int $selectedExistingProject = null;
public ?Project $createdProject = null; public ?Project $createdProject = null;
public function mount() public function mount()
@ -66,25 +70,62 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
refreshSession(); refreshSession();
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }
public function setServer(string $type)
public function setServerType(string $type)
{ {
if ($type === 'localhost') { if ($type === 'localhost') {
$this->createdServer = Server::find(0); $this->createdServer = Server::find(0);
if (!$this->createdServer) { if (!$this->createdServer) {
return $this->emit('error', 'Localhost server is not found. Something went wrong during installation. Please try to reinstall or contact support.'); return $this->emit('error', 'Localhost server is not found. Something went wrong during installation. Please try to reinstall or contact support.');
} }
$this->currentState = 'select-proxy'; return $this->validateServer();
} elseif ($type === 'remote') { } elseif ($type === 'remote') {
$this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->get(); $this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
if ($this->privateKeys->count() > 0) {
$this->selectedExistingPrivateKey = $this->privateKeys->first()->id;
}
$this->servers = Server::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
if ($this->servers->count() > 0) {
$this->selectedExistingServer = $this->servers->first()->id;
$this->currentState = 'select-existing-server';
return;
}
$this->currentState = 'private-key'; $this->currentState = 'private-key';
} }
} }
public function selectExistingServer()
{
$this->createdServer = Server::find($this->selectedExistingServer);
if (!$this->createdServer) {
$this->emit('error', 'Server is not found.');
$this->currentState = 'private-key';
return;
}
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
$this->validateServer();
$this->getProxyType();
$this->getProjects();
}
public function getProxyType() {
$proxyTypeSet = $this->createdServer->proxy->type;
if (!$proxyTypeSet) {
$this->currentState = 'select-proxy';
return;
}
$this->getProjects();
}
public function selectExistingPrivateKey() public function selectExistingPrivateKey()
{ {
ray($this->selectedExistingPrivateKey); $this->currentState = 'create-server';
}
public function createNewServer()
{
$this->selectedExistingServer = null;
$this->currentState = 'private-key';
} }
public function setPrivateKey(string $type) public function setPrivateKey(string $type)
{ {
$this->selectedExistingPrivateKey = null;
$this->privateKeyType = $type; $this->privateKeyType = $type;
if ($type === 'create' && !isDev()) { if ($type === 'create' && !isDev()) {
$this->createNewPrivateKey(); $this->createNewPrivateKey();
@ -123,11 +164,12 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
'private_key_id' => $this->createdPrivateKey->id, 'private_key_id' => $this->createdPrivateKey->id,
'team_id' => currentTeam()->id 'team_id' => currentTeam()->id
]); ]);
$this->validateServer();
}
public function validateServer() {
try { try {
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->createdServer); ['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->createdServer);
if (!$uptime) { if (!$uptime) {
$this->createdServer->delete();
$this->createdPrivateKey->delete();
throw new \Exception('Server is not reachable.'); throw new \Exception('Server is not reachable.');
} else { } else {
$this->createdServer->settings->update([ $this->createdServer->settings->update([
@ -135,11 +177,14 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
]); ]);
$this->emit('success', 'Server is reachable.'); $this->emit('success', 'Server is reachable.');
} }
if ($dockerVersion) { ray($dockerVersion, $uptime);
if (!$dockerVersion) {
$this->emit('error', 'Docker is not installed on the server.'); $this->emit('error', 'Docker is not installed on the server.');
$this->currentState = 'install-docker'; $this->currentState = 'install-docker';
return; return;
} }
$this->getProxyType();
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this); return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this);
} }
@ -153,13 +198,25 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
public function selectProxy(string|null $proxyType = null) public function selectProxy(string|null $proxyType = null)
{ {
if (!$proxyType) { if (!$proxyType) {
return $this->currentState = 'create-project'; return $this->getProjects();
} }
$this->createdServer->proxy->type = $proxyType; $this->createdServer->proxy->type = $proxyType;
$this->createdServer->proxy->status = 'exited'; $this->createdServer->proxy->status = 'exited';
$this->createdServer->save(); $this->createdServer->save();
$this->getProjects();
}
public function getProjects() {
$this->projects = Project::ownedByCurrentTeam(['name'])->get();
if ($this->projects->count() > 0) {
$this->selectedExistingProject = $this->projects->first()->id;
}
$this->currentState = 'create-project'; $this->currentState = 'create-project';
} }
public function selectExistingProject() {
$this->createdProject = Project::find($this->selectedExistingProject);
$this->currentState = 'create-resource';
}
public function createNewProject() public function createNewProject()
{ {
$this->createdProject = Project::create([ $this->createdProject = Project::create([

View File

@ -5,10 +5,12 @@ namespace App\Http\Livewire\Project\New;
use App\Models\Server; use App\Models\Server;
use Countable; use Countable;
use Livewire\Component; use Livewire\Component;
use Route;
class Select extends Component class Select extends Component
{ {
public $current_step = 'type'; public $current_step = 'type';
public ?int $server = null;
public string $type; public string $type;
public string $server_id; public string $server_id;
public string $destination_uuid; public string $destination_uuid;
@ -16,6 +18,9 @@ class Select extends Component
public $destinations = []; public $destinations = [];
public array $parameters; public array $parameters;
protected $queryString = [
'server',
];
public function mount() public function mount()
{ {
$this->parameters = get_route_parameters(); $this->parameters = get_route_parameters();
@ -31,6 +36,12 @@ class Select extends Component
$this->set_destination($server->destinations()->first()->uuid); $this->set_destination($server->destinations()->first()->uuid);
} }
} }
if (!is_null($this->server )) {
$foundServer = $this->servers->where('id', $this->server)->first();
if ($foundServer) {
return $this->set_server($foundServer);
}
}
$this->current_step = 'servers'; $this->current_step = 'servers';
} }

View File

@ -6,7 +6,7 @@ use App\Models\Server;
use Livewire\Component; use Livewire\Component;
use Masmerise\Toaster\Toaster; use Masmerise\Toaster\Toaster;
class PrivateKey extends Component class ShowPrivateKey extends Component
{ {
public Server $server; public Server $server;
public $privateKeys; public $privateKeys;

View File

@ -1,23 +1,26 @@
@php use App\Enums\ProxyTypes; @endphp @php use App\Enums\ProxyTypes; @endphp
<div> <div>
<div>
@if ($currentState === 'welcome') @if ($currentState === 'welcome')
<h1 class="text-5xl font-bold">Welcome to Coolify</h1> <h1 class="text-5xl font-bold">Welcome to Coolify</h1>
<p class="py-6 text-xl text-center">Let me help you to set the basics.</p> <p class="py-6 text-xl text-center">Let me help you to set the basics.</p>
<div class="flex justify-center "> <div class="flex justify-center ">
<div class="justify-center box" wire:click="$set('currentState', 'select-server')">Get Started <div class="justify-center box" wire:click="$set('currentState', 'select-server-type')">Get Started
</div> </div>
</div> </div>
@endif @endif
@if ($currentState === 'select-server') </div>
<div>
@if ($currentState === 'select-server-type')
<x-boarding-step title="Server"> <x-boarding-step title="Server">
<x-slot:question> <x-slot:question>
Do you want to deploy your resources on your <x-highlighted text="Localhost" /> Do you want to deploy your resources on your <x-highlighted text="Localhost" />
or on a <x-highlighted text="Remote Server" />? or on a <x-highlighted text="Remote Server" />?
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<div class="justify-center box" wire:click="setServer('localhost')">Localhost <div class="justify-center box" wire:click="setServerType('localhost')">Localhost
</div> </div>
<div class="justify-center box" wire:click="setServer('remote')">Remote Server <div class="justify-center box" wire:click="setServerType('remote')">Remote Server
</div> </div>
</x-slot:actions> </x-slot:actions>
<x-slot:explanation> <x-slot:explanation>
@ -31,6 +34,8 @@
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>
@endif @endif
</div>
<div>
@if ($currentState === 'private-key') @if ($currentState === 'private-key')
<x-boarding-step title="SSH Key"> <x-boarding-step title="SSH Key">
<x-slot:question> <x-slot:question>
@ -41,14 +46,17 @@
</div> </div>
<div class="justify-center box" wire:click="setPrivateKey('create')">No (create one for me) <div class="justify-center box" wire:click="setPrivateKey('create')">No (create one for me)
</div> </div>
<form wire:submit.prevent='selectExistingPrivateKey'> @if (count($privateKeys) > 0)
<x-forms.select wire:model.defer='selectedExistingPrivateKey'> <form wire:submit.prevent='selectExistingPrivateKey' class="flex flex-col w-full gap-4 pr-10">
<x-forms.select label="Existing SSH Keys" id='selectedExistingPrivateKey'>
@foreach ($privateKeys as $privateKey) @foreach ($privateKeys as $privateKey)
<option value="{{ $privateKey->id }}">{{ $privateKey->name }}</option> <option wire:key="{{ $loop->index }}" value="{{ $privateKey->id }}">
{{ $privateKey->name }}</option>
@endforeach @endforeach
</x-forms.select> </x-forms.select>
<x-forms.button type="submit">Select this</x-forms.button> <x-forms.button type="submit">Use this SSH Key</x-forms.button>
</form> </form>
@endif
</x-slot:actions> </x-slot:actions>
<x-slot:explanation> <x-slot:explanation>
<p>SSH Keys are used to connect to a remote server through a secure shell, called SSH.</p> <p>SSH Keys are used to connect to a remote server through a secure shell, called SSH.</p>
@ -60,6 +68,39 @@
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>
@endif @endif
</div>
<div>
@if ($currentState === 'select-existing-server')
<x-boarding-step title="Select a server">
<x-slot:question>
There are already servers available for your Team. Do you want to use one of them?
</x-slot:question>
<x-slot:actions>
<div class="justify-center box" wire:click="createNewServer">No (create a new one)
</div>
<div>
<form wire:submit.prevent='selectExistingServer' class="flex flex-col w-full gap-4 lg:w-96">
<x-forms.select label="Existing servers" class="w-96" id='selectedExistingServer'>
@foreach ($servers as $server)
<option wire:key="{{ $loop->index }}" value="{{ $server->id }}">
{{ $server->name }}</option>
@endforeach
</x-forms.select>
<x-forms.button type="submit">Use this Server</x-forms.button>
</form>
</div>
</x-slot:actions>
<x-slot:explanation>
<p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p>
<p>You can use your own private key, or you can let Coolify to create one for you.</p>
<p>In both ways, you need to add the public version of your private key to the remote server's
<code>~/.ssh/authorized_keys</code> file.
</p>
</x-slot:explanation>
</x-boarding-step>
@endif
</div>
<div>
@if ($currentState === 'create-private-key') @if ($currentState === 'create-private-key')
<x-boarding-step title="Create Private Key"> <x-boarding-step title="Create Private Key">
<x-slot:question> <x-slot:question>
@ -69,8 +110,8 @@
<form wire:submit.prevent='savePrivateKey' class="flex flex-col w-full gap-4 pr-10"> <form wire:submit.prevent='savePrivateKey' class="flex flex-col w-full gap-4 pr-10">
<x-forms.input required placeholder="Choose a name for your Private Key. Could be anything." <x-forms.input required placeholder="Choose a name for your Private Key. Could be anything."
label="Name" id="privateKeyName" /> label="Name" id="privateKeyName" />
<x-forms.input placeholder="Description, so others will know more about this." label="Description" <x-forms.input placeholder="Description, so others will know more about this."
id="privateKeyDescription" /> label="Description" id="privateKeyDescription" />
<x-forms.textarea required placeholder="-----BEGIN OPENSSH PRIVATE KEY-----" label="Private Key" <x-forms.textarea required placeholder="-----BEGIN OPENSSH PRIVATE KEY-----" label="Private Key"
id="privateKey" /> id="privateKey" />
@if ($privateKeyType === 'create' && !isDev()) @if ($privateKeyType === 'create' && !isDev())
@ -90,6 +131,8 @@
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>
@endif @endif
</div>
<div>
@if ($currentState === 'create-server') @if ($currentState === 'create-server')
<x-boarding-step title="Create Server"> <x-boarding-step title="Create Server">
<x-slot:question> <x-slot:question>
@ -106,8 +149,8 @@
<div class="flex gap-2"> <div class="flex gap-2">
<x-forms.input required placeholder="Hostname or IP address" label="Hostname or IP Address" <x-forms.input required placeholder="Hostname or IP address" label="Hostname or IP Address"
id="remoteServerHost" /> id="remoteServerHost" />
<x-forms.input required placeholder="Port number of your server. Default is 22." label="Port" <x-forms.input required placeholder="Port number of your server. Default is 22."
id="remoteServerPort" /> label="Port" id="remoteServerPort" />
<x-forms.input required readonly <x-forms.input required readonly
placeholder="Username to connect to your server. Default is root." label="Username" placeholder="Username to connect to your server. Default is root." label="Username"
id="remoteServerUser" /> id="remoteServerUser" />
@ -121,7 +164,19 @@
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>
@endif @endif
</div>
<div>
@if ($currentState === 'install-docker') @if ($currentState === 'install-docker')
<x-modal modalId="installDocker">
<x-slot:modalBody>
<livewire:activity-monitor header="Docker Installation Logs" />
</x-slot:modalBody>
<x-slot:modalSubmit>
<x-forms.button onclick="installDocker.close()" type="submit">
Close
</x-forms.button>
</x-slot:modalSubmit>
</x-modal>
<x-boarding-step title="Install Docker"> <x-boarding-step title="Install Docker">
<x-slot:question> <x-slot:question>
Could not find Docker Engine on your server. Do you want me to install it for you? Could not find Docker Engine on your server. Do you want me to install it for you?
@ -137,6 +192,8 @@
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>
@endif @endif
</div>
<div>
@if ($currentState === 'select-proxy') @if ($currentState === 'select-proxy')
<x-boarding-step title="Select a Proxy"> <x-boarding-step title="Select a Proxy">
<x-slot:question> <x-slot:question>
@ -163,13 +220,34 @@
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>
@endif @endif
</div>
<div>
@if ($currentState === 'create-project') @if ($currentState === 'create-project')
<x-boarding-step title="Project"> <x-boarding-step title="Project">
<x-slot:question> <x-slot:question>
@if (count($projects) > 0)
You already have some projects. Do you want to use one of them or should I create a new one for
you?
@else
I will create an initial project for you. You can change all the details later on. I will create an initial project for you. You can change all the details later on.
@endif
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<div class="justify-center box" wire:click="createNewProject">Let's do it!</div> <div class="justify-center box" wire:click="createNewProject">Let's create a new one!</div>
<div>
@if (count($projects) > 0)
<form wire:submit.prevent='selectExistingProject'
class="flex flex-col w-full gap-4 lg:w-96">
<x-forms.select label="Existing projects" class="w-96" id='selectedExistingProject'>
@foreach ($projects as $project)
<option wire:key="{{ $loop->index }}" value="{{ $project->id }}">
{{ $project->name }}</option>
@endforeach
</x-forms.select>
<x-forms.button type="submit">Use this Project</x-forms.button>
</form>
@endif
</div>
</x-slot:actions> </x-slot:actions>
<x-slot:explanation> <x-slot:explanation>
<p>Projects are bound together several resources into one virtual group. There are no <p>Projects are bound together several resources into one virtual group. There are no
@ -179,6 +257,8 @@
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>
@endif @endif
</div>
<div>
@if ($currentState === 'create-resource') @if ($currentState === 'create-resource')
<x-boarding-step title="Resources"> <x-boarding-step title="Resources">
<x-slot:question> <x-slot:question>
@ -193,6 +273,7 @@
</x-slot:explanation> </x-slot:explanation>
</x-boarding-step> </x-boarding-step>
@endif @endif
</div>
<div class="flex justify-center gap-2 pt-4"> <div class="flex justify-center gap-2 pt-4">
<a wire:click='skipBoarding'>Skip boarding process</a> <a wire:click='skipBoarding'>Skip boarding process</a>
<a wire:click='restartBoarding'>Restart boarding process</a> <a wire:click='restartBoarding'>Restart boarding process</a>

View File

@ -1,4 +1,4 @@
<x-layout> <x-layout>
<x-server.navbar :server="$server" /> <x-server.navbar :server="$server" />
<livewire:server.private-key :server="$server" :privateKeys="$privateKeys" /> <livewire:server.show-private-key :server="$server" :privateKeys="$privateKeys" />
</x-layout> </x-layout>