better boarding flow

This commit is contained in:
Andras Bacsai 2023-08-30 14:46:51 +02:00
parent 248863cf16
commit 6f00740f67
13 changed files with 153 additions and 80 deletions

View File

@ -7,15 +7,19 @@ use App\Models\GithubApp;
use App\Models\Project; use App\Models\Project;
use App\Models\StandaloneDocker; use App\Models\StandaloneDocker;
use App\Models\SwarmDocker; use App\Models\SwarmDocker;
use App\Traits\SaveFromRedirect;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Livewire\Component; use Livewire\Component;
use Route;
class GithubPrivateRepository extends Component class GithubPrivateRepository extends Component
{ {
use SaveFromRedirect;
public $current_step = 'github_apps'; public $current_step = 'github_apps';
public $github_apps; public $github_apps;
public GithubApp $github_app; public GithubApp $github_app;
public $parameters; public $parameters;
public $currentRoute;
public $query; public $query;
public $type; public $type;
@ -36,14 +40,30 @@ class GithubPrivateRepository extends Component
public string|null $publish_directory = null; public string|null $publish_directory = null;
protected int $page = 1; protected int $page = 1;
// public function saveFromRedirect(string $route, ?Collection $parameters = null){
// session()->forget('from');
// if (!$parameters || $parameters->count() === 0) {
// $parameters = $this->parameters;
// }
// $parameters = collect($parameters) ?? collect([]);
// $queries = collect($this->query) ?? collect([]);
// $parameters = $parameters->merge($queries);
// session(['from'=> [
// 'back'=> $this->currentRoute,
// 'route' => $route,
// 'parameters' => $parameters
// ]]);
// return redirect()->route($route);
// }
public function mount() public function mount()
{ {
$this->currentRoute = Route::currentRouteName();
$this->parameters = get_route_parameters(); $this->parameters = get_route_parameters();
$this->query = request()->query(); $this->query = request()->query();
$this->repositories = $this->branches = collect(); $this->repositories = $this->branches = collect();
$this->github_apps = GithubApp::private(); $this->github_apps = GithubApp::private();
} }
public function loadRepositories($github_app_id) public function loadRepositories($github_app_id)
{ {
$this->repositories = collect(); $this->repositories = collect();

View File

@ -39,7 +39,6 @@ class Change extends Component
{ {
if (is_cloud() && !isDev()) { if (is_cloud() && !isDev()) {
$this->webhook_endpoint = config('app.url'); $this->webhook_endpoint = config('app.url');
ray($this->webhook_endpoint);
} else { } else {
$this->webhook_endpoint = $this->ipv4; $this->webhook_endpoint = $this->ipv4;
$this->is_system_wide = $this->github_app->is_system_wide; $this->is_system_wide = $this->github_app->is_system_wide;

View File

@ -42,6 +42,9 @@ class Create extends Component
'is_system_wide' => $this->is_system_wide, 'is_system_wide' => $this->is_system_wide,
'team_id' => currentTeam()->id, 'team_id' => currentTeam()->id,
]); ]);
if (session('from')) {
session(['from' => session('from') + ['source_id' => $github_app->id]]);
}
redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]); redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler(err: $e, that: $this); return general_error_handler(err: $e, that: $this);

View File

@ -0,0 +1,25 @@
<?php
namespace App\Traits;
use Illuminate\Support\Collection;
trait SaveFromRedirect
{
public function saveFromRedirect(string $route, ?Collection $parameters = null)
{
session()->forget('from');
if (!$parameters || $parameters->count() === 0) {
$parameters = $this->parameters;
}
$parameters = collect($parameters) ?? collect([]);
$queries = collect($this->query) ?? collect([]);
$parameters = $parameters->merge($queries);
session(['from' => [
'back' => $this->currentRoute,
'route' => $route,
'parameters' => $parameters
]]);
return redirect()->route($route);
}
}

View File

@ -15,7 +15,7 @@ class Button extends Component
public bool $disabled = false, public bool $disabled = false,
public bool $isModal = false, public bool $isModal = false,
public bool $noStyle = false, public bool $noStyle = false,
public string|null $modalId = null, public ?string $modalId = null,
public string $defaultClass = "btn btn-primary btn-sm font-normal text-white normal-case no-animation rounded border-none" public string $defaultClass = "btn btn-primary btn-sm font-normal text-white normal-case no-animation rounded border-none"
) { ) {
if ($this->noStyle) { if ($this->noStyle) {
@ -23,9 +23,6 @@ class Button extends Component
} }
} }
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string public function render(): View|Closure|string
{ {
return view('components.forms.button'); return view('components.forms.button');

View File

@ -7,6 +7,7 @@ use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
use Illuminate\Database\QueryException; use Illuminate\Database\QueryException;
use Illuminate\Mail\Message; use Illuminate\Mail\Message;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
@ -52,8 +53,7 @@ function showBoarding(): bool
} }
function refreshSession(): void function refreshSession(): void
{ {
$team = currentTeam(); session(['currentTeam' => currentTeam()]);
session(['currentTeam' => $team]);
} }
function general_error_handler(Throwable | null $err = null, $that = null, $isJson = false, $customErrorMessage = null): mixed function general_error_handler(Throwable | null $err = null, $that = null, $isJson = false, $customErrorMessage = null): mixed
{ {
@ -259,3 +259,4 @@ function send_user_an_email(MailMessage $mail, string $email): void
->html((string) $mail->render()) ->html((string) $mail->render())
); );
} }

View File

@ -408,6 +408,12 @@ const magicActions = [{
name: 'Goto: Switch Teams', name: 'Goto: Switch Teams',
icon: 'goto', icon: 'goto',
sequence: ['main', 'redirect'] sequence: ['main', 'redirect']
},
{
id: 23,
name: 'Goto: Boarding process',
icon: 'goto',
sequence: ['main', 'redirect']
} }
] ]
const initialState = { const initialState = {
@ -635,6 +641,9 @@ async function redirect() {
case 22: case 22:
targetUrl.pathname = `/team` targetUrl.pathname = `/team`
break; break;
case 23:
targetUrl.pathname = `/boarding`
break;
} }
window.location.href = targetUrl; window.location.href = targetUrl;
} }

View File

@ -5,8 +5,8 @@
<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-type')">Get Started <x-forms.button class="justify-center box" wire:click="$set('currentState', 'select-server-type')">Get Started
</div> </x-forms.button>
</div> </div>
@endif @endif
</div> </div>
@ -18,10 +18,10 @@
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="setServerType('localhost')">Localhost <x-forms.button class="justify-center box" wire:target="setServerType('localhost')" wire:click="setServerType('localhost')">Localhost
</div> </x-forms.button>
<div class="justify-center box" wire:click="setServerType('remote')">Remote Server <x-forms.button class="justify-center box" wire:target="setServerType('remote')" wire:click="setServerType('remote')">Remote Server
</div> </x-forms.button>
</x-slot:actions> </x-slot:actions>
<x-slot:explanation> <x-slot:explanation>
<p>Servers are the main building blocks, as they will host your applications, databases, <p>Servers are the main building blocks, as they will host your applications, databases,
@ -42,10 +42,10 @@
Do you have your own SSH Private Key? Do you have your own SSH Private Key?
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<div class="justify-center box" wire:click="setPrivateKey('own')">Yes <x-forms.button class="justify-center box" wire:target="setPrivateKey('own')" wire:click="setPrivateKey('own')">Yes
</div> </x-forms.button>
<div class="justify-center box" wire:click="setPrivateKey('create')">No (create one for me) <x-forms.button class="justify-center box" wire:target="setPrivateKey('create')" wire:click="setPrivateKey('create')">No (create one for me)
</div> </x-forms.button>
@if (count($privateKeys) > 0) @if (count($privateKeys) > 0)
<form wire:submit.prevent='selectExistingPrivateKey' class="flex flex-col w-full gap-4 pr-10"> <form wire:submit.prevent='selectExistingPrivateKey' class="flex flex-col w-full gap-4 pr-10">
<x-forms.select label="Existing SSH Keys" id='selectedExistingPrivateKey'> <x-forms.select label="Existing SSH Keys" id='selectedExistingPrivateKey'>
@ -76,8 +76,8 @@
There are already servers available for your Team. Do you want to use one of them? There are already servers available for your Team. Do you want to use one of them?
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<div class="justify-center box" wire:click="createNewServer">No (create a new one) <x-forms.button class="justify-center box" wire:click="createNewServer">No (create one for me)
</div> </x-forms.button>
<div> <div>
<form wire:submit.prevent='selectExistingServer' class="flex flex-col w-full gap-4 lg:w-96"> <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'> <x-forms.select label="Existing servers" class="w-96" id='selectedExistingServer'>
@ -182,9 +182,9 @@
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?
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<div class="justify-center box" wire:click="installDocker" onclick="installDocker.showModal()"> <x-forms.button class="justify-center box" wire:click="installDocker" onclick="installDocker.showModal()">
Let's do Let's do
it!</div> it!</x-forms.button>
</x-slot:actions> </x-slot:actions>
<x-slot:explanation> <x-slot:explanation>
<p>This will install the latest Docker Engine on your server, configure a few things to be able <p>This will install the latest Docker Engine on your server, configure a few things to be able
@ -233,7 +233,7 @@
@endif @endif
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<div class="justify-center box" wire:click="createNewProject">Let's create a new one!</div> <x-forms.button class="justify-center box" wire:click="createNewProject">Let's create a new one!</x-forms.button>
<div> <div>
@if (count($projects) > 0) @if (count($projects) > 0)
<form wire:submit.prevent='selectExistingProject' <form wire:submit.prevent='selectExistingProject'

View File

@ -1,10 +1,9 @@
<div> <div>
<div class="flex items-end gap-2"> <div class="flex items-end gap-2">
<h1>Create a new Application</h1> <h1>Create a new Application</h1>
<a href="{{ route('source.new') }}"><x-forms.button class="group-hover:text-white"> <x-forms.button wire:click="saveFromRedirect('source.new')" class="group-hover:text-white">
+ Add New GitHub App + Add New GitHub App
</x-forms.button> </x-forms.button>
</a>
</div> </div>
<div class="pb-4 ">Deploy any public or private git repositories through a GitHub App.</div> <div class="pb-4 ">Deploy any public or private git repositories through a GitHub App.</div>
@if ($github_apps->count() !== 0) @if ($github_apps->count() !== 0)
@ -25,7 +24,7 @@
{{ $ghapp->name }} {{ $ghapp->name }}
</div> </div>
<div>{{ $ghapp->http_url }}</div> <div>{{ $ghapp->http_url }}</div>
<span wire:target="loadRepositories" wire:loading.delay <span wire:target="loadRepositories({{ $ghapp->id }})" wire:loading.delay
class="loading loading-xs text-warning loading-spinner"></span> class="loading loading-xs text-warning loading-spinner"></span>
</div> </div>
</div> </div>
@ -39,7 +38,7 @@
</div> </div>
<div class="text-xs text-gray-400 group-hover:text-white"> <div class="text-xs text-gray-400 group-hover:text-white">
{{ data_get($ghapp, 'html_url') }}</div> {{ data_get($ghapp, 'html_url') }}</div>
<span wire:target="loadRepositories" wire:loading.delay <span wire:target="loadRepositories({{ $ghapp->id }})" wire:loading.delay
class="">Loading...</span> class="">Loading...</span>
</div> </div>
</div> </div>

View File

@ -70,7 +70,6 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@endif @endif
@if ($current_step === 'servers') @if ($current_step === 'servers')
@ -79,7 +78,7 @@
<li class="step step-secondary">Select a Server</li> <li class="step step-secondary">Select a Server</li>
<li class="step">Select a Destination</li> <li class="step">Select a Destination</li>
</ul> </ul>
<div class="grid grid-cols-3 gap-2 text-left "> <div class="flex flex-col justify-center gap-2 text-left xl:flex-row">
@forelse($servers as $server) @forelse($servers as $server)
<div class="box group" <div class="box group"
wire:click="set_server({{ $server }})"> wire:click="set_server({{ $server }})">
@ -108,7 +107,7 @@
<li class="step step-secondary">Select a Server</li> <li class="step step-secondary">Select a Server</li>
<li class="step step-secondary">Select a Destination</li> <li class="step step-secondary">Select a Destination</li>
</ul> </ul>
<div class="grid grid-cols-3 gap-2 text-left "> <div class="flex flex-col justify-center gap-2 text-left xl:flex-row">
@foreach ($standaloneDockers as $standaloneDocker) @foreach ($standaloneDockers as $standaloneDocker)
<div class="box group" <div class="box group"
wire:click="set_destination('{{ $standaloneDocker->uuid }}')"> wire:click="set_destination('{{ $standaloneDocker->uuid }}')">

View File

@ -11,17 +11,15 @@
@if ($github_app->app_id) @if ($github_app->app_id)
<x-forms.button type="submit">Save</x-forms.button> <x-forms.button type="submit">Save</x-forms.button>
<a href="{{ get_installation_path($github_app) }}"> @if (data_get($github_app, 'installation_id'))
<x-forms.button> <a href="{{ get_installation_path($github_app) }}">
@if ($github_app->installation_id) <x-forms.button>
Update Repositories Update Repositories
<x-external-link /> <x-external-link />
@else </x-forms.button>
Install Repositories </a>
<x-external-link /> @endif
@endif
</x-forms.button>
</a>
@else @else
<x-forms.button disabled type="submit">Save</x-forms.button> <x-forms.button disabled type="submit">Save</x-forms.button>
@endif @endif
@ -30,8 +28,21 @@
</x-forms.button> </x-forms.button>
</div> </div>
</div> </div>
<div class="subtitle ">Your Private GitHub App for private repositories.</div> <div class="subtitle">Your Private GitHub App for private repositories.</div>
@if ($github_app->app_id) @if (data_get($github_app, 'app_id'))
@if (!data_get($github_app, 'installation_id'))
<div class="mb-10 rounded alert alert-warning">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<span>You must complete this step before you can use this source!</span>
</div>
<a class="justify-center box" href="{{ get_installation_path($github_app) }}">
Install Repositories on GitHub
</a>
@else
<div class="w-48"> <div class="w-48">
<x-forms.checkbox label="System Wide?" <x-forms.checkbox label="System Wide?"
helper="If checked, this GitHub App will be available for everyone in this Coolify instance." helper="If checked, this GitHub App will be available for everyone in this Coolify instance."
@ -64,17 +75,19 @@
<x-forms.input id="github_app.client_secret" label="Client Secret" type="password" /> <x-forms.input id="github_app.client_secret" label="Client Secret" type="password" />
<x-forms.input id="github_app.webhook_secret" label="Webhook Secret" type="password" /> <x-forms.input id="github_app.webhook_secret" label="Webhook Secret" type="password" />
</div> </div>
@endif
@else @else
<div class="mb-10 rounded alert alert-warning">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<span>You must complete this step before you can use this source!</span>
</div>
<form class="flex gap-4"> <form class="flex gap-4">
<div class="flex items-end gap-2"> <h2>Register a GitHub App</h2>
<h3>Register a GitHub App</h3>
<x-forms.button
x-on:click.prevent="createGithubApp('{{ $webhook_endpoint }}','{{ $preview_deployment_permissions }}')">
Register a
GitHub
Application
</x-forms.button>
</div>
<div class="pt-1 pb-2 ">You need to register a GitHub App before using this source.</div> <div class="pt-1 pb-2 ">You need to register a GitHub App before using this source.</div>
<div class="pt-2 pb-10"> <div class="pt-2 pb-10">
@if (!is_cloud() || isDev()) @if (!is_cloud() || isDev())
@ -90,8 +103,20 @@
@if ($fqdn) @if ($fqdn)
<option value="{{ $fqdn }}">Use {{ $fqdn }}</option> <option value="{{ $fqdn }}">Use {{ $fqdn }}</option>
@endif @endif
@if (config('app.url'))
<option value="{{ config('app.url') }}">Use {{ config('app.url') }}</option>
@endif
</x-forms.select> </x-forms.select>
<x-forms.button
x-on:click.prevent="createGithubApp('{{ $webhook_endpoint }}','{{ $preview_deployment_permissions }}')">
Register
</x-forms.button>
</div> </div>
@else
<x-forms.button
x-on:click.prevent="createGithubApp('{{ $webhook_endpoint }}','{{ $preview_deployment_permissions }}')">
Register Now
</x-forms.button>
@endif @endif
<div class="flex flex-col gap-2 pt-4"> <div class="flex flex-col gap-2 pt-4">
<x-forms.checkbox disabled instantSave id="default_permissions" label="Default Permissions" <x-forms.checkbox disabled instantSave id="default_permissions" label="Default Permissions"
@ -102,29 +127,6 @@
</div> </div>
</div> </div>
</form> </form>
<div class="flex gap-2">
<x-forms.input id="github_app.name" label="App Name" disabled />
<x-forms.input id="github_app.organization" label="Organization"
placeholder="If empty, personal user will be used" disabled />
</div>
<div class="flex gap-2">
<x-forms.input id="github_app.html_url" label="HTML Url" disabled />
<x-forms.input id="github_app.api_url" label="API Url" disabled />
</div>
<div class="flex gap-2">
@if ($github_app->html_url === 'https://github.com')
<x-forms.input id="github_app.custom_user" label="User" disabled />
<x-forms.input type="number" id="github_app.custom_port" label="Port" disabled />
@else
<x-forms.input id="github_app.custom_user" label="User" required />
<x-forms.input type="number" id="github_app.custom_port" label="Port" required />
@endif
</div>
@if (!is_cloud() || isDev())
<x-forms.checkbox
helper="If checked, this GitHub App will be available for everyone in this Coolify instance."
label="System Wide?" disabled id="is_system_wide" />
@endif
<script> <script>
function createGithubApp(webhook_endpoint, preview_deployment_permissions) { function createGithubApp(webhook_endpoint, preview_deployment_permissions) {
const { const {

View File

@ -7,23 +7,22 @@
<a class="flex gap-4 text-center hover:no-underline box group" <a class="flex gap-4 text-center hover:no-underline box group"
href="{{ route('source.github.show', ['github_app_uuid' => data_get($source, 'uuid')]) }}"> href="{{ route('source.github.show', ['github_app_uuid' => data_get($source, 'uuid')]) }}">
<x-git-icon class="text-white w-9 h-9" git="{{ $source->getMorphClass() }}" /> <x-git-icon class="text-white w-9 h-9" git="{{ $source->getMorphClass() }}" />
<div class="group-hover:text-white"> <div class="text-left group-hover:text-white">
<div>{{ $source->name }}</div> <div>{{ $source->name }}</div>
@if (is_null($source->app_id)) @if (is_null($source->app_id))
<span class="text-error">Not registered</span> <span class="text-error">Configuration is not finished</span>
@endif @endif
</div> </div>
</a> </a>
@endif @endif
@if ($source->getMorphClass() === 'App\Models\GitlabApp') @if ($source->getMorphClass() === 'App\Models\GitlabApp')
<a class="flex gap-4 text-center hover:no-underline box group" <a class="flex gap-4 text-center hover:no-underline box group"
href="{{ route('source.gitlab.show', ['gitlab_app_uuid' => data_get($source, 'uuid')]) }}"> href="{{ route('source.gitlab.show', ['gitlab_app_uuid' => data_get($source, 'uuid')]) }}">
<x-git-icon class="text-white w-9 h-9" git="{{ $source->getMorphClass() }}" /> <x-git-icon class="text-white w-9 h-9" git="{{ $source->getMorphClass() }}" />
<div class="group-hover:text-white"> <div class="text-left group-hover:text-white">
<div>{{ $source->name }}</div> <div>{{ $source->name }}</div>
@if (is_null($source->app_id)) @if (is_null($source->app_id))
<span class="text-error">Not registered</span> <span class="text-error">Configuration is not finished</span>
@endif @endif
</div> </div>
</a> </a>

View File

@ -145,6 +145,26 @@ Route::middleware(['auth'])->group(function () {
if ($settings->public_ipv6) { if ($settings->public_ipv6) {
$ipv6 = 'http://' . $settings->public_ipv6 . ':' . config('app.port'); $ipv6 = 'http://' . $settings->public_ipv6 . ':' . config('app.port');
} }
if ($github_app->installation_id && session('from')) {
$source_id = data_get(session('from'), 'source_id');
if (!$source_id || $github_app->id !== $source_id) {
session()->forget('from');
} else {
$parameters = data_get(session('from'), 'parameters');
$back = data_get(session('from'), 'back');
$environment_name = data_get($parameters, 'environment_name');
$project_uuid = data_get($parameters, 'project_uuid');
$type = data_get($parameters, 'type');
$destination = data_get($parameters, 'destination');
session()->forget('from');
return redirect()->route($back, [
'environment_name' => $environment_name,
'project_uuid' => $project_uuid,
'type' => $type,
'destination' => $destination,
]);
}
}
return view('source.github.show', [ return view('source.github.show', [
'github_app' => $github_app, 'github_app' => $github_app,
'name' => $name, 'name' => $name,