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" />
/ or ⌘K to focus diff --git a/resources/views/livewire/server/new/by-hetzner.blade.php b/resources/views/livewire/server/new/by-hetzner.blade.php index 552d6b097..e95c85887 100644 --- a/resources/views/livewire/server/new/by-hetzner.blade.php +++ b/resources/views/livewire/server/new/by-hetzner.blade.php @@ -18,8 +18,7 @@
- + Continue
@@ -111,16 +110,12 @@ -
- -
-
Back - Create Server + Buy & Create Server
diff --git a/resources/views/livewire/server/show.blade.php b/resources/views/livewire/server/show.blade.php index f1f1180e8..f47f24b20 100644 --- a/resources/views/livewire/server/show.blade.php +++ b/resources/views/livewire/server/show.blade.php @@ -1,4 +1,4 @@ -
+
{{ data_get_str($server, 'name')->limit(10) }} > General | Coolify @@ -10,13 +10,68 @@

General

@if ($server->hetzner_server_id) -
+
- Hetzner + @if ($hetznerServerStatus) + + @if (in_array($hetznerServerStatus, ['starting', 'initializing'])) + + + + + + @endif + $hetznerServerStatus === 'running', + 'text-red-500' => $hetznerServerStatus === 'off', + ])> + {{ ucfirst($hetznerServerStatus) }} + + + @else + + + + + + + Checking status... + + @endif +
+ @if ($server->cloudProviderToken && !$server->isFunctional() && $hetznerServerStatus === 'off') + + Power On + + @endif + @endif + @if ($isValidating) +
+ + + + + + Validating...
@endif @if ($server->id === 0) @@ -26,7 +81,8 @@ class="flex items-center gap-1 px-2 py-1 text-xs font-semibold rounded bg-white ]" :confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Save" canGate="update" :canResource="$server" /> @else - Save + Save @if ($server->isFunctional()) Validate & configure @@ -46,7 +102,21 @@ class="flex items-center gap-1 px-2 py-1 text-xs font-semibold rounded bg-white @else You can't use this server until it is validated. @endif - @if ((!$isReachable || !$isUsable) && $server->id !== 0) + @if ($isValidating) +
+ + Validation in Progress + + + + +
+ @endif + @if ( + (!$isReachable || !$isUsable) && + $server->id !== 0 && + !$isValidating && + !in_array($hetznerServerStatus, ['initializing', 'starting', 'stopping', 'off'])) Validate & configure @@ -79,12 +149,15 @@ class="mt-8 mb-4 w-full font-bold box-without-bg bg-coollabs hover:bg-coollabs-1 @endif
- - + + @if (!$isSwarmWorker && !$isBuildServer) + helper='A wildcard domain allows you to receive a randomly generated domain for your new applications.

For instance, if you set "https://example.com" as your wildcard domain, your applications will receive domains like "https://randomId.example.com".' + :disabled="$isValidating" /> @endif
@@ -92,11 +165,12 @@ class="mt-8 mb-4 w-full font-bold box-without-bg bg-coollabs hover:bg-coollabs-1 + required :disabled="$isValidating" />
- + + label="Port" required :disabled="$isValidating" />
@@ -106,45 +180,63 @@ class="mt-8 mb-4 w-full font-bold box-without-bg bg-coollabs hover:bg-coollabs-1 helper="Server's timezone. This is used for backups, cron jobs, etc." />
@can('update', $server) -
+ @if ($isValidating)
- - +
-
- +
+ @else +
+
+
+ + + + +
+
+ +
-
+ @endif @else
@@ -171,7 +263,7 @@ class="w-full input opacity-50 cursor-not-allowed" label="Use it as a build server?" /> @else + id="isBuildServer" label="Use it as a build server?" :disabled="$isValidating" /> @endif
@@ -191,7 +283,7 @@ class="w-full input opacity-50 cursor-not-allowed" + label="Is it a Swarm Manager?" :disabled="$isValidating" /> @endif @if ($server->settings->is_swarm_manager) @@ -202,7 +294,7 @@ class="w-full input opacity-50 cursor-not-allowed" + label="Is it a Swarm Worker?" :disabled="$isValidating" /> @endif
@endif