From bf5c08d071d5374cf2119de8c21301b151298aff Mon Sep 17 00:00:00 2001
From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com>
Date: Thu, 9 Oct 2025 16:54:13 +0200
Subject: [PATCH] work work on hetzner integration
---
app/Actions/Server/DeleteServer.php | 67 +++++--
app/Events/ServerValidated.php | 51 +++++
.../Controllers/Api/ServersController.php | 8 +-
app/Jobs/ServerConnectionCheckJob.php | 36 ++++
app/Jobs/ValidateAndInstallServerJob.php | 162 +++++++++++++++
app/Livewire/Server/Delete.php | 8 +-
app/Livewire/Server/New/ByHetzner.php | 5 +-
app/Livewire/Server/Show.php | 92 +++++++++
app/Livewire/Server/ValidateAndInstall.php | 5 +
app/Models/Server.php | 8 +
app/Services/HetznerService.php | 14 ++
...hetzner_server_status_to_servers_table.php | 28 +++
...036_add_is_validating_to_servers_table.php | 28 +++
.../views/livewire/global-search.blade.php | 2 +-
.../livewire/server/new/by-hetzner.blade.php | 9 +-
.../views/livewire/server/show.blade.php | 184 +++++++++++++-----
16 files changed, 628 insertions(+), 79 deletions(-)
create mode 100644 app/Events/ServerValidated.php
create mode 100644 app/Jobs/ValidateAndInstallServerJob.php
create mode 100644 database/migrations/2025_10_09_113602_add_hetzner_server_status_to_servers_table.php
create mode 100644 database/migrations/2025_10_09_125036_add_is_validating_to_servers_table.php
diff --git a/app/Actions/Server/DeleteServer.php b/app/Actions/Server/DeleteServer.php
index b7523714f..1b4302911 100644
--- a/app/Actions/Server/DeleteServer.php
+++ b/app/Actions/Server/DeleteServer.php
@@ -11,57 +11,86 @@ class DeleteServer
{
use AsAction;
- public function handle(Server $server, bool $deleteFromHetzner = false)
+ public function handle(int $serverId, bool $deleteFromHetzner = false, ?int $hetznerServerId = null, ?int $cloudProviderTokenId = null, ?int $teamId = null)
{
- // Delete from Hetzner Cloud if requested and server has hetzner_server_id
- if ($deleteFromHetzner && $server->hetzner_server_id) {
- $this->deleteFromHetzner($server);
+ $server = Server::withTrashed()->find($serverId);
+
+ // Delete from Hetzner even if server is already gone from Coolify
+ if ($deleteFromHetzner && ($hetznerServerId || ($server && $server->hetzner_server_id))) {
+ $this->deleteFromHetznerById(
+ $hetznerServerId ?? $server->hetzner_server_id,
+ $cloudProviderTokenId ?? $server->cloud_provider_token_id,
+ $teamId ?? $server->team_id
+ );
}
- StopSentinel::run($server);
- $server->forceDelete();
+ ray($server ? 'Deleting server from Coolify' : 'Server already deleted from Coolify, skipping Coolify deletion');
+
+ // If server is already deleted from Coolify, skip this part
+ if (! $server) {
+ return; // Server already force deleted from Coolify
+ }
+
+ ray('force deleting server from Coolify', ['server_id' => $server->id]);
+
+ try {
+ $server->forceDelete();
+ } catch (\Throwable $e) {
+ ray('Failed to force delete server from Coolify', [
+ 'error' => $e->getMessage(),
+ 'server_id' => $server->id,
+ ]);
+ logger()->error('Failed to force delete server from Coolify', [
+ 'error' => $e->getMessage(),
+ 'server_id' => $server->id,
+ ]);
+ }
}
- private function deleteFromHetzner(Server $server): void
+ private function deleteFromHetznerById(int $hetznerServerId, ?int $cloudProviderTokenId, int $teamId): void
{
try {
- // Use the server's associated token, or fallback to first available team token
- $token = $server->cloudProviderToken;
+ // Use the provided token, or fallback to first available team token
+ $token = null;
+
+ if ($cloudProviderTokenId) {
+ $token = CloudProviderToken::find($cloudProviderTokenId);
+ }
if (! $token) {
- $token = CloudProviderToken::where('team_id', $server->team_id)
+ $token = CloudProviderToken::where('team_id', $teamId)
->where('provider', 'hetzner')
->first();
}
if (! $token) {
ray('No Hetzner token found for team, skipping Hetzner deletion', [
- 'team_id' => $server->team_id,
- 'server_id' => $server->id,
+ 'team_id' => $teamId,
+ 'hetzner_server_id' => $hetznerServerId,
]);
return;
}
$hetznerService = new HetznerService($token->token);
- $hetznerService->deleteServer($server->hetzner_server_id);
+ $hetznerService->deleteServer($hetznerServerId);
ray('Deleted server from Hetzner', [
- 'hetzner_server_id' => $server->hetzner_server_id,
- 'server_id' => $server->id,
+ 'hetzner_server_id' => $hetznerServerId,
+ 'team_id' => $teamId,
]);
} catch (\Throwable $e) {
ray('Failed to delete server from Hetzner', [
'error' => $e->getMessage(),
- 'hetzner_server_id' => $server->hetzner_server_id,
- 'server_id' => $server->id,
+ 'hetzner_server_id' => $hetznerServerId,
+ 'team_id' => $teamId,
]);
// Log the error but don't prevent the server from being deleted from Coolify
logger()->error('Failed to delete server from Hetzner', [
'error' => $e->getMessage(),
- 'hetzner_server_id' => $server->hetzner_server_id,
- 'server_id' => $server->id,
+ 'hetzner_server_id' => $hetznerServerId,
+ 'team_id' => $teamId,
]);
}
}
diff --git a/app/Events/ServerValidated.php b/app/Events/ServerValidated.php
new file mode 100644
index 000000000..95a116ebe
--- /dev/null
+++ b/app/Events/ServerValidated.php
@@ -0,0 +1,51 @@
+check() && auth()->user()->currentTeam()) {
+ $teamId = auth()->user()->currentTeam()->id;
+ }
+ $this->teamId = $teamId;
+ $this->serverUuid = $serverUuid;
+ }
+
+ public function broadcastOn(): array
+ {
+ if (is_null($this->teamId)) {
+ return [];
+ }
+
+ return [
+ new PrivateChannel("team.{$this->teamId}"),
+ ];
+ }
+
+ public function broadcastAs(): string
+ {
+ return 'ServerValidated';
+ }
+
+ public function broadcastWith(): array
+ {
+ return [
+ 'teamId' => $this->teamId,
+ 'serverUuid' => $this->serverUuid,
+ ];
+ }
+}
diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php
index cbd20400a..811938386 100644
--- a/app/Http/Controllers/Api/ServersController.php
+++ b/app/Http/Controllers/Api/ServersController.php
@@ -746,7 +746,13 @@ public function delete_server(Request $request)
return response()->json(['message' => 'Local server cannot be deleted.'], 400);
}
$server->delete();
- DeleteServer::dispatch($server);
+ DeleteServer::dispatch(
+ $server->id,
+ false, // Don't delete from Hetzner via API
+ $server->hetzner_server_id,
+ $server->cloud_provider_token_id,
+ $server->team_id
+ );
return response()->json(['message' => 'Server deleted.']);
}
diff --git a/app/Jobs/ServerConnectionCheckJob.php b/app/Jobs/ServerConnectionCheckJob.php
index 8b55434f6..5cb10146d 100644
--- a/app/Jobs/ServerConnectionCheckJob.php
+++ b/app/Jobs/ServerConnectionCheckJob.php
@@ -54,6 +54,11 @@ public function handle()
return;
}
+ // Check Hetzner server status if applicable
+ if ($this->server->hetzner_server_id && $this->server->cloudProviderToken) {
+ $this->checkHetznerStatus();
+ }
+
// Temporarily disable mux if requested
if ($this->disableMux) {
$this->disableSshMux();
@@ -95,6 +100,37 @@ public function handle()
}
}
+ private function checkHetznerStatus(): void
+ {
+ try {
+ $hetznerService = new \App\Services\HetznerService($this->server->cloudProviderToken->token);
+ $serverData = $hetznerService->getServer($this->server->hetzner_server_id);
+ $status = $serverData['status'] ?? null;
+
+ // Save status to database
+ $this->server->update(['hetzner_server_status' => $status]);
+
+ // If Hetzner reports server is off, mark as unreachable
+ if ($status === 'off') {
+ $this->server->settings->update([
+ 'is_reachable' => false,
+ 'is_usable' => false,
+ ]);
+
+ Log::info('ServerConnectionCheck: Hetzner server is powered off', [
+ 'server_id' => $this->server->id,
+ 'server_name' => $this->server->name,
+ 'hetzner_status' => $status,
+ ]);
+ }
+ } catch (\Throwable $e) {
+ Log::debug('ServerConnectionCheck: Hetzner status check failed', [
+ 'server_id' => $this->server->id,
+ 'error' => $e->getMessage(),
+ ]);
+ }
+ }
+
private function checkConnection(): bool
{
try {
diff --git a/app/Jobs/ValidateAndInstallServerJob.php b/app/Jobs/ValidateAndInstallServerJob.php
new file mode 100644
index 000000000..388791f10
--- /dev/null
+++ b/app/Jobs/ValidateAndInstallServerJob.php
@@ -0,0 +1,162 @@
+onQueue('high');
+ }
+
+ public function handle(): void
+ {
+ try {
+ // Mark validation as in progress
+ $this->server->update(['is_validating' => true]);
+
+ Log::info('ValidateAndInstallServer: Starting validation', [
+ 'server_id' => $this->server->id,
+ 'server_name' => $this->server->name,
+ 'attempt' => $this->numberOfTries + 1,
+ ]);
+
+ // Validate connection
+ ['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
+ if (! $uptime) {
+ $errorMessage = 'Server is not reachable. Please validate your configuration and connection.
Check this documentation for further help.
Error: '.$error;
+ $this->server->update([
+ 'validation_logs' => $errorMessage,
+ 'is_validating' => false,
+ ]);
+ Log::error('ValidateAndInstallServer: Server not reachable', [
+ 'server_id' => $this->server->id,
+ 'error' => $error,
+ ]);
+
+ return;
+ }
+
+ // Validate OS
+ $supportedOsType = $this->server->validateOS();
+ if (! $supportedOsType) {
+ $errorMessage = 'Server OS type is not supported. Please install Docker manually before continuing: documentation.';
+ $this->server->update([
+ 'validation_logs' => $errorMessage,
+ 'is_validating' => false,
+ ]);
+ Log::error('ValidateAndInstallServer: OS not supported', [
+ 'server_id' => $this->server->id,
+ ]);
+
+ return;
+ }
+
+ // Check if Docker is installed
+ $dockerInstalled = $this->server->validateDockerEngine();
+ $dockerComposeInstalled = $this->server->validateDockerCompose();
+
+ if (! $dockerInstalled || ! $dockerComposeInstalled) {
+ // Try to install Docker
+ if ($this->numberOfTries >= $this->maxTries) {
+ $errorMessage = 'Docker Engine could not be installed after '.$this->maxTries.' attempts. Please install Docker manually before continuing: documentation.';
+ $this->server->update([
+ 'validation_logs' => $errorMessage,
+ 'is_validating' => false,
+ ]);
+ Log::error('ValidateAndInstallServer: Docker installation failed after max tries', [
+ 'server_id' => $this->server->id,
+ 'attempts' => $this->numberOfTries,
+ ]);
+
+ return;
+ }
+
+ Log::info('ValidateAndInstallServer: Installing Docker', [
+ 'server_id' => $this->server->id,
+ 'attempt' => $this->numberOfTries + 1,
+ ]);
+
+ // Install Docker
+ $this->server->installDocker();
+
+ // Retry validation after installation
+ self::dispatch($this->server, $this->numberOfTries + 1)->delay(now()->addSeconds(30));
+
+ return;
+ }
+
+ // Validate Docker version
+ $dockerVersion = $this->server->validateDockerEngineVersion();
+ if (! $dockerVersion) {
+ $requiredDockerVersion = str(config('constants.docker.minimum_required_version'))->before('.');
+ $errorMessage = 'Minimum Docker Engine version '.$requiredDockerVersion.' is not installed. Please install Docker manually before continuing: documentation.';
+ $this->server->update([
+ 'validation_logs' => $errorMessage,
+ 'is_validating' => false,
+ ]);
+ Log::error('ValidateAndInstallServer: Docker version not sufficient', [
+ 'server_id' => $this->server->id,
+ ]);
+
+ return;
+ }
+
+ // Validation successful!
+ Log::info('ValidateAndInstallServer: Validation successful', [
+ 'server_id' => $this->server->id,
+ 'server_name' => $this->server->name,
+ ]);
+
+ // Start proxy if needed
+ if (! $this->server->isBuildServer()) {
+ $proxyShouldRun = CheckProxy::run($this->server, true);
+ if ($proxyShouldRun) {
+ StartProxy::dispatch($this->server);
+ }
+ }
+
+ // Mark validation as complete
+ $this->server->update(['is_validating' => false]);
+
+ // Refresh server to get latest state
+ $this->server->refresh();
+
+ // Broadcast events to update UI
+ ServerValidated::dispatch($this->server->team_id, $this->server->uuid);
+ ServerReachabilityChanged::dispatch($this->server);
+
+ } catch (\Throwable $e) {
+ Log::error('ValidateAndInstallServer: Exception occurred', [
+ 'server_id' => $this->server->id,
+ 'error' => $e->getMessage(),
+ 'trace' => $e->getTraceAsString(),
+ ]);
+
+ $this->server->update([
+ 'validation_logs' => 'An error occurred during validation: '.$e->getMessage(),
+ 'is_validating' => false,
+ ]);
+ }
+ }
+}
diff --git a/app/Livewire/Server/Delete.php b/app/Livewire/Server/Delete.php
index 6d12895eb..8c2c54c99 100644
--- a/app/Livewire/Server/Delete.php
+++ b/app/Livewire/Server/Delete.php
@@ -45,7 +45,13 @@ public function delete($password)
}
$this->server->delete();
- DeleteServer::dispatch($this->server, $this->delete_from_hetzner);
+ DeleteServer::dispatch(
+ $this->server->id,
+ $this->delete_from_hetzner,
+ $this->server->hetzner_server_id,
+ $this->server->cloud_provider_token_id,
+ $this->server->team_id
+ );
return redirect()->route('server.index');
} catch (\Throwable $e) {
diff --git a/app/Livewire/Server/New/ByHetzner.php b/app/Livewire/Server/New/ByHetzner.php
index d0a7582cd..c5559576d 100644
--- a/app/Livewire/Server/New/ByHetzner.php
+++ b/app/Livewire/Server/New/ByHetzner.php
@@ -49,8 +49,6 @@ class ByHetzner extends Component
public string $server_name = '';
- public bool $start_after_create = true;
-
public ?int $private_key_id = null;
public bool $loading_data = false;
@@ -111,7 +109,6 @@ protected function rules(): array
'selected_image' => 'required|integer',
'selected_server_type' => 'required|string',
'private_key_id' => 'required|integer|exists:private_keys,id,team_id,'.currentTeam()->id,
- 'start_after_create' => 'boolean',
]);
}
@@ -370,7 +367,7 @@ private function createHetznerServer(string $token): array
'server_type' => $this->selected_server_type,
'image' => $this->selected_image,
'location' => $this->selected_location,
- 'start_after_create' => $this->start_after_create,
+ 'start_after_create' => true,
'ssh_keys' => [$sshKeyId],
];
diff --git a/app/Livewire/Server/Show.php b/app/Livewire/Server/Show.php
index db4dc9b88..e758f0f54 100644
--- a/app/Livewire/Server/Show.php
+++ b/app/Livewire/Server/Show.php
@@ -67,13 +67,21 @@ class Show extends Component
public string $serverTimezone;
+ public ?string $hetznerServerStatus = null;
+
+ public bool $hetznerServerManuallyStarted = false;
+
+ public bool $isValidating = false;
+
public function getListeners()
{
$teamId = $this->server->team_id ?? auth()->user()->currentTeam()->id;
return [
'refreshServerShow' => 'refresh',
+ 'refreshServer' => '$refresh',
"echo-private:team.{$teamId},SentinelRestarted" => 'handleSentinelRestarted',
+ "echo-private:team.{$teamId},ServerValidated" => 'handleServerValidated',
];
}
@@ -138,6 +146,10 @@ public function mount(string $server_uuid)
if (! $this->server->isEmpty()) {
$this->isBuildServerLocked = true;
}
+ // Load saved Hetzner status and validation state
+ $this->hetznerServerStatus = $this->server->hetzner_server_status;
+ $this->isValidating = $this->server->is_validating ?? false;
+
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -218,6 +230,7 @@ public function syncData(bool $toModel = false)
$this->isSentinelDebugEnabled = $this->server->settings->is_sentinel_debug_enabled;
$this->sentinelUpdatedAt = $this->server->sentinel_updated_at;
$this->serverTimezone = $this->server->settings->server_timezone;
+ $this->isValidating = $this->server->is_validating ?? false;
}
}
@@ -361,6 +374,85 @@ public function instantSave()
}
}
+ public function checkHetznerServerStatus(bool $manual = false)
+ {
+ try {
+ if (! $this->server->hetzner_server_id || ! $this->server->cloudProviderToken) {
+ $this->dispatch('error', 'This server is not associated with a Hetzner Cloud server or token.');
+
+ return;
+ }
+
+ $hetznerService = new \App\Services\HetznerService($this->server->cloudProviderToken->token);
+ $serverData = $hetznerService->getServer($this->server->hetzner_server_id);
+
+ $this->hetznerServerStatus = $serverData['status'] ?? null;
+
+ // Save status to database
+ $this->server->update(['hetzner_server_status' => $this->hetznerServerStatus]);
+
+ if ($manual) {
+ $this->dispatch('success', 'Server status refreshed: '.ucfirst($this->hetznerServerStatus ?? 'unknown'));
+ }
+
+ // If Hetzner server is off but Coolify thinks it's still reachable, update Coolify's state
+ if ($this->hetznerServerStatus === 'off' && $this->server->settings->is_reachable) {
+ ['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
+ if ($uptime) {
+ $this->dispatch('success', 'Server is reachable.');
+ $this->server->settings->is_reachable = $this->isReachable = true;
+ $this->server->settings->is_usable = $this->isUsable = true;
+ $this->server->settings->save();
+ ServerReachabilityChanged::dispatch($this->server);
+ } else {
+ $this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.
Check this documentation for further help.
Error: '.$error);
+
+ return;
+ }
+ }
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
+
+ public function handleServerValidated($event = null)
+ {
+ // Check if event is for this server
+ if ($event && isset($event['serverUuid']) && $event['serverUuid'] !== $this->server->uuid) {
+ return;
+ }
+
+ // Refresh server data
+ $this->server->refresh();
+ $this->syncData();
+
+ // Update validation state
+ $this->isValidating = $this->server->is_validating ?? false;
+ $this->dispatch('refreshServerShow');
+ $this->dispatch('refreshServer');
+ }
+
+ public function startHetznerServer()
+ {
+ try {
+ if (! $this->server->hetzner_server_id || ! $this->server->cloudProviderToken) {
+ $this->dispatch('error', 'This server is not associated with a Hetzner Cloud server or token.');
+
+ return;
+ }
+
+ $hetznerService = new \App\Services\HetznerService($this->server->cloudProviderToken->token);
+ $hetznerService->powerOnServer($this->server->hetzner_server_id);
+
+ $this->hetznerServerStatus = 'starting';
+ $this->server->update(['hetzner_server_status' => 'starting']);
+ $this->hetznerServerManuallyStarted = true; // Set flag to trigger auto-validation when running
+ $this->dispatch('success', 'Hetzner server is starting...');
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
+
public function submit()
{
try {
diff --git a/app/Livewire/Server/ValidateAndInstall.php b/app/Livewire/Server/ValidateAndInstall.php
index bf0b7b6a5..39b473660 100644
--- a/app/Livewire/Server/ValidateAndInstall.php
+++ b/app/Livewire/Server/ValidateAndInstall.php
@@ -4,6 +4,7 @@
use App\Actions\Proxy\CheckProxy;
use App\Actions\Proxy\StartProxy;
+use App\Events\ServerValidated;
use App\Models\Server;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
@@ -136,8 +137,12 @@ public function validateDockerVersion()
} else {
$this->docker_version = $this->server->validateDockerEngineVersion();
if ($this->docker_version) {
+ // Mark validation as complete
+ $this->server->update(['is_validating' => false]);
+
$this->dispatch('refreshServerShow');
$this->dispatch('refreshBoardingIndex');
+ ServerValidated::dispatch($this->server->team_id, $this->server->uuid);
$this->dispatch('success', 'Server validated, proxy is starting in a moment.');
$proxyShouldRun = CheckProxy::run($this->server, true);
if (! $proxyShouldRun) {
diff --git a/app/Models/Server.php b/app/Models/Server.php
index e1a004755..92111f0f0 100644
--- a/app/Models/Server.php
+++ b/app/Models/Server.php
@@ -136,6 +136,7 @@ protected static function booted()
$destination->delete();
});
$server->settings()->delete();
+ $server->sslCertificates()->delete();
});
}
@@ -164,6 +165,8 @@ protected static function booted()
'cloud_provider_token_id',
'team_id',
'hetzner_server_id',
+ 'hetzner_server_status',
+ 'is_validating',
];
protected $guarded = [];
@@ -896,6 +899,11 @@ public function cloudProviderToken()
return $this->belongsTo(CloudProviderToken::class);
}
+ public function sslCertificates()
+ {
+ return $this->hasMany(SslCertificate::class);
+ }
+
public function muxFilename()
{
return 'mux_'.$this->uuid;
diff --git a/app/Services/HetznerService.php b/app/Services/HetznerService.php
index 039eb81a9..80afa02b9 100644
--- a/app/Services/HetznerService.php
+++ b/app/Services/HetznerService.php
@@ -89,6 +89,20 @@ public function createServer(array $params): array
return $response['server'] ?? [];
}
+ public function getServer(int $serverId): array
+ {
+ $response = $this->request('get', "/servers/{$serverId}");
+
+ return $response['server'] ?? [];
+ }
+
+ public function powerOnServer(int $serverId): array
+ {
+ $response = $this->request('post', "/servers/{$serverId}/actions/poweron");
+
+ return $response['action'] ?? [];
+ }
+
public function deleteServer(int $serverId): void
{
$this->request('delete', "/servers/{$serverId}");
diff --git a/database/migrations/2025_10_09_113602_add_hetzner_server_status_to_servers_table.php b/database/migrations/2025_10_09_113602_add_hetzner_server_status_to_servers_table.php
new file mode 100644
index 000000000..d94c9c76f
--- /dev/null
+++ b/database/migrations/2025_10_09_113602_add_hetzner_server_status_to_servers_table.php
@@ -0,0 +1,28 @@
+string('hetzner_server_status')->nullable()->after('hetzner_server_id');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('servers', function (Blueprint $table) {
+ $table->dropColumn('hetzner_server_status');
+ });
+ }
+};
diff --git a/database/migrations/2025_10_09_125036_add_is_validating_to_servers_table.php b/database/migrations/2025_10_09_125036_add_is_validating_to_servers_table.php
new file mode 100644
index 000000000..ddb655d2c
--- /dev/null
+++ b/database/migrations/2025_10_09_125036_add_is_validating_to_servers_table.php
@@ -0,0 +1,28 @@
+boolean('is_validating')->default(false)->after('hetzner_server_status');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('servers', function (Blueprint $table) {
+ $table->dropColumn('is_validating');
+ });
+ }
+};
diff --git a/resources/views/livewire/global-search.blade.php b/resources/views/livewire/global-search.blade.php
index 3bf29d392..d8cbcf2d8 100644
--- a/resources/views/livewire/global-search.blade.php
+++ b/resources/views/livewire/global-search.blade.php
@@ -266,7 +266,7 @@ class="fixed top-0 left-0 z-99 flex items-start justify-center w-screen h-screen
+ class="w-full pl-12 pr-32 py-4 text-base bg-white dark:bg-coolgray-100 border-none rounded-lg shadow-xl ring-1 ring-neutral-200 dark:ring-coolgray-300 dark:text-white placeholder-neutral-400 dark:placeholder-neutral-500 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-coollabs dark:focus-visible:ring-warning focus-visible:ring-offset-2 dark:focus-visible:ring-offset-base" />