Update UI elements and text content

This commit is contained in:
Andras Bacsai 2024-03-20 15:46:59 +01:00
parent 6b49d32102
commit fafc4fb71e
20 changed files with 576 additions and 539 deletions

View File

@ -2,22 +2,27 @@
namespace App\Livewire\Boarding; namespace App\Livewire\Boarding;
use App\Actions\Server\InstallDocker;
use App\Enums\ProxyTypes; use App\Enums\ProxyTypes;
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 App\Models\Team; use App\Models\Team;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Livewire\Attributes\Url;
use Livewire\Component; use Livewire\Component;
class Index extends Component class Index extends Component
{ {
protected $listeners = ['serverInstalled' => 'validateServer']; protected $listeners = ['serverInstalled' => 'validateServer'];
public string $currentState = 'welcome';
#[Url()]
public string $state = 'welcome';
#[Url()]
public ?string $selectedServerType = null; public ?string $selectedServerType = null;
public ?Collection $privateKeys = null; public ?Collection $privateKeys = null;
#[Url()]
public ?int $selectedExistingPrivateKey = null; public ?int $selectedExistingPrivateKey = null;
public ?string $privateKeyType = null; public ?string $privateKeyType = null;
public ?string $privateKey = null; public ?string $privateKey = null;
@ -27,6 +32,8 @@ class Index extends Component
public ?PrivateKey $createdPrivateKey = null; public ?PrivateKey $createdPrivateKey = null;
public ?Collection $servers = null; public ?Collection $servers = null;
#[Url()]
public ?int $selectedExistingServer = null; public ?int $selectedExistingServer = null;
public ?string $remoteServerName = null; public ?string $remoteServerName = null;
public ?string $remoteServerDescription = null; public ?string $remoteServerDescription = null;
@ -38,7 +45,9 @@ class Index extends Component
public ?Server $createdServer = null; public ?Server $createdServer = null;
public Collection $projects; public Collection $projects;
public ?int $selectedExistingProject = null;
#[Url()]
public ?int $selectedProject = null;
public ?Project $createdProject = null; public ?Project $createdProject = null;
public bool $dockerInstallationStarted = false; public bool $dockerInstallationStarted = false;
@ -62,13 +71,33 @@ public function mount()
$this->remoteServerDescription = 'Created by Coolify'; $this->remoteServerDescription = 'Created by Coolify';
$this->remoteServerHost = 'coolify-testing-host'; $this->remoteServerHost = 'coolify-testing-host';
} }
if ($this->state === 'create-project') {
$this->getProjects();
}
if ($this->state === 'create-resource') {
$this->selectExistingServer();
$this->selectExistingProject();
}
if ($this->state === 'private-key') {
$this->setServerType('remote');
}
if ($this->state === 'create-server') {
$this->selectExistingPrivateKey();
}
if ($this->state === 'validate-server') {
$this->selectExistingServer();
}
if ($this->state === 'select-existing-server') {
$this->selectExistingServer();
}
} }
public function explanation() public function explanation()
{ {
if (isCloud()) { if (isCloud()) {
return $this->setServerType('remote'); return $this->setServerType('remote');
} }
$this->currentState = 'select-server-type'; $this->state = 'select-server-type';
} }
public function restartBoarding() public function restartBoarding()
@ -89,6 +118,7 @@ public function setServerType(string $type)
$this->selectedServerType = $type; $this->selectedServerType = $type;
if ($this->selectedServerType === 'localhost') { if ($this->selectedServerType === 'localhost') {
$this->createdServer = Server::find(0); $this->createdServer = Server::find(0);
$this->selectedExistingServer = 0;
if (!$this->createdServer) { if (!$this->createdServer) {
return $this->dispatch('error', 'Localhost server is not found. Something went wrong during installation. Please try to reinstall or contact support.'); return $this->dispatch('error', 'Localhost server is not found. Something went wrong during installation. Please try to reinstall or contact support.');
} }
@ -106,10 +136,10 @@ public function setServerType(string $type)
$this->servers = Server::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get(); $this->servers = Server::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
if ($this->servers->count() > 0) { if ($this->servers->count() > 0) {
$this->selectedExistingServer = $this->servers->first()->id; $this->selectedExistingServer = $this->servers->first()->id;
$this->currentState = 'select-existing-server'; $this->state = 'select-existing-server';
return; return;
} }
$this->currentState = 'private-key'; $this->state = 'private-key';
} }
} }
public function selectExistingServer() public function selectExistingServer()
@ -117,12 +147,12 @@ public function selectExistingServer()
$this->createdServer = Server::find($this->selectedExistingServer); $this->createdServer = Server::find($this->selectedExistingServer);
if (!$this->createdServer) { if (!$this->createdServer) {
$this->dispatch('error', 'Server is not found.'); $this->dispatch('error', 'Server is not found.');
$this->currentState = 'private-key'; $this->state = 'private-key';
return; return;
} }
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id; $this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
$this->serverPublicKey = $this->createdServer->privateKey->publicKey(); $this->serverPublicKey = $this->createdServer->privateKey->publicKey();
$this->currentState = 'validate-server'; $this->state = 'validate-server';
} }
public function getProxyType() public function getProxyType()
{ {
@ -130,7 +160,7 @@ public function getProxyType()
$this->selectProxy(ProxyTypes::TRAEFIK_V2->value); $this->selectProxy(ProxyTypes::TRAEFIK_V2->value);
// $proxyTypeSet = $this->createdServer->proxy->type; // $proxyTypeSet = $this->createdServer->proxy->type;
// if (!$proxyTypeSet) { // if (!$proxyTypeSet) {
// $this->currentState = 'select-proxy'; // $this->state = 'select-proxy';
// return; // return;
// } // }
$this->getProjects(); $this->getProjects();
@ -139,12 +169,12 @@ public function selectExistingPrivateKey()
{ {
$this->createdPrivateKey = PrivateKey::find($this->selectedExistingPrivateKey); $this->createdPrivateKey = PrivateKey::find($this->selectedExistingPrivateKey);
$this->privateKey = $this->createdPrivateKey->private_key; $this->privateKey = $this->createdPrivateKey->private_key;
$this->currentState = 'create-server'; $this->state = 'create-server';
} }
public function createNewServer() public function createNewServer()
{ {
$this->selectedExistingServer = null; $this->selectedExistingServer = null;
$this->currentState = 'private-key'; $this->state = 'private-key';
} }
public function setPrivateKey(string $type) public function setPrivateKey(string $type)
{ {
@ -153,7 +183,7 @@ public function setPrivateKey(string $type)
if ($type === 'create') { if ($type === 'create') {
$this->createNewPrivateKey(); $this->createNewPrivateKey();
} }
$this->currentState = 'create-private-key'; $this->state = 'create-private-key';
} }
public function savePrivateKey() public function savePrivateKey()
{ {
@ -168,7 +198,7 @@ public function savePrivateKey()
'team_id' => currentTeam()->id 'team_id' => currentTeam()->id
]); ]);
$this->createdPrivateKey->save(); $this->createdPrivateKey->save();
$this->currentState = 'create-server'; $this->state = 'create-server';
} }
public function saveServer() public function saveServer()
{ {
@ -196,7 +226,8 @@ public function saveServer()
$this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel; $this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel;
$this->createdServer->settings->save(); $this->createdServer->settings->save();
$this->createdServer->addInitialNetwork(); $this->createdServer->addInitialNetwork();
$this->currentState = 'validate-server'; $this->selectedExistingServer = $this->createdServer->id;
$this->state = 'validate-server';
} }
public function installServer() public function installServer()
{ {
@ -223,7 +254,7 @@ public function validateServer()
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this->createdServer, true); $dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this->createdServer, true);
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion); $dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
if (is_null($dockerVersion)) { if (is_null($dockerVersion)) {
$this->currentState = 'validate-server'; $this->state = 'validate-server';
throw new \Exception('Docker not found or old version is installed.'); throw new \Exception('Docker not found or old version is installed.');
} }
$this->createdServer->settings()->update([ $this->createdServer->settings()->update([
@ -249,14 +280,14 @@ public function getProjects()
{ {
$this->projects = Project::ownedByCurrentTeam(['name'])->get(); $this->projects = Project::ownedByCurrentTeam(['name'])->get();
if ($this->projects->count() > 0) { if ($this->projects->count() > 0) {
$this->selectedExistingProject = $this->projects->first()->id; $this->selectedProject = $this->projects->first()->id;
} }
$this->currentState = 'create-project'; $this->state = 'create-project';
} }
public function selectExistingProject() public function selectExistingProject()
{ {
$this->createdProject = Project::find($this->selectedExistingProject); $this->createdProject = Project::find($this->selectedProject);
$this->currentState = 'create-resource'; $this->state = 'create-resource';
} }
public function createNewProject() public function createNewProject()
{ {
@ -264,7 +295,7 @@ public function createNewProject()
'name' => "My first project", 'name' => "My first project",
'team_id' => currentTeam()->id 'team_id' => currentTeam()->id
]); ]);
$this->currentState = 'create-resource'; $this->state = 'create-resource';
} }
public function showNewResource() public function showNewResource()
{ {

View File

@ -20,7 +20,7 @@ protected function rules() {
} }
public function mount() { public function mount() {
$this->oauth_settings_map = OauthSetting::all()->reduce(function($carry, $setting) { $this->oauth_settings_map = OauthSetting::all()->sortBy('provider')->reduce(function($carry, $setting) {
$carry[$setting->provider] = $setting; $carry[$setting->provider] = $setting;
return $carry; return $carry;
}, []); }, []);

View File

@ -9,7 +9,7 @@
"auth.confirm_password": "Confirm password", "auth.confirm_password": "Confirm password",
"auth.forgot_password": "Forgot password", "auth.forgot_password": "Forgot password",
"auth.forgot_password_send_email": "Send password reset email", "auth.forgot_password_send_email": "Send password reset email",
"auth.register_now": "Register a new account", "auth.register_now": "Register",
"auth.logout": "Logout", "auth.logout": "Logout",
"auth.register": "Register", "auth.register": "Register",
"auth.registration_disabled": "Registration is disabled. Please contact the administrator.", "auth.registration_disabled": "Registration is disabled. Please contact the administrator.",

View File

@ -11,6 +11,9 @@ body {
@apply text-sm antialiased scrollbar; @apply text-sm antialiased scrollbar;
} }
.button {
@apply px-3 py-1.5 text-sm font-normal normal-case rounded bg-neutral-200 hover:bg-neutral-300 dark:bg-coolgray-200 dark:text-white dark:hover:bg-coolgray-100 dark:disabled:bg-coolgray-100/40 dark:disabled:text-neutral-800 min-w-fit flex items-center justify-center;
}
button[isError]:not(:disabled) { button[isError]:not(:disabled) {
@apply bg-red-600 hover:bg-red-700; @apply bg-red-600 hover:bg-red-700;
} }
@ -19,9 +22,7 @@ button[isHighlighted]:not(:disabled) {
@apply bg-coollabs hover:bg-coollabs-100; @apply bg-coollabs hover:bg-coollabs-100;
} }
.button {
@apply px-3 py-1.5 text-sm font-normal normal-case rounded dark:bg-coolgray-200 dark:text-white dark:hover:bg-coolgray-100 dark:disabled:bg-coolgray-100/40 dark:disabled:text-neutral-800 min-w-fit flex items-center justify-center;
}
h1 { h1 {
@apply text-2xl font-bold dark:text-white text-neutral-800; @apply text-2xl font-bold dark:text-white text-neutral-800;
@ -82,6 +83,7 @@ tr td:first-child {
input { input {
@apply pr-10; @apply pr-10;
} }
.input { .input {
@apply block w-full py-1.5 rounded border-0 text-sm ring-inset ring-1 dark:bg-coolgray-100 dark:text-white text-black focus:ring-2 dark:focus:ring-coolgray-300 dark:ring-coolgray-300 dark:read-only:text-neutral-500 dark:read-only:ring-0 dark:read-only:bg-coolgray-100/40 dark:placeholder:text-neutral-700; @apply block w-full py-1.5 rounded border-0 text-sm ring-inset ring-1 dark:bg-coolgray-100 dark:text-white text-black focus:ring-2 dark:focus:ring-coolgray-300 dark:ring-coolgray-300 dark:read-only:text-neutral-500 dark:read-only:ring-0 dark:read-only:bg-coolgray-100/40 dark:placeholder:text-neutral-700;
} }
@ -89,12 +91,15 @@ .input {
option { option {
@apply text-white; @apply text-white;
} }
.alert-success { .alert-success {
@apply flex items-center gap-2 text-success; @apply flex items-center gap-2 text-success;
} }
.alert-error { .alert-error {
@apply flex items-center gap-2 text-error; @apply flex items-center gap-2 text-error;
} }
.dropdown-item { .dropdown-item {
@apply relative flex cursor-pointer select-none dark:hover:text-white dark:hover:bg-coollabs items-center px-2 py-1.5 text-xs justify-center outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 gap-2 @apply relative flex cursor-pointer select-none dark:hover:text-white dark:hover:bg-coollabs items-center px-2 py-1.5 text-xs justify-center outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 gap-2
} }
@ -115,8 +120,6 @@ .badge-error {
@apply bg-error; @apply bg-error;
} }
[type='checkbox']:checked { [type='checkbox']:checked {
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='black' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"); background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='black' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
} }
@ -126,11 +129,11 @@ .menu {
} }
.menu-item { .menu-item {
@apply flex items-center w-full gap-2 px-4 py-1 min-w-48 hover:bg-coolgray-100 dark:hover:text-white; @apply flex items-center w-full gap-2 py-1 pl-2 dark:hover:bg-coolgray-100 dark:hover:text-white hover:bg-neutral-300;
} }
.menu-item-active { .menu-item-active {
@apply rounded-none dark:bg-coolgray-200 dark:text-warning; @apply text-black rounded-none dark:bg-coolgray-200 dark:text-warning bg-neutral-200;
} }
.icon { .icon {
@ -170,15 +173,21 @@ .icon:hover {
} }
.box { .box {
@apply flex p-2 transition-colors cursor-pointer min-h-[4rem] bg-coolgray-100 hover:bg-coollabs-100 hover:text-white hover:no-underline; @apply flex p-2 transition-colors cursor-pointer min-h-[4rem] dark:bg-coolgray-100 bg-neutral-50 hover:bg-neutral-200 dark:hover:bg-coollabs-100 dark:hover:text-white hover:no-underline;
} }
.box-without-bg { .box-without-bg {
@apply flex p-2 transition-colors hover:text-white hover:no-underline min-h-[4rem]; @apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem];
} }
.box-title {
@apply font-bold text-black dark:text-white group-hover:dark:text-white;
}
.box-description {
@apply text-xs font-bold text-neutral-500 group-hover:dark:text-white group-hover:text-black;
}
.description { .description {
@apply text-xs font-bold text-neutral-500 group-hover:text-white; @apply text-xs font-bold text-neutral-500 group-hover:dark:text-white group-hover:text-black;
} }
.lds-heart { .lds-heart {
@ -224,8 +233,9 @@ .buyme {
} }
.title { .title {
@apply hidden pb-0 lg:block lg:pb-8 ; @apply hidden pb-0 lg:block lg:pb-8;
} }
.subtitle { .subtitle {
@apply pt-2 pb-10; @apply pt-2 pb-10;
} }
@ -237,4 +247,3 @@ .fullscreen {
.toast { .toast {
z-index: 1; z-index: 1;
} }

View File

@ -1,29 +1,15 @@
<x-layout-simple> <x-layout-simple>
<div class="min-h-screen hero"> <section class="bg-gray-50 dark:bg-base">
<div class="w-96 min-w-fit"> <div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
<div class="flex flex-col items-center pb-8"> <a class="flex items-center mb-6 text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white">
<div class="text-5xl font-extrabold tracking-tight text-center text-white">Coolify</div> Coolify
</div> </a>
<div class="flex items-center gap-2"> <div
<h1>{{ __('auth.login') }}</h1> class="w-full bg-white rounded shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-base dark:border-coolgray-200">
@if ($is_registration_enabled) <div class="p-6 space-y-4 md:space-y-6 sm:p-8">
@if (config('coolify.waitlist')) <form action="/login" method="POST" class="flex flex-col gap-2">
<a href="/waitlist" @csrf
class="text-xs text-center text-white normal-case bg-transparent border-none rounded no-animation hover:no-underline btn btn-sm bg-coollabs-gradient"> @env('local')
Join the waitlist
</a>
@else
<a href="/register"
class="text-xs text-center text-white normal-case bg-transparent border-none rounded no-animation hover:no-underline btn btn-sm bg-coollabs-gradient">
{{ __('auth.register_now') }}
</a>
@endif
@endif
</div>
<div>
<form action="/login" method="POST" class="flex flex-col gap-2">
@csrf
@env('local')
<x-forms.input value="test@example.com" type="email" name="email" required <x-forms.input value="test@example.com" type="email" name="email" required
label="{{ __('input.email') }}" autofocus /> label="{{ __('input.email') }}" autofocus />
@ -33,40 +19,59 @@ class="text-xs text-center text-white normal-case bg-transparent border-none rou
{{ __('auth.forgot_password') }}? {{ __('auth.forgot_password') }}?
</a> </a>
@else @else
<x-forms.input type="email" name="email" required label="{{ __('input.email') }}" autofocus /> <x-forms.input type="email" name="email" required label="{{ __('input.email') }}"
autofocus />
<x-forms.input type="password" name="password" required label="{{ __('input.password') }}" /> <x-forms.input type="password" name="password" required label="{{ __('input.password') }}" />
<a href="/forgot-password" class="text-xs"> <a href="/forgot-password" class="text-xs">
{{ __('auth.forgot_password') }}? {{ __('auth.forgot_password') }}?
</a> </a>
@endenv @endenv
<x-forms.button type="submit">{{ __('auth.login') }}</x-forms.button> <x-forms.button class="mt-10" type="submit">{{ __('auth.login') }}</x-forms.button>
@foreach ($enabled_oauth_providers as $provider_setting) @if ($is_registration_enabled)
<x-forms.button type="button" onclick="document.location.href='/auth/{{$provider_setting->provider}}/redirect'"> <a href="/register"
{{ __("auth.login.$provider_setting->provider") }} class="button bg-coollabs-gradient">
</x-forms.button> {{ __('auth.register_now') }}
@endforeach </a>
@if (!$is_registration_enabled) @endif
<div class="text-center ">{{ __('auth.registration_disabled') }}</div> @if ($enabled_oauth_providers->isNotEmpty())
@endif <div class="relative">
@if ($errors->any()) <div class="absolute inset-0 flex items-center" aria-hidden="true">
<div class="text-xs text-center text-error"> <div class="w-full border-t dark:border-coolgray-200"></div>
@foreach ($errors->all() as $error) </div>
<p>{{ $error }}</p> <div class="relative flex justify-center">
@endforeach <span class="px-2 text-sm dark:text-neutral-500 dark:bg-base">or</span>
</div> </div>
@endif </div>
@if (session('status')) @endif
<div class="mb-4 font-medium text-green-600"> @foreach ($enabled_oauth_providers as $provider_setting)
{{ session('status') }} <x-forms.button type="button"
</div> onclick="document.location.href='/auth/{{ $provider_setting->provider }}/redirect'">
@endif {{ __("auth.login.$provider_setting->provider") }}
@if (session('error')) </x-forms.button>
<div class="mb-4 font-medium text-red-600"> @endforeach
{{ session('error') }} @if (!$is_registration_enabled)
</div> <div class="text-center text-neutral-500">{{ __('auth.registration_disabled') }}</div>
@endif @endif
</form> @if ($errors->any())
<div class="text-xs text-center text-error">
@foreach ($errors->all() as $error)
<p>{{ $error }}</p>
@endforeach
</div>
@endif
@if (session('status'))
<div class="mb-4 font-medium text-green-600">
{{ session('status') }}
</div>
@endif
@if (session('error'))
<div class="mb-4 font-medium text-red-600">
{{ session('error') }}
</div>
@endif
</form>
</div>
</div> </div>
</div> </div>
</div> </section>
</x-layout-simple> </x-layout-simple>

View File

@ -1,48 +1,53 @@
<x-layout-simple> <x-layout-simple>
<div class="flex items-center justify-center min-h-screen "> <section class="bg-gray-50 dark:bg-base">
<div class="w-1/2"> <div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
<div class="flex flex-col items-center pb-8"> <a class="flex items-center mb-6 text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white">
<div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div> Coolify
<x-version /> </a>
<div
class="w-full bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-base dark:border-coolgray-200">
<div class="p-6 space-y-4 md:space-y-6 sm:p-8">
<h1 class="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
Create an account
</h1>
<form action="/register" method="POST" class="flex flex-col gap-2">
@csrf
@env('local')
<x-forms.input required value="test3 normal user" type="text" name="name"
label="{{ __('input.name') }}" />
<x-forms.input required value="test3@example.com" type="email" name="email"
label="{{ __('input.email') }}" />
<div class="flex gap-2">
<x-forms.input required value="password" type="password" name="password"
label="{{ __('input.password') }}" />
<x-forms.input required value="password" type="password" name="password_confirmation"
label="{{ __('input.password.again') }}" />
</div>
@else
<x-forms.input required type="text" name="name" label="{{ __('input.name') }}" />
<x-forms.input required type="email" name="email" label="{{ __('input.email') }}" />
<div class="flex gap-2">
<x-forms.input required type="password" name="password"
label="{{ __('input.password') }}" />
<x-forms.input required type="password" name="password_confirmation"
label="{{ __('input.password.again') }}" />
</div>
@endenv
<x-forms.button class="mb-4" type="submit">Register</x-forms.button>
<a href="/login" class="button bg-coollabs-gradient">
{{ __('auth.already_registered') }}
</a>
</form>
@if ($errors->any())
<div class="text-xs text-center text-error">
@foreach ($errors->all() as $error)
<p>{{ $error }}</p>
@endforeach
</div>
@endif
</div>
</div> </div>
<div class="flex items-center gap-2">
<h1>{{ __('auth.register') }}</h1>
<a href="/login"
class="text-xs text-center text-white normal-case bg-transparent border-none rounded no-animation hover:no-underline btn btn-sm bg-coollabs-gradient">
{{ __('auth.already_registered') }}
</a>
</div>
<form action="/register" method="POST" class="flex flex-col gap-2">
@csrf
@env('local')
<x-forms.input required value="test3 normal user" type="text" name="name"
label="{{ __('input.name') }}" />
<x-forms.input required value="test3@example.com" type="email" name="email"
label="{{ __('input.email') }}" />
<div class="flex gap-2">
<x-forms.input required value="password" type="password" name="password"
label="{{ __('input.password') }}" />
<x-forms.input required value="password" type="password" name="password_confirmation"
label="{{ __('input.password.again') }}" />
</div>
@else
<x-forms.input required type="text" name="name" label="{{ __('input.name') }}" />
<x-forms.input required type="email" name="email" label="{{ __('input.email') }}" />
<div class="flex gap-2">
<x-forms.input required type="password" name="password" label="{{ __('input.password') }}" />
<x-forms.input required type="password" name="password_confirmation"
label="{{ __('input.password.again') }}" />
</div>
@endenv
<x-forms.button type="submit">{{ __('auth.register') }}</x-forms.button>
</form>
@if ($errors->any())
<div class="text-xs text-center text-error">
@foreach ($errors->all() as $error)
<p>{{ $error }}</p>
@endforeach
</div>
@endif
</div> </div>
</div> </section>
</x-layout-simple> </x-layout-simple>

View File

@ -1,9 +1,9 @@
<div class="grid grid-cols-1 gap-4 md:grid-cols-3"> <div class="grid grid-cols-1 gap-4 md:grid-cols-3">
<div class="box-border col-span-2 min-w-[24rem] min-h-[21rem]"> <div class="box-border col-span-2 min-w-[24rem] min-h-[21rem]">
<h1 class="text-5xl font-bold">{{ $title }}</h1> <h1 class="text-5xl font-bold">{{ $title }}</h1>
<div class="py-6 "> <div class="py-6">
@isset($question) @isset($question)
<p class="text-base"> <p class="text-base dark:text-neutral-400">
{{ $question }} {{ $question }}
</p> </p>
@endisset @endisset

View File

@ -1 +1 @@
<span class="inline-block text-warning">{{ $text }}</span> <span class="inline-block font-bold text-warning">{{ $text }}</span>

View File

@ -1,5 +1,6 @@
@props(['text' => null]) @props(['text' => null])
<div class="inline-flex items-center justify-center" {{ $attributes }}> <div class="inline-flex items-center justify-center" {{ $attributes }}>
<div>{{ $text }}</div>
<svg class="w-4 h-4 mx-1 ml-3 text-warning animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" <svg class="w-4 h-4 mx-1 ml-3 text-warning animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24"> viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
@ -7,5 +8,4 @@
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"> d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
</path> </path>
</svg> </svg>
<div>{{ $text }}</div>
</div> </div>

View File

@ -1,11 +1,14 @@
<div class="flex items-center h-16 pt-2 lg:pt-4 shrink-0"> <nav class="flex flex-col flex-1 pl-4 border-r dark:border-coolgray-300 dark:bg-coolgray-100 bg-neutral-50">
<a href="/" class="flex items-center w-12 h-12 -mx-1 text-xl font-bold text-white"><img <div class="flex w-full px-2 pt-6 pb-4">
class="transition rounded " src="{{ asset('coolify-transparent.png') }}"></a> <div class="flex flex-col">
</div> <div class="text-2xl font-bold tracking-wide dark:text-white">Coolify</div>
<nav class="flex flex-col flex-1"> <x-version />
</div>
<button onclick="changeTheme()">Dark/light</button>
</div>
<ul role="list" class="flex flex-col flex-1 gap-y-7"> <ul role="list" class="flex flex-col flex-1 gap-y-7">
<li class="flex-1"> <li class="flex-1 ">
<ul role="list" class="flex flex-col h-full -mx-2 space-y-1"> <ul role="list" class="flex flex-col h-full space-y-1.5">
<li> <li>
<a title="Dashboard" href="/" <a title="Dashboard" href="/"
class="{{ request()->is('/') ? 'menu-item-active menu-item' : 'menu-item' }}"> class="{{ request()->is('/') ? 'menu-item-active menu-item' : 'menu-item' }}">
@ -278,8 +281,8 @@ class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border text-
</a> </a>
</li> </li>
</ul> </ul>
</li> --}} </li>
{{-- <li class="mt-auto -mx-6"> <li class="mt-auto -mx-6">
<a href="#" <a href="#"
class="flex items-center px-6 py-3 text-sm font-semibold leading-6 text-gray-900 gap-x-4 hover:bg-gray-50"> class="flex items-center px-6 py-3 text-sm font-semibold leading-6 text-gray-900 gap-x-4 hover:bg-gray-50">
<img class="w-8 h-8 rounded-full bg-gray-50" <img class="w-8 h-8 rounded-full bg-gray-50"

View File

@ -1,2 +1,2 @@
<a {{ $attributes->merge(['class' => 'text-xs cursor-pointer opacity-60 hover:opacity-100 hover:text-white z-[60]']) }} <a {{ $attributes->merge(['class' => 'text-xs cursor-pointer opacity-60 hover:opacity-100 dark:hover:text-white hover:text-black z-[60]']) }}
href="https://github.com/coollabsio/coolify/releases/tag/v{{ config('version') }}">v{{ config('version') }}</a> href="https://github.com/coollabsio/coolify/releases/tag/v{{ config('version') }}">v{{ config('version') }}</a>

View File

@ -10,7 +10,7 @@
<div class="relative z-50 lg:hidden" :class="open ? 'block' : 'hidden'" role="dialog" aria-modal="true"> <div class="relative z-50 lg:hidden" :class="open ? 'block' : 'hidden'" role="dialog" aria-modal="true">
<div class="fixed inset-0 bg-black/80"></div> <div class="fixed inset-0 bg-black/80"></div>
<div class="fixed inset-0 flex"> <div class="fixed inset-0 flex">
<div class="relative flex flex-1 w-full mr-16 max-w-56 "> <div class="relative flex flex-1 w-full mr-16 max-w-48 ">
<div class="absolute top-0 flex justify-center w-16 pt-5 left-full"> <div class="absolute top-0 flex justify-center w-16 pt-5 left-full">
<button type="button" class="-m-2.5 p-2.5" x-on:click="open = !open"> <button type="button" class="-m-2.5 p-2.5" x-on:click="open = !open">
<span class="sr-only">Close sidebar</span> <span class="sr-only">Close sidebar</span>
@ -21,21 +21,20 @@
</button> </button>
</div> </div>
<div class="flex flex-col px-6 pb-2 overflow-y-auto dark:bg-coolgray-100 gap-y-5 scrollbar"> <div class="flex flex-col pb-2 overflow-y-auto min-w-48 dark:bg-coolgray-100 gap-y-5 scrollbar">
<x-navbar /> <x-navbar />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-56 lg:flex-col"> <div class="hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-48 lg:flex-col">
<div class="flex flex-col px-4 overflow-y-auto grow gap-y-5 scrollbar"> <div class="flex flex-col overflow-y-auto grow gap-y-5 scrollbar">
<x-navbar /> <x-navbar />
</div> </div>
</div> </div>
<div <div class="sticky top-0 z-40 flex items-center px-4 py-4 gap-x-6 sm:px-6 lg:hidden">
class="sticky top-0 z-40 flex items-center px-4 py-4 gap-x-6 sm:px-6 lg:hidden">
<button type="button" class="-m-2.5 p-2.5 dark:text-warning lg:hidden" x-on:click="open = !open"> <button type="button" class="-m-2.5 p-2.5 dark:text-warning lg:hidden" x-on:click="open = !open">
<span class="sr-only">Open sidebar</span> <span class="sr-only">Open sidebar</span>
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 24 24"> <svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 24 24">
@ -52,7 +51,7 @@ class="sticky top-0 z-40 flex items-center px-4 py-4 gap-x-6 sm:px-6 lg:hidden">
</a> --}} </a> --}}
</div> </div>
<main class="lg:pl-56"> <main class="lg:pl-48">
<div> <div>
<div class="p-4 sm:px-6 lg:px-8 lg:py-6"> <div class="p-4 sm:px-6 lg:px-8 lg:py-6">
{{ $slot }} {{ $slot }}

View File

@ -38,10 +38,9 @@
@section('body') @section('body')
<body> <body>
{{-- <button onclick="changeTheme()" class="fixed z-50 left-52">Dark/light</button> --}}
@livewire('wire-elements-modal') @livewire('wire-elements-modal')
<x-toast /> <x-toast />
<x-version class="fixed left-7 bottom-1" /> {{-- <x-version class="fixed left-7 bottom-1" /> --}}
<script data-navigate-once> <script data-navigate-once>
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia( if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia(
'(prefers-color-scheme: dark)').matches)) { '(prefers-color-scheme: dark)').matches)) {

View File

@ -1,350 +1,335 @@
@php use App\Enums\ProxyTypes; @endphp @php use App\Enums\ProxyTypes; @endphp
<div> <section class="bg-gray-50 dark:bg-base">
<div> <div class="flex flex-col items-center justify-center px-6 py-8 mx-auto max-w-7xl md:h-screen lg:py-0">
@if ($currentState === 'welcome') @if ($state === '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 set up the basics.</p> <p class="py-6 text-xl text-center">Let me help you set up the basics.</p>
<div class="flex justify-center "> <div class="flex justify-center ">
<x-forms.button class="justify-center w-64 box" wire:click="$set('currentState','explanation')">Get <x-forms.button class="justify-center w-64 box" wire:click="$set('state','explanation')">Get
Started Started
</x-forms.button> </x-forms.button>
</div> </div>
@endif @endif
</div> <div>
<div> @if ($state === 'explanation')
@if ($currentState === 'explanation') <x-boarding-step title="What is Coolify?">
<x-boarding-step title="What is Coolify?"> <x-slot:question>
<x-slot:question> Coolify is an all-in-one application to automate tasks on your servers, deploy application with
Coolify is an all-in-one application to automate tasks on your servers, deploy application with Git Git
integrations, deploy databases and services, monitor these resources with notifications and alerts integrations, deploy databases and services, monitor these resources with notifications and
without vendor lock-in alerts
and <a href="https://coolify.io" class="text-white hover:underline">much much more</a>. without vendor lock-in
<br><br> and <a href="https://coolify.io" class="text-white hover:underline">much much more</a>.
<span class="text-xl"> <br><br>
<x-highlighted text="Self-hosting with superpowers!" /></span> <span class="text-xl">
</x-slot:question> <x-highlighted text="Self-hosting with superpowers!" /></span>
<x-slot:explanation> </x-slot:question>
<p><x-highlighted text="Task automation:" /> You don't need to manage your servers anymore. Coolify does <x-slot:explanation>
it for you.</p> <p><x-highlighted text="Task automation:" /> You don't need to manage your servers anymore.
<p><x-highlighted text="No vendor lock-in:" /> All configurations are stored on your servers, so Coolify does
everything works without a connection to Coolify (except integrations and automations).</p> it for you.</p>
<p><x-highlighted text="Monitoring:" />You can get notified on your favourite platforms (Discord, <p><x-highlighted text="No vendor lock-in:" /> All configurations are stored on your servers, so
Telegram, Email, etc.) when something goes wrong, or an action is needed from your side.</p> everything works without a connection to Coolify (except integrations and automations).</p>
</x-slot:explanation> <p><x-highlighted text="Monitoring:" />You can get notified on your favourite platforms
<x-slot:actions> (Discord,
<x-forms.button class="justify-center w-64 box" wire:click="explanation">Next Telegram, Email, etc.) when something goes wrong, or an action is needed from your side.</p>
</x-forms.button> </x-slot:explanation>
</x-slot:actions> <x-slot:actions>
</x-boarding-step> <x-forms.button class="justify-center w-64 box" wire:click="explanation">Next
@endif </x-forms.button>
@if ($currentState === 'select-server-type') </x-slot:actions>
<x-boarding-step title="Server"> </x-boarding-step>
<x-slot:question> @endif
Do you want to deploy your resources to your <x-highlighted text="Localhost" /> @if ($state === 'select-server-type')
or to a <x-highlighted text="Remote Server" />? <x-boarding-step title="Server">
</x-slot:question> <x-slot:question>
<x-slot:actions> Do you want to deploy your resources to your <x-highlighted text="Localhost" />
<x-forms.button class="justify-center w-64 box" wire:target="setServerType('localhost')" or to a <x-highlighted text="Remote Server" />?
wire:click="setServerType('localhost')">Localhost </x-slot:question>
</x-forms.button> <x-slot:actions>
<x-forms.button class="justify-center w-64 box" wire:target="setServerType('localhost')"
wire:click="setServerType('localhost')">Localhost
</x-forms.button>
<x-forms.button class="justify-center w-64 box " wire:target="setServerType('remote')" <x-forms.button class="justify-center w-64 box " wire:target="setServerType('remote')"
wire:click="setServerType('remote')">Remote Server wire:click="setServerType('remote')">Remote Server
</x-forms.button>
@if (!$serverReachable)
Localhost is not reachable with the following public key.
<br /> <br />
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for user
'root' or skip the boarding process and add a new private key manually to Coolify and to the
server.
<br />
Check this <a target="_blank" class="underline"
href="https://coolify.io/docs/server/openssh">documentation</a> for further help.
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
<x-forms.button class="w-64 box" wire:target="setServerType('localhost')"
wire:click="setServerType('localhost')">Check again
</x-forms.button> </x-forms.button>
@endif @if (!$serverReachable)
</x-slot:actions> Localhost is not reachable with the following public key.
<x-slot:explanation> <br /> <br />
<p>Servers are the main building blocks, as they will host your applications, databases, Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for
services, called resources. Any CPU intensive process will use the server's CPU where you user
are deploying your resources.</p> 'root' or skip the boarding process and add a new private key manually to Coolify and to the
<p>Localhost is the server where Coolify is running on. It is not recommended to use one server server.
for everything.</p> <br />
<p>Remote Server is a server reachable through SSH. It can be hosted at home, or from any cloud Check this <a target="_blank" class="underline"
provider.</p> href="https://coolify.io/docs/server/openssh">documentation</a> for further help.
</x-slot:explanation> <x-forms.input readonly id="serverPublicKey"></x-forms.input>
</x-boarding-step> <x-forms.button class="w-64 box" wire:target="setServerType('localhost')"
@endif wire:click="setServerType('localhost')">Check again
</div>
<div>
@if ($currentState === 'private-key')
<x-boarding-step title="SSH Key">
<x-slot:question>
Do you have your own SSH Private Key?
</x-slot:question>
<x-slot:actions>
<x-forms.button class="justify-center w-64 box" wire:target="setPrivateKey('own')"
wire:click="setPrivateKey('own')">Yes
</x-forms.button>
<x-forms.button class="justify-center w-64 box" wire:target="setPrivateKey('create')"
wire:click="setPrivateKey('create')">No (create one for me)
</x-forms.button>
@if (count($privateKeys) > 0)
<form wire:submit='selectExistingPrivateKey' class="flex flex-col w-full gap-4 pr-10">
<x-forms.select label="Existing SSH Keys" id='selectedExistingPrivateKey'>
@foreach ($privateKeys as $privateKey)
<option wire:key="{{ $loop->index }}" value="{{ $privateKey->id }}">
{{ $privateKey->name }}</option>
@endforeach
</x-forms.select>
<x-forms.button type="submit">Use this SSH Key</x-forms.button>
</form>
@endif
</x-slot:actions>
<x-slot:explanation>
<p>SSH Keys are used to connect to a remote server through a secure shell, called SSH.</p>
<p>You can use your own ssh 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 ssh private key to the remote
server's
<code class="text-warning">~/.ssh/authorized_keys</code> file.
</p>
</x-slot:explanation>
</x-boarding-step>
@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="flex flex-col gap-4">
<div>
<x-forms.button class="justify-center w-64 box" wire:click="createNewServer">No (create one
for
me)
</x-forms.button> </x-forms.button>
</div> @endif
<div> </x-slot:actions>
<form wire:submit='selectExistingServer' class="flex flex-col w-full gap-4 lg:w-96"> <x-slot:explanation>
<x-forms.select label="Existing servers" class="w-96" id='selectedExistingServer'> <p>Servers are the main building blocks, as they will host your applications, databases,
@foreach ($servers as $server) services, called resources. Any CPU intensive process will use the server's CPU where you
<option wire:key="{{ $loop->index }}" value="{{ $server->id }}"> are deploying your resources.</p>
{{ $server->name }}</option> <p><x-highlighted text="Localhost" /> is the server where Coolify is running on. It is not
recommended to use one server
for everything.</p>
<p><x-highlighted text="A remote server" /> is a server reachable through SSH. It can be hosted
at home, or from any cloud
provider.</p>
</x-slot:explanation>
</x-boarding-step>
@endif
</div>
<div>
@if ($state === 'private-key')
<x-boarding-step title="SSH Key">
<x-slot:question>
Do you have your own SSH Private Key?
</x-slot:question>
<x-slot:actions>
<x-forms.button class="justify-center w-64 box" wire:target="setPrivateKey('own')"
wire:click="setPrivateKey('own')">Yes
</x-forms.button>
<x-forms.button class="justify-center w-64 box" wire:target="setPrivateKey('create')"
wire:click="setPrivateKey('create')">No (create one for me)
</x-forms.button>
@if (count($privateKeys) > 0)
<form wire:submit='selectExistingPrivateKey' class="flex flex-col w-full gap-4 pr-10">
<x-forms.select label="Existing SSH Keys" id='selectedExistingPrivateKey'>
@foreach ($privateKeys as $privateKey)
<option wire:key="{{ $loop->index }}" value="{{ $privateKey->id }}">
{{ $privateKey->name }}</option>
@endforeach @endforeach
</x-forms.select> </x-forms.select>
<x-forms.button type="submit">Use this Server</x-forms.button> <x-forms.button type="submit">Use this SSH Key</x-forms.button>
</form> </form>
</div>
</div>
@if (!$serverReachable)
This server is not reachable with the following public key.
<br /> <br />
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for user
'root' or skip the boarding process and add a new private key manually to Coolify and to the
server.
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
<x-forms.button class="w-64 box" wire:target="validateServer" wire:click="validateServer">Check
again
</x-forms.button>
@endif
</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')
<x-boarding-step title="Create Private Key">
<x-slot:question>
Please let me know your key details.
</x-slot:question>
<x-slot:actions>
<form wire:submit='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."
label="Name" id="privateKeyName" />
<x-forms.input placeholder="Description, so others will know more about this."
label="Description" id="privateKeyDescription" />
<x-forms.textarea required placeholder="-----BEGIN OPENSSH PRIVATE KEY-----" label="Private Key"
id="privateKey" />
@if ($privateKeyType === 'create')
<x-forms.textarea rows="7" readonly label="Public Key" id="publicKey" />
<span class="font-bold text-warning">ACTION REQUIRED: Copy the 'Public Key' to your server's
~/.ssh/authorized_keys
file.</span>
@endif @endif
<x-forms.button type="submit">Save</x-forms.button> </x-slot:actions>
</form> <x-slot:explanation>
</x-slot:actions> <p>SSH Keys are used to connect to a remote server through a secure shell, called SSH.</p>
<x-slot:explanation> <p>You can use your own ssh private key, or you can let Coolify to create one for you.</p>
<p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p> <p>In both ways, you need to add the public version of your ssh private key to the remote
<p>You can use your own private key, or you can let Coolify to create one for you.</p> server's
<p>In both ways, you need to add the public version of your private key to the remote server's <code class="text-warning">~/.ssh/authorized_keys</code> file.
<code>~/.ssh/authorized_keys</code> file. </p>
</p> </x-slot:explanation>
</x-slot:explanation> </x-boarding-step>
</x-boarding-step> @endif
@endif </div>
</div> <div>
<div> @if ($state === 'select-existing-server')
@if ($currentState === 'create-server') <x-boarding-step title="Select a server">
<x-boarding-step title="Create Server"> <x-slot:question>
<x-slot:question> There are already servers available for your Team. Do you want to use one of them?
Please let me know your server details. </x-slot:question>
</x-slot:question> <x-slot:actions>
<x-slot:actions> <div class="flex flex-col gap-4">
<form wire:submit='saveServer' class="flex flex-col w-full gap-4 pr-10"> <div>
<div class="flex gap-2"> <x-forms.button class="justify-center w-64 box" wire:click="createNewServer">No (create
<x-forms.input required placeholder="Choose a name for your Server. Could be anything." one
label="Name" id="remoteServerName" /> for
me)
</x-forms.button>
</div>
<div>
<form wire:submit='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>
</div>
@if (!$serverReachable)
This server is not reachable with the following public key.
<br /> <br />
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for
user
'root' or skip the boarding process and add a new private key manually to Coolify and to the
server.
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
<x-forms.button class="w-64 box" wire:target="validateServer"
wire:click="validateServer">Check
again
</x-forms.button>
@endif
</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 ($state === 'create-private-key')
<x-boarding-step title="Create Private Key">
<x-slot:question>
Please let me know your key details.
</x-slot:question>
<x-slot:actions>
<form wire:submit='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."
label="Name" id="privateKeyName" />
<x-forms.input placeholder="Description, so others will know more about this." <x-forms.input placeholder="Description, so others will know more about this."
label="Description" id="remoteServerDescription" /> label="Description" id="privateKeyDescription" />
</div> <x-forms.textarea required placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"
<div class="flex gap-2"> label="Private Key" id="privateKey" />
<x-forms.input required placeholder="127.0.0.1" label="IP Address" id="remoteServerHost" /> @if ($privateKeyType === 'create')
<x-forms.input required placeholder="Port number of your server. Default is 22." <x-forms.textarea rows="7" readonly label="Public Key" id="publicKey" />
label="Port" id="remoteServerPort" /> <span class="font-bold text-warning">ACTION REQUIRED: Copy the 'Public Key' to your
<x-forms.input required readonly server's
placeholder="Username to connect to your server. Default is root." label="Username" ~/.ssh/authorized_keys
id="remoteServerUser" /> file.</span>
</div> @endif
<div class="w-64"> <x-forms.button type="submit">Save</x-forms.button>
<x-forms.checkbox </form>
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<br><span class='text-warning'>Coolify does not install/setup Cloudflare (cloudflared) on your server.</span>" </x-slot:actions>
id="isCloudflareTunnel" label="Cloudflare Tunnel" /> <x-slot:explanation>
</div> <p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p>
<x-forms.button type="submit">Continue</x-forms.button> <p>You can use your own private key, or you can let Coolify to create one for you.</p>
</form> <p>In both ways, you need to add the public version of your private key to the remote server's
</x-slot:actions> <code>~/.ssh/authorized_keys</code> file.
<x-slot:explanation> </p>
<p>Username should be <x-highlighted text="root" /> for now. We are working on to use </x-slot:explanation>
non-root users.</p> </x-boarding-step>
</x-slot:explanation> @endif
</x-boarding-step> </div>
@endif <div>
</div> @if ($state === 'create-server')
<div> <x-boarding-step title="Create Server">
@if ($currentState === 'validate-server') <x-slot:question>
<x-boarding-step title="Validate & Configure Server"> Please let me know your server details.
<x-slot:question> </x-slot:question>
I need to validate your server (connection, Docker Engine, etc) and configure if something is <x-slot:actions>
missing for me. Are you okay with this? <form wire:submit='saveServer' class="flex flex-col w-full gap-4 pr-10">
</x-slot:question> <div class="flex gap-2">
<x-slot:actions> <x-forms.input required placeholder="Choose a name for your Server. Could be anything."
<x-slide-over closeWithX fullScreen> label="Name" id="remoteServerName" />
<x-slot:title>Validate & configure</x-slot:title> <x-forms.input placeholder="Description, so others will know more about this."
<x-slot:content> label="Description" id="remoteServerDescription" />
<livewire:server.validate-and-install :server="$this->createdServer" /> </div>
</x-slot:content> <div class="flex gap-2">
<x-forms.button @click="slideOverOpen=true" class="font-bold box w-96" <x-forms.input required placeholder="127.0.0.1" label="IP Address"
wire:click.prevent='installServer' isHighlighted> id="remoteServerHost" />
Let's do it! <x-forms.input required placeholder="Port number of your server. Default is 22."
</x-forms.button> label="Port" id="remoteServerPort" />
</x-slide-over> <x-forms.input required readonly
</x-slot:actions> placeholder="Username to connect to your server. Default is root."
<x-slot:explanation> label="Username" id="remoteServerUser" />
<p>This will install the latest Docker Engine on your server, configure a few things to be able </div>
to run optimal.<br><br>Minimum Docker Engine version is: 22<br><br>To manually install Docker <div class="w-64">
Engine, check <a target="_blank" class="underline text-warning" <x-forms.checkbox
href="https://docs.docker.com/engine/install/#server">this helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<br><span class='text-warning'>Coolify does not install/setup Cloudflare (cloudflared) on your server.</span>"
documentation</a>.</p> id="isCloudflareTunnel" label="Cloudflare Tunnel" />
</x-slot:explanation> </div>
</x-boarding-step> <x-forms.button type="submit">Continue</x-forms.button>
@endif </form>
</div> </x-slot:actions>
{{-- <div> <x-slot:explanation>
@if ($currentState === 'select-proxy') <p>Username should be <x-highlighted text="root" /> for now. We are working on to use
<x-boarding-step title="Select a Proxy"> non-root users.</p>
<x-slot:question> </x-slot:explanation>
If you would like to attach any kind of domain to your resources, you need a proxy. </x-boarding-step>
</x-slot:question> @endif
<x-slot:actions> </div>
<x-forms.button wire:click="selectProxy" class="w-64 box"> <div>
Decide later @if ($state === 'validate-server')
</x-forms.button> <x-boarding-step title="Validate & Configure Server">
<x-forms.button class="w-32 box" wire:click="selectProxy('{{ ProxyTypes::TRAEFIK_V2 }}')"> <x-slot:question>
Traefik I need to validate your server (connection, Docker Engine, etc) and configure if something is
v2 missing for me. Are you okay with this?
</x-forms.button> </x-slot:question>
<x-forms.button disabled class="w-32 box"> <x-slot:actions>
Nginx <x-slide-over closeWithX fullScreen>
</x-forms.button> <x-slot:title>Validate & configure</x-slot:title>
<x-forms.button disabled class="w-32 box"> <x-slot:content>
Caddy <livewire:server.validate-and-install :server="$this->createdServer" />
</x-forms.button> </x-slot:content>
</x-slot:actions> <x-forms.button @click="slideOverOpen=true" class="font-bold box w-96"
<x-slot:explanation> wire:click.prevent='installServer' isHighlighted>
<p>This will install the latest Docker Engine on your server, configure a few things to be able Let's do it!
to run optimal.</p> </x-forms.button>
</x-slot:explanation> </x-slide-over>
</x-boarding-step> </x-slot:actions>
@endif <x-slot:explanation>
</div> --}} <p>This will install the latest Docker Engine on your server, configure a few things to be able
<div> to run optimal.<br><br>Minimum Docker Engine version is: 22<br><br>To manually install
@if ($currentState === 'create-project') Docker
<x-boarding-step title="Project"> Engine, check <a target="_blank" class="underline text-warning"
<x-slot:question> href="https://docs.docker.com/engine/install/#server">this
@if (count($projects) > 0) documentation</a>.</p>
You already have some projects. Do you want to use one of them or should I create a new one for </x-slot:explanation>
you? </x-boarding-step>
@else @endif
Let's create an initial project for you. You can change all the details later on. </div>
@endif <div>
</x-slot:question> @if ($state === 'create-project')
<x-slot:actions> <x-boarding-step title="Project">
<x-forms.button class="justify-center w-64 box" wire:click="createNewProject">Create new <x-slot:question>
project!</x-forms.button>
<div>
@if (count($projects) > 0) @if (count($projects) > 0)
<form wire:submit='selectExistingProject' class="flex flex-col w-full gap-4 lg:w-96"> You already have some projects. Do you want to use one of them or should I create a new one
<x-forms.select label="Existing projects" class="w-96" for
id='selectedExistingProject'> you?
@foreach ($projects as $project) @else
<option wire:key="{{ $loop->index }}" value="{{ $project->id }}"> Let's create an initial project for you. You can change all the details later on.
{{ $project->name }}</option>
@endforeach
</x-forms.select>
<x-forms.button type="submit">Use this Project</x-forms.button>
</form>
@endif @endif
</div> </x-slot:question>
</x-slot:actions> <x-slot:actions>
<x-slot:explanation> <x-forms.button class="justify-center w-64 box" wire:click="createNewProject">Create new
<p>Projects contain several resources combined into one virtual group. There are no project!</x-forms.button>
limitations on the number of projects you can add.</p> <div>
<p>Each project should have at least one environment, this allows you to create a production & @if (count($projects) > 0)
staging version of the same application, but grouped separately.</p> <form wire:submit='selectExistingProject' class="flex flex-col w-full gap-4 lg:w-96">
</x-slot:explanation> <x-forms.select label="Existing projects" class="w-96" id='selectedProject'>
</x-boarding-step> @foreach ($projects as $project)
@endif <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:explanation>
<p>Projects contain several resources combined into one virtual group. There are no
limitations on the number of projects you can add.</p>
<p>Each project should have at least one environment, this allows you to create a production &
staging version of the same application, but grouped separately.</p>
</x-slot:explanation>
</x-boarding-step>
@endif
</div>
<div>
@if ($state === 'create-resource')
<x-boarding-step title="Resources">
<x-slot:question>
Let's go to the new resource page, where you can create your first resource.
</x-slot:question>
<x-slot:actions>
<div class="items-center justify-center w-64 box" wire:click="showNewResource">Let's do
it!</div>
</x-slot:actions>
<x-slot:explanation>
<p>A resource could be an application, a database or a service (like WordPress).</p>
</x-slot:explanation>
</x-boarding-step>
@endif
</div>
<div class="flex justify-center gap-2 pt-4">
<a wire:click='skipBoarding'>Skip boarding process</a>
<a wire:click='restartBoarding'>Restart boarding process</a>
</div>
</div> </div>
<div> </section>
@if ($currentState === 'create-resource')
<x-boarding-step title="Resources">
<x-slot:question>
Let's go to the new resource page, where you can create your first resource.
</x-slot:question>
<x-slot:actions>
<div class="items-center justify-center w-64 box" wire:click="showNewResource">Let's do
it!</div>
</x-slot:actions>
<x-slot:explanation>
<p>A resource could be an application, a database or a service (like WordPress).</p>
</x-slot:explanation>
</x-boarding-step>
@endif
</div>
<div class="flex justify-center gap-2 pt-4">
<a wire:click='skipBoarding'>Skip boarding process</a>
<a wire:click='restartBoarding'>Restart boarding process</a>
</div>
</div>

View File

@ -5,7 +5,7 @@
<h1 class="title">Dashboard</h1> <h1 class="title">Dashboard</h1>
{{-- <div class="subtitle">Your self-hosted environment</div> --}} {{-- <div class="subtitle">Your self-hosted environment</div> --}}
@if (request()->query->get('success')) @if (request()->query->get('success'))
<div class="mb-10 text-white rounded alert-success"> <div class="mb-10 rounded dark:text-white alert-success">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none" <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none"
viewBox="0 0 24 24"> viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
@ -22,26 +22,26 @@
@if (data_get($project, 'environments')->count() === 1) @if (data_get($project, 'environments')->count() === 1)
<a class="flex flex-col flex-1 mx-6 hover:no-underline" <a class="flex flex-col flex-1 mx-6 hover:no-underline"
href="{{ route('project.resource.index', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}"> href="{{ route('project.resource.index', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
<div class="font-bold text-white">{{ $project->name }}</div> <div class="box-title">{{ $project->name }}</div>
<div class="description"> <div class="box-description">
{{ $project->description }}</div> {{ $project->description }}</div>
</a> </a>
@else @else
<a class="flex flex-col flex-1 mx-6 hover:no-underline" <a class="flex flex-col flex-1 mx-6 hover:no-underline"
href="{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}"> href="{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}">
<div class="font-bold text-white">{{ $project->name }}</div> <div class="box-title">{{ $project->name }}</div>
<div class="description"> <div class="box-description">
{{ $project->description }}</div> {{ $project->description }}</div>
</a> </a>
@endif @endif
<div class="flex items-center"> <div class="flex items-center group">
<a class="mx-4 rounded group-hover:text-white hover:no-underline" <a class="mx-4 rounded hover:no-underline"
href="{{ route('project.resource.create', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}"> href="{{ route('project.resource.create', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
<span class="font-bold hover:text-warning">+ Add Resource</span> <span class="p-2 font-bold group-hover:dark:text-white group-hover:text-black dark:hover:bg-coollabs hover:bg-neutral-300">+ Add Resource</span>
</a> </a>
<a class="mx-4 rounded group-hover:text-white" <a class="mx-4 rounded group-hover:dark:text-white group-hover:text-black"
href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}"> href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon hover:text-warning" viewBox="0 0 24 24" <svg xmlns="http://www.w3.org/2000/svg" class="icon hover:text-coollabs" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round"> stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />
@ -54,9 +54,9 @@
</div> </div>
@empty @empty
<div> <div>
No projects found. Add your first server <a class="text-white underline" No projects found. Add your first server <a class="underline dark:text-white"
onclick="newEmptyProject.showModal()">here</a> or onclick="newEmptyProject.showModal()">here</a> or
go to the <a class="text-white underline" href="{{ route('onboarding') }}">onboarding page.</a> go to the <a class="underline dark:text-white" href="{{ route('onboarding') }}">onboarding page.</a>
<livewire:project.add-empty /> <livewire:project.add-empty />
</div> </div>
@endforelse @endforelse
@ -71,10 +71,10 @@
'border-red-500' => !$server->settings->is_reachable, 'border-red-500' => !$server->settings->is_reachable,
])> ])>
<div class="flex flex-col mx-6"> <div class="flex flex-col mx-6">
<div class="font-bold text-white"> <div class="box-title">
{{ $server->name }} {{ $server->name }}
</div> </div>
<div class="description"> <div class="box-description">
{{ $server->description }}</div> {{ $server->description }}</div>
<div class="flex gap-1 text-xs text-error"> <div class="flex gap-1 text-xs text-error">
@if (!$server->settings->is_reachable) @if (!$server->settings->is_reachable)
@ -93,8 +93,9 @@
@empty @empty
<div> <div>
No servers found. No servers found.
Add your first server <a class="text-white underline" href="{{ route('server.create') }}">here</a> or Add your first server <a class="underline dark:text-white" href="{{ route('server.create') }}">here</a>
go to the <a class="text-white underline" href="{{ route('onboarding') }}">onboarding page.</a> or
go to the <a class="underline dark:text-white" href="{{ route('onboarding') }}">onboarding page.</a>
</div> </div>
@endforelse @endforelse
</div> </div>
@ -116,15 +117,15 @@
'border-yellow-500' => data_get($deployment, 'status') === 'in_progress', 'border-yellow-500' => data_get($deployment, 'status') === 'in_progress',
])> ])>
<div class="flex flex-col mx-6"> <div class="flex flex-col mx-6">
<div class="font-bold text-white"> <div class="box-title">
{{ data_get($deployment, 'application_name') }} {{ data_get($deployment, 'application_name') }}
</div> </div>
@if (data_get($deployment, 'pull_request_id') !== 0) @if (data_get($deployment, 'pull_request_id') !== 0)
<div class="description"> <div class="box-description">
PR #{{ data_get($deployment, 'pull_request_id') }} PR #{{ data_get($deployment, 'pull_request_id') }}
</div> </div>
@endif @endif
<div class="description"> <div class="box-description">
{{ str(data_get($deployment, 'status'))->headline() }} {{ str(data_get($deployment, 'status'))->headline() }}
</div> </div>
</div> </div>

View File

@ -12,7 +12,7 @@
</x-slide-over> </x-slide-over>
@endif @endif
</div> </div>
<div class="subtitle">All your projects.</div> <div class="subtitle">All your projects are here.</div>
<div class="grid gap-2 lg:grid-cols-2"> <div class="grid gap-2 lg:grid-cols-2">
@if ($servers === 0) @if ($servers === 0)
<div> <div>

View File

@ -52,7 +52,7 @@ class="items-center justify-center box">+ Add New Resource</a>
<a class="h-24 box group" :href="item.hrefLink"> <a class="h-24 box group" :href="item.hrefLink">
<div class="flex flex-col w-full px-4 mx-2"> <div class="flex flex-col w-full px-4 mx-2">
<div class="flex gap-2"> <div class="flex gap-2">
<div class="pb-2 font-bold text-white" x-text="item.name"></div> <div class="pb-2 box-title" x-text="item.name"></div>
<template x-if="item.status.startsWith('running')"> <template x-if="item.status.startsWith('running')">
<div title="running" class="mt-1 bg-success badge "></div> <div title="running" class="mt-1 bg-success badge "></div>
</template> </template>
@ -66,16 +66,16 @@ class="items-center justify-center box">+ Add New Resource</a>
<div title="degraded" class="mt-1 bg-warning badge "></div> <div title="degraded" class="mt-1 bg-warning badge "></div>
</template> </template>
</div> </div>
<div class="max-w-full truncate description" x-text="item.description"></div> <div class="max-w-full truncate box-description" x-text="item.description"></div>
<div class="max-w-full truncate description" x-text="item.fqdn"></div> <div class="max-w-full truncate box-description" x-text="item.fqdn"></div>
</div> </div>
</a> </a>
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6"> <div class="flex gap-1 pt-1 group-hover:dark:text-white group-hover:text-black group min-h-6">
<template x-for="tag in item.tags"> <template x-for="tag in item.tags">
<div class="px-2 py-1 cursor-pointer description bg-coolgray-100 hover:bg-coolgray-300" <div class="px-2 py-1 cursor-pointer box-description dark:bg-coolgray-100 dark:hover:bg-coolgray-300 bg-neutral-100 hover:bg-neutral-200"
@click.prevent="gotoTag(tag.name)" x-text="tag.name"></div> @click.prevent="gotoTag(tag.name)" x-text="tag.name"></div>
</template> </template>
<div class="flex items-center px-2 text-xs cursor-pointer text-neutral-500/20 group-hover:text-white hover:bg-coolgray-300" <div class="flex items-center px-2 text-xs cursor-pointer dark:text-neutral-500/20 text-neutral-500 group-hover:text-neutral-700 group-hover:dark:text-white dark:hover:bg-coolgray-300 hover:bg-neutral-200"
@click.prevent="goto(item)">Add tag</div> @click.prevent="goto(item)">Add tag</div>
</div> </div>
</span> </span>
@ -85,7 +85,7 @@ class="items-center justify-center box">+ Add New Resource</a>
<a class="h-24 box group" :href="item.hrefLink"> <a class="h-24 box group" :href="item.hrefLink">
<div class="flex flex-col px-4 mx-2"> <div class="flex flex-col px-4 mx-2">
<div class="flex gap-2"> <div class="flex gap-2">
<div class="pb-2 font-bold text-white" x-text="item.name"></div> <div class="pb-2 font-bold box-title" x-text="item.name"></div>
<template x-if="item.status.startsWith('running')"> <template x-if="item.status.startsWith('running')">
<div title="running" class="mt-1 bg-success badge "></div> <div title="running" class="mt-1 bg-success badge "></div>
</template> </template>
@ -99,10 +99,10 @@ class="items-center justify-center box">+ Add New Resource</a>
<div title="degraded" class="mt-1 bg-warning badge "></div> <div title="degraded" class="mt-1 bg-warning badge "></div>
</template> </template>
</div> </div>
<div class="max-w-full truncate description" x-text="item.description"></div> <div class="max-w-full truncate box-description" x-text="item.description"></div>
</div> </div>
</a> </a>
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6"> <div class="flex gap-1 pt-1 group-hover:dark:text-white group-hover:text-black group min-h-6">
<template x-for="tag in item.tags"> <template x-for="tag in item.tags">
<div class="px-2 py-1 cursor-pointer description bg-coolgray-100 hover:bg-coolgray-300" <div class="px-2 py-1 cursor-pointer description bg-coolgray-100 hover:bg-coolgray-300"
@click.prevent="gotoTag(tag.name)" x-text="tag.name"></div> @click.prevent="gotoTag(tag.name)" x-text="tag.name"></div>

View File

@ -5,7 +5,7 @@
<x-forms.button >+ Add</x-forms.button> <x-forms.button >+ Add</x-forms.button>
</a> </a>
</div> </div>
<div class="subtitle">All your projects.</div> <div class="subtitle">All your servers are here.</div>
<div class="grid gap-2 lg:grid-cols-2"> <div class="grid gap-2 lg:grid-cols-2">
@forelse ($servers as $server) @forelse ($servers as $server)

View File

@ -4,14 +4,14 @@
</div> </div>
<div class="pb-4 ">Email settings for password resets, invitations, shared with Pro+ subscribers etc.</div> <div class="pb-4 ">Email settings for password resets, invitations, shared with Pro+ subscribers etc.</div>
<form wire:submit='submitFromFields' class="pb-4"> <form wire:submit='submitFromFields' class="pb-4">
<div class="flex flex-col items-end w-full gap-2 xl:flex-row"> <div class="flex gap-2">
<x-forms.input required id="settings.smtp_from_name" helper="Name used in emails." label="From Name" /> <x-forms.input required id="settings.smtp_from_name" helper="Name used in emails." label="From Name" />
<x-forms.input required id="settings.smtp_from_address" helper="Email address used in emails." <x-forms.input required id="settings.smtp_from_address" helper="Email address used in emails."
label="From Address" /> label="From Address" />
<x-forms.button type="submit"> <x-forms.button type="submit">
Save Save
</x-forms.button> </x-forms.button>
@if (isEmailEnabled($settings) && auth()->user()->isAdminFromSession() && isTestEmailEnabled($settings)) {{-- @if (isEmailEnabled($settings) && auth()->user()->isAdminFromSession() && isTestEmailEnabled($settings))
<x-modal-input buttonTitle="Send Test Email" title="Send Test Email"> <x-modal-input buttonTitle="Send Test Email" title="Send Test Email">
<form wire:submit='submit' class="flex flex-col gap-2"> <form wire:submit='submit' class="flex flex-col gap-2">
<x-forms.input placeholder="test@example.com" id="emails" label="Recipients" required /> <x-forms.input placeholder="test@example.com" id="emails" label="Recipients" required />
@ -21,16 +21,21 @@
</x-forms.button> </x-forms.button>
</form> </form>
</x-modal-input> </x-modal-input>
@endif @endif --}}
</div> </div>
</form> </form>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="p-4 border border-coolgray-500"> <div class="p-4 border border-coolgray-500">
<h3>SMTP Server</h3>
<div class="w-32">
<x-forms.checkbox instantSave id="settings.smtp_enabled" label="Enabled" />
</div>
<form wire:submit='submit' class="flex flex-col"> <form wire:submit='submit' class="flex flex-col">
<div class="flex gap-2">
<h3>SMTP Server</h3>
<x-forms.button type="submit">
Save
</x-forms.button>
</div>
<div class="w-32">
<x-forms.checkbox instantSave id="settings.smtp_enabled" label="Enabled" />
</div>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="flex flex-col w-full gap-2 xl:flex-row"> <div class="flex flex-col w-full gap-2 xl:flex-row">
<x-forms.input required id="settings.smtp_host" placeholder="smtp.mailgun.org" label="Host" /> <x-forms.input required id="settings.smtp_host" placeholder="smtp.mailgun.org" label="Host" />
@ -45,30 +50,25 @@
label="Timeout" /> label="Timeout" />
</div> </div>
</div> </div>
<div class="flex justify-end gap-4 pt-6"> </form>
</div>
<div class="p-4 border border-coolgray-500">
<form wire:submit='submitResend' class="flex flex-col">
<div class="flex gap-2">
<h3>Resend</h3>
<x-forms.button type="submit"> <x-forms.button type="submit">
Save Save
</x-forms.button> </x-forms.button>
</div> </div>
</form> <div class="w-32">
</div> <x-forms.checkbox instantSave='instantSaveResend' id="settings.resend_enabled" label="Enabled" />
<div class="p-4 border border-coolgray-500"> </div>
<h3>Resend</h3>
<div class="w-32">
<x-forms.checkbox instantSave='instantSaveResend' id="settings.resend_enabled" label="Enabled" />
</div>
<form wire:submit='submitResend' class="flex flex-col">
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="flex flex-col w-full gap-2 xl:flex-row"> <div class="flex flex-col w-full gap-2 xl:flex-row">
<x-forms.input type="password" id="settings.resend_api_key" placeholder="API key" required <x-forms.input type="password" id="settings.resend_api_key" placeholder="API key" required
label="Host" /> label="Host" />
</div> </div>
</div> </div>
<div class="flex justify-end gap-4 pt-6">
<x-forms.button type="submit">
Save
</x-forms.button>
</div>
</form> </form>
</div> </div>
</div> </div>

View File

@ -10,7 +10,7 @@
@click.prevent="activeTab = 'smtp'; window.location.hash = 'smtp'" href="#">Transactional @click.prevent="activeTab = 'smtp'; window.location.hash = 'smtp'" href="#">Transactional
Email</a> Email</a>
<a :class="activeTab === 'auth' && 'text-white'" <a :class="activeTab === 'auth' && 'text-white'"
@click.prevent="activeTab = 'auth'; window.location.hash = 'auth'" href="#">Authentication</a> @click.prevent="activeTab = 'auth'; window.location.hash = 'auth'" href="#">Authentication (OAuth)</a>
</div> </div>
<div class="w-full pl-8"> <div class="w-full pl-8">
<div x-cloak x-show="activeTab === 'general'" class="h-full"> <div x-cloak x-show="activeTab === 'general'" class="h-full">