server->team_id ?? auth()->user()->currentTeam()->id; return [ 'refreshServerShow' => 'refresh', 'refreshServer' => '$refresh', "echo-private:team.{$teamId},SentinelRestarted" => 'handleSentinelRestarted', "echo-private:team.{$teamId},ServerValidated" => 'handleServerValidated', ]; } protected function rules(): array { return [ 'name' => ValidationPatterns::nameRules(), 'description' => ValidationPatterns::descriptionRules(), 'ip' => ['required', new ValidServerIp], 'user' => ValidationPatterns::serverUsernameRules(), 'port' => 'required|integer|between:1,65535', 'connectionTimeout' => 'required|integer|min:1|max:300', 'validationLogs' => 'nullable', 'wildcardDomain' => 'nullable|url', 'isReachable' => 'required', 'isUsable' => 'required', 'isSwarmManager' => 'required', 'isSwarmWorker' => 'required', 'isBuildServer' => 'required', 'isMetricsEnabled' => 'required', 'sentinelToken' => 'required', 'sentinelUpdatedAt' => 'nullable', 'sentinelMetricsRefreshRateSeconds' => 'required|integer|min:1', 'sentinelMetricsHistoryDays' => 'required|integer|min:1', 'sentinelPushIntervalSeconds' => 'required|integer|min:10', 'sentinelCustomUrl' => 'nullable|url', 'isSentinelEnabled' => 'required', 'isSentinelDebugEnabled' => 'required', 'serverTimezone' => 'required', ]; } protected function messages(): array { return array_merge( ValidationPatterns::combinedMessages(), [ 'ip.required' => 'The IP Address field is required.', 'user.required' => 'The User field is required.', ...ValidationPatterns::serverUsernameMessages(), 'port.required' => 'The Port field is required.', 'connectionTimeout.required' => 'The SSH Connection Timeout field is required.', 'connectionTimeout.integer' => 'The SSH Connection Timeout must be an integer.', 'connectionTimeout.min' => 'The SSH Connection Timeout must be at least 1 second.', 'connectionTimeout.max' => 'The SSH Connection Timeout must not exceed 300 seconds.', 'wildcardDomain.url' => 'The Wildcard Domain must be a valid URL.', 'sentinelToken.required' => 'The Sentinel Token field is required.', 'sentinelMetricsRefreshRateSeconds.required' => 'The Metrics Refresh Rate field is required.', 'sentinelMetricsRefreshRateSeconds.integer' => 'The Metrics Refresh Rate must be an integer.', 'sentinelMetricsRefreshRateSeconds.min' => 'The Metrics Refresh Rate must be at least 1 second.', 'sentinelMetricsHistoryDays.required' => 'The Metrics History Days field is required.', 'sentinelMetricsHistoryDays.integer' => 'The Metrics History Days must be an integer.', 'sentinelMetricsHistoryDays.min' => 'The Metrics History Days must be at least 1 day.', 'sentinelPushIntervalSeconds.required' => 'The Push Interval field is required.', 'sentinelPushIntervalSeconds.integer' => 'The Push Interval must be an integer.', 'sentinelPushIntervalSeconds.min' => 'The Push Interval must be at least 10 seconds.', 'sentinelCustomUrl.url' => 'The Custom Sentinel URL must be a valid URL.', 'serverTimezone.required' => 'The Server Timezone field is required.', ] ); } public function mount(string $server_uuid) { try { $this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail(); $this->syncData(); if (! $this->server->isBuildServer() && ! $this->server->isEmpty()) { $this->isBuildServerLocked = true; } // Load saved Hetzner status and validation state $this->hetznerServerStatus = $this->server->hetzner_server_status; $this->vultrInstanceStatus = $this->server->vultr_instance_status; $this->digitalOceanDropletStatus = $this->server->digitalocean_droplet_status; $this->isValidating = $this->server->is_validating ?? false; // Load cloud provider tokens for linking $this->loadHetznerTokens(); $this->loadVultrTokens(); $this->loadDigitalOceanTokens(); } catch (\Throwable $e) { return handleError($e, $this); } } #[Computed] public function timezones(): array { return collect(timezone_identifiers_list()) ->sort() ->values() ->toArray(); } public function syncData(bool $toModel = false) { if ($toModel) { $this->validate(); $this->authorize('update', $this->server); $foundServer = Server::where('ip', $this->ip) ->where('id', '!=', $this->server->id) ->first(); if ($foundServer) { $this->ip = $this->server->ip; if ($foundServer->team_id === currentTeam()->id) { throw new \Exception('A server with this IP/Domain already exists in your team.'); } throw new \Exception('A server with this IP/Domain is already in use by another team.'); } $this->server->name = $this->name; $this->server->description = $this->description; $this->server->ip = $this->ip; $this->server->user = $this->user; $this->server->port = $this->port; $this->server->validation_logs = $this->validationLogs; $this->server->save(); $this->server->settings->connection_timeout = $this->connectionTimeout; $this->server->settings->is_swarm_manager = $this->isSwarmManager; $this->server->settings->wildcard_domain = $this->wildcardDomain; $this->server->settings->is_swarm_worker = $this->isSwarmWorker; $this->server->settings->is_build_server = $this->isBuildServer; $this->server->settings->is_metrics_enabled = $this->isMetricsEnabled; $this->server->settings->sentinel_token = $this->sentinelToken; $this->server->settings->sentinel_metrics_refresh_rate_seconds = $this->sentinelMetricsRefreshRateSeconds; $this->server->settings->sentinel_metrics_history_days = $this->sentinelMetricsHistoryDays; $this->server->settings->sentinel_push_interval_seconds = $this->sentinelPushIntervalSeconds; $this->server->settings->sentinel_custom_url = $this->sentinelCustomUrl; $this->server->settings->is_sentinel_enabled = $this->isSentinelEnabled; $this->server->settings->is_sentinel_debug_enabled = $this->isSentinelDebugEnabled; if (! validate_timezone($this->serverTimezone)) { $this->serverTimezone = config('app.timezone'); throw new \Exception('Invalid timezone.'); } else { $this->server->settings->server_timezone = $this->serverTimezone; } $this->server->settings->save(); } else { $this->name = $this->server->name; $this->description = $this->server->description; $this->ip = $this->server->ip; $this->user = $this->server->user; $this->port = $this->server->port; $this->connectionTimeout = $this->server->settings->connection_timeout; $this->wildcardDomain = $this->server->settings->wildcard_domain; $this->isReachable = $this->server->settings->is_reachable; $this->isUsable = $this->server->settings->is_usable; $this->isSwarmManager = $this->server->settings->is_swarm_manager; $this->isSwarmWorker = $this->server->settings->is_swarm_worker; $this->isBuildServer = $this->server->settings->is_build_server; $this->isMetricsEnabled = $this->server->settings->is_metrics_enabled; $this->sentinelToken = $this->server->settings->sentinel_token; $this->sentinelMetricsRefreshRateSeconds = $this->server->settings->sentinel_metrics_refresh_rate_seconds; $this->sentinelMetricsHistoryDays = $this->server->settings->sentinel_metrics_history_days; $this->sentinelPushIntervalSeconds = $this->server->settings->sentinel_push_interval_seconds; $this->sentinelCustomUrl = $this->server->settings->sentinel_custom_url; $this->isSentinelEnabled = $this->server->settings->is_sentinel_enabled; $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; } } public function refresh() { $this->syncData(); } public function handleSentinelRestarted($event) { // Only refresh if the event is for this server if (isset($event['serverUuid']) && $event['serverUuid'] === $this->server->uuid) { $this->server->refresh(); // Only refresh display-only state; never re-sync text-input properties // (would clobber any unsaved typing — see coolify#6062 / #6354 / #9695). $this->sentinelUpdatedAt = $this->server->sentinel_updated_at; $this->dispatch('success', 'Sentinel has been restarted successfully.'); } } public function validateServer($install = true) { try { $this->authorize('update', $this->server); if ($this->server->vultr_instance_id) { $status = $this->server->refreshVultrState(); $this->server->refresh(); $this->vultrInstanceStatus = $this->server->vultr_instance_status; $this->ip = $this->server->ip; if (in_array($status, ['stopped', 'suspended', 'deleted'], true)) { $message = $status === 'deleted' ? 'Vultr instance is deleted or no longer accessible. Relink this server before validating.' : 'Vultr instance is '.($status ?? 'not running').'. Power it on before validating.'; $this->dispatch('error', $message); return; } } $this->validationLogs = $this->server->validation_logs = null; $this->server->save(); $this->dispatch('init', $install); } catch (\Throwable $e) { return handleError($e, $this); } } public function checkLocalhostConnection() { try { $this->syncData(true); ['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 restartSentinel() { try { $this->authorize('manageSentinel', $this->server); $customImage = isDev() ? $this->sentinelCustomDockerImage : null; $this->server->restartSentinel($customImage); $this->dispatch('info', 'Restarting Sentinel.'); } catch (\Throwable $e) { return handleError($e, $this); } } public function updatedIsSentinelDebugEnabled($value) { try { $this->submit(); $this->restartSentinel(); } catch (\Throwable $e) { return handleError($e, $this); } } public function updatedIsMetricsEnabled($value) { try { $this->submit(); $this->restartSentinel(); } catch (\Throwable $e) { return handleError($e, $this); } } public function updatedIsBuildServer($value) { try { $this->authorize('update', $this->server); if ($value === true && ! $this->server->isEmpty()) { $this->isBuildServer = false; $this->dispatch('error', 'A server with existing resources cannot be configured as a build server.'); return; } if ($value === true && $this->isSentinelEnabled) { $this->isSentinelEnabled = false; $this->isMetricsEnabled = false; $this->isSentinelDebugEnabled = false; StopSentinel::dispatch($this->server); $this->dispatch('info', 'Sentinel has been disabled as build servers cannot run Sentinel.'); } $this->submit(); // Dispatch event to refresh the navbar $this->dispatch('refreshServerShow'); } catch (\Throwable $e) { return handleError($e, $this); } } public function updatedIsSentinelEnabled($value) { try { $this->authorize('manageSentinel', $this->server); if ($value === true) { if ($this->isBuildServer) { $this->isSentinelEnabled = false; $this->dispatch('error', 'Sentinel cannot be enabled on build servers.'); return; } $customImage = isDev() ? $this->sentinelCustomDockerImage : null; StartSentinel::run($this->server, true, null, $customImage); } else { $this->isMetricsEnabled = false; $this->isSentinelDebugEnabled = false; StopSentinel::dispatch($this->server); } $this->submit(); } catch (\Throwable $e) { return handleError($e, $this); } } public function regenerateSentinelToken() { try { $this->authorize('manageSentinel', $this->server); $this->server->settings->generateSentinelToken(); $this->dispatch('success', 'Token regenerated. Restarting Sentinel.'); } catch (\Throwable $e) { return handleError($e, $this); } } public function instantSave() { try { $this->syncData(true); } catch (\Throwable $e) { return handleError($e, $this); } } public function checkHetznerServerStatus(bool $manual = false) { try { $this->authorize('view', $this->server); 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 HetznerService($this->server->cloudProviderToken->token); $serverData = $hetznerService->getServer($this->server->hetzner_server_id); $this->hetznerServerStatus = $serverData['status'] ?? null; // Save status to database without triggering model events if ($this->server->hetzner_server_status !== $this->hetznerServerStatus) { $this->server->hetzner_server_status = $this->hetznerServerStatus; $this->server->update(['hetzner_server_status' => $this->hetznerServerStatus]); } $assignedIp = data_get($serverData, 'public_net.ipv4.ip') ?? data_get($serverData, 'public_net.ipv6.ip'); if ($this->server->backfillPlaceholderIp($assignedIp)) { $this->ip = $this->server->ip; } 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 checkVultrInstanceStatus(bool $manual = false) { try { if (! $this->server->vultr_instance_id || ! $this->server->cloudProviderToken) { $this->dispatch('error', 'This server is not associated with a Vultr instance or token.'); return; } $this->vultrInstanceStatus = $this->server->refreshVultrState(); $this->server->refresh(); $this->ip = $this->server->ip; if ($manual) { $this->dispatch('success', 'Instance status refreshed: '.ucfirst($this->vultrInstanceStatus ?? 'unknown')); } } catch (\Throwable $e) { return handleError($e, $this); } } public function checkDigitalOceanDropletStatus(bool $manual = false) { try { $this->authorize('view', $this->server); if (! $this->server->digitalocean_droplet_id || ! $this->server->cloudProviderToken) { $this->dispatch('error', 'This server is not associated with a DigitalOcean droplet or token.'); return; } $this->digitalOceanDropletStatus = $this->server->refreshDigitalOceanState(); $this->server->refresh(); $this->ip = $this->server->ip; if ($manual) { $this->dispatch('success', 'Droplet status refreshed: '.ucfirst($this->digitalOceanDropletStatus ?? 'unknown')); } } 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 and only the display-only state that validation produces. // Never re-sync text-input properties via syncData() — would clobber any // unsaved typing (see coolify#6062 / #6354 / #9695). $this->server->refresh(); $this->server->settings->refresh(); $this->isValidating = $this->server->is_validating ?? false; $this->validationLogs = $this->server->validation_logs; $this->isReachable = $this->server->settings->is_reachable; $this->isUsable = $this->server->settings->is_usable; // Reload Hetzner tokens in case the linking section should now be shown $this->loadHetznerTokens(); $this->loadVultrTokens(); $this->loadDigitalOceanTokens(); $this->dispatch('refreshServerShow'); $this->dispatch('refreshServer'); } public function startHetznerServer() { try { $this->authorize('update', $this->server); 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 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 startVultrInstance() { try { $this->authorize('update', $this->server); if (! $this->server->vultr_instance_id || ! $this->server->cloudProviderToken) { $this->dispatch('error', 'This server is not associated with a Vultr instance or token.'); return; } $vultrService = new VultrService($this->server->cloudProviderToken->token); $vultrService->startInstance($this->server->vultr_instance_id); $this->vultrInstanceStatus = 'starting'; $this->server->update(['vultr_instance_status' => 'starting']); $this->vultrInstanceManuallyStarted = true; $this->dispatch('success', 'Vultr instance is starting...'); } catch (\Throwable $e) { return handleError($e, $this); } } public function startDigitalOceanDroplet() { try { $this->authorize('update', $this->server); if (! $this->server->digitalocean_droplet_id || ! $this->server->cloudProviderToken) { $this->dispatch('error', 'This server is not associated with a DigitalOcean droplet or token.'); return; } $digitalOceanService = new DigitalOceanService($this->server->cloudProviderToken->token); $digitalOceanService->powerOnDroplet((int) $this->server->digitalocean_droplet_id); $this->digitalOceanDropletStatus = 'new'; $this->server->update(['digitalocean_droplet_status' => 'new']); $this->digitalOceanDropletManuallyStarted = true; $this->dispatch('success', 'DigitalOcean droplet is starting...'); } catch (\Throwable $e) { return handleError($e, $this); } } public function refreshServerMetadata(): void { try { $this->authorize('update', $this->server); $result = $this->server->gatherServerMetadata(); if ($result) { $this->server->refresh(); $this->dispatch('success', 'Server details refreshed.'); } else { $this->dispatch('error', 'Could not fetch server details. Is the server reachable?'); } } catch (\Throwable $e) { handleError($e, $this); } } public function submit() { try { $this->syncData(true); $this->dispatch('success', 'Server settings updated.'); } catch (\Throwable $e) { return handleError($e, $this); } } public function loadHetznerTokens(): void { $this->availableHetznerTokens = CloudProviderToken::ownedByCurrentTeam() ->where('provider', 'hetzner') ->get(); } public function loadVultrTokens(): void { $this->availableVultrTokens = CloudProviderToken::ownedByCurrentTeam() ->where('provider', 'vultr') ->get(); } public function loadDigitalOceanTokens(): void { $this->availableDigitalOceanTokens = CloudProviderToken::ownedByCurrentTeam() ->where('provider', 'digitalocean') ->get(); } #[Computed] public function limaStartCommand(): ?string { if (! isDev()) { return null; } return match ($this->server->uuid) { 'lima-ubuntu-2404' => 'limactl start --yes --name=coolify-lima-ubuntu-2404 docker/lima/ubuntu-2404.yaml', 'lima-ubuntu-2604' => 'limactl start --yes --name=coolify-lima-ubuntu-2604 docker/lima/ubuntu-2604.yaml', default => null, }; } public function searchHetznerServer(): void { $this->hetznerSearchError = null; $this->hetznerNoMatchFound = false; $this->matchedHetznerServer = null; if (! $this->selectedHetznerTokenId) { $this->hetznerSearchError = 'Please select a Hetzner token.'; return; } try { $this->authorize('update', $this->server); $token = $this->availableHetznerTokens->firstWhere('id', $this->selectedHetznerTokenId); if (! $token) { $this->hetznerSearchError = 'Invalid token selected.'; return; } $hetznerService = new HetznerService($token->token); $matched = $hetznerService->findServerByIp($this->server->ip); if ($matched) { $this->matchedHetznerServer = $matched; } else { $this->hetznerNoMatchFound = true; } } catch (\Throwable $e) { $this->hetznerSearchError = 'Failed to search Hetzner servers: '.$e->getMessage(); } } public function searchHetznerServerById(): void { $this->hetznerSearchError = null; $this->hetznerNoMatchFound = false; $this->matchedHetznerServer = null; if (! $this->selectedHetznerTokenId) { $this->hetznerSearchError = 'Please select a Hetzner token first.'; return; } if (! $this->manualHetznerServerId) { $this->hetznerSearchError = 'Please enter a Hetzner Server ID.'; return; } try { $this->authorize('update', $this->server); $token = $this->availableHetznerTokens->firstWhere('id', $this->selectedHetznerTokenId); if (! $token) { $this->hetznerSearchError = 'Invalid token selected.'; return; } $hetznerService = new HetznerService($token->token); $serverData = $hetznerService->getServer((int) $this->manualHetznerServerId); if (! empty($serverData)) { $this->matchedHetznerServer = $serverData; } else { $this->hetznerNoMatchFound = true; } } catch (\Throwable $e) { $this->hetznerSearchError = 'Failed to fetch Hetzner server: '.$e->getMessage(); } } public function linkToHetzner() { if (! $this->matchedHetznerServer) { $this->dispatch('error', 'No Hetzner server selected.'); return; } try { $this->authorize('update', $this->server); $token = $this->availableHetznerTokens->firstWhere('id', $this->selectedHetznerTokenId); if (! $token) { $this->dispatch('error', 'Invalid token selected.'); return; } // Verify the server exists and is accessible with the token $hetznerService = new HetznerService($token->token); $serverData = $hetznerService->getServer($this->matchedHetznerServer['id']); if (empty($serverData)) { $this->dispatch('error', 'Could not find Hetzner server with ID: '.$this->matchedHetznerServer['id']); return; } // Update the server with Hetzner details $this->server->update([ 'cloud_provider_token_id' => $this->selectedHetznerTokenId, 'hetzner_server_id' => $this->matchedHetznerServer['id'], 'hetzner_server_status' => $serverData['status'] ?? null, ]); $this->hetznerServerStatus = $serverData['status'] ?? null; // Clear the linking state $this->matchedHetznerServer = null; $this->selectedHetznerTokenId = null; $this->manualHetznerServerId = null; $this->hetznerNoMatchFound = false; $this->hetznerSearchError = null; $this->dispatch('success', 'Server successfully linked to Hetzner Cloud!'); $this->dispatch('close-modal'); $this->dispatch('refreshServerShow'); } catch (\Throwable $e) { return handleError($e, $this); } } public function searchDigitalOceanDroplet(): void { $this->digitalOceanSearchError = null; $this->digitalOceanNoMatchFound = false; $this->matchedDigitalOceanDroplet = null; if (! $this->selectedDigitalOceanTokenId) { $this->digitalOceanSearchError = 'Please select a DigitalOcean token.'; return; } try { $this->authorize('update', $this->server); $token = $this->availableDigitalOceanTokens->firstWhere('id', $this->selectedDigitalOceanTokenId); if (! $token) { $this->digitalOceanSearchError = 'Invalid token selected.'; return; } $digitalOceanService = new DigitalOceanService($token->token); $matched = $digitalOceanService->findDropletByIp($this->server->ip); if ($matched) { $this->matchedDigitalOceanDroplet = $matched; } else { $this->digitalOceanNoMatchFound = true; } } catch (\Throwable $e) { $this->digitalOceanSearchError = 'Failed to search DigitalOcean droplets: '.$e->getMessage(); } } public function searchDigitalOceanDropletById(): void { $this->digitalOceanSearchError = null; $this->digitalOceanNoMatchFound = false; $this->matchedDigitalOceanDroplet = null; if (! $this->selectedDigitalOceanTokenId) { $this->digitalOceanSearchError = 'Please select a DigitalOcean token first.'; return; } if (! $this->manualDigitalOceanDropletId) { $this->digitalOceanSearchError = 'Please enter a DigitalOcean Droplet ID.'; return; } try { $this->authorize('update', $this->server); $token = $this->availableDigitalOceanTokens->firstWhere('id', $this->selectedDigitalOceanTokenId); if (! $token) { $this->digitalOceanSearchError = 'Invalid token selected.'; return; } $digitalOceanService = new DigitalOceanService($token->token); $dropletData = $digitalOceanService->getDroplet((int) $this->manualDigitalOceanDropletId); if (! empty($dropletData)) { $this->matchedDigitalOceanDroplet = $dropletData; } else { $this->digitalOceanNoMatchFound = true; } } catch (\Throwable $e) { $this->digitalOceanSearchError = 'Failed to fetch DigitalOcean droplet: '.$e->getMessage(); } } public function linkToDigitalOcean() { if (! $this->matchedDigitalOceanDroplet) { $this->dispatch('error', 'No DigitalOcean droplet selected.'); return; } try { $this->authorize('update', $this->server); $token = $this->availableDigitalOceanTokens->firstWhere('id', $this->selectedDigitalOceanTokenId); if (! $token) { $this->dispatch('error', 'Invalid token selected.'); return; } $digitalOceanService = new DigitalOceanService($token->token); $dropletData = $digitalOceanService->getDroplet((int) $this->matchedDigitalOceanDroplet['id']); if (empty($dropletData)) { $this->dispatch('error', 'Could not find DigitalOcean droplet with ID: '.$this->matchedDigitalOceanDroplet['id']); return; } $ip = $digitalOceanService->getPublicIpAddress($dropletData); $updates = [ 'cloud_provider_token_id' => $this->selectedDigitalOceanTokenId, 'digitalocean_droplet_id' => $this->matchedDigitalOceanDroplet['id'], 'digitalocean_droplet_status' => $dropletData['status'] ?? null, ]; if ($ip) { $updates['ip'] = $ip; } $this->server->update($updates); $this->digitalOceanDropletStatus = $dropletData['status'] ?? null; $this->matchedDigitalOceanDroplet = null; $this->selectedDigitalOceanTokenId = null; $this->manualDigitalOceanDropletId = null; $this->digitalOceanNoMatchFound = false; $this->digitalOceanSearchError = null; $this->dispatch('success', 'Server successfully linked to DigitalOcean!'); $this->dispatch('close-modal'); $this->dispatch('refreshServerShow'); } catch (\Throwable $e) { return handleError($e, $this); } } public function searchVultrInstance(): void { $this->vultrSearchError = null; $this->vultrNoMatchFound = false; $this->matchedVultrInstance = null; if (! $this->selectedVultrTokenId) { $this->vultrSearchError = 'Please select a Vultr token.'; return; } try { $this->authorize('update', $this->server); $token = $this->availableVultrTokens->firstWhere('id', $this->selectedVultrTokenId); if (! $token) { $this->vultrSearchError = 'Invalid token selected.'; return; } $vultrService = new VultrService($token->token); $matched = $vultrService->findInstanceByIp($this->server->ip); if ($matched) { $this->matchedVultrInstance = $matched; } else { $this->vultrNoMatchFound = true; } } catch (\Throwable $e) { $this->vultrSearchError = 'Failed to search Vultr instances: '.$e->getMessage(); } } public function searchVultrInstanceById(): void { $this->vultrSearchError = null; $this->vultrNoMatchFound = false; $this->matchedVultrInstance = null; if (! $this->selectedVultrTokenId) { $this->vultrSearchError = 'Please select a Vultr token first.'; return; } if (! $this->manualVultrInstanceId) { $this->vultrSearchError = 'Please enter a Vultr Instance ID.'; return; } try { $this->authorize('update', $this->server); $token = $this->availableVultrTokens->firstWhere('id', $this->selectedVultrTokenId); if (! $token) { $this->vultrSearchError = 'Invalid token selected.'; return; } $vultrService = new VultrService($token->token); $instanceData = $vultrService->getInstance($this->manualVultrInstanceId); if (! empty($instanceData)) { $this->matchedVultrInstance = $instanceData; } else { $this->vultrNoMatchFound = true; } } catch (\Throwable $e) { $this->vultrSearchError = 'Failed to fetch Vultr instance: '.$e->getMessage(); } } public function linkToVultr() { if (! $this->matchedVultrInstance) { $this->dispatch('error', 'No Vultr instance selected.'); return; } try { $this->authorize('update', $this->server); $token = $this->availableVultrTokens->firstWhere('id', $this->selectedVultrTokenId); if (! $token) { $this->dispatch('error', 'Invalid token selected.'); return; } $vultrService = new VultrService($token->token); $instanceData = $vultrService->getInstance($this->matchedVultrInstance['id']); if (empty($instanceData)) { $this->dispatch('error', 'Could not find Vultr instance with ID: '.$this->matchedVultrInstance['id']); return; } $this->server->update([ 'cloud_provider_token_id' => $this->selectedVultrTokenId, 'vultr_instance_id' => $this->matchedVultrInstance['id'], 'vultr_instance_status' => $instanceData['status'] ?? null, ]); $this->vultrInstanceStatus = $instanceData['status'] ?? null; $this->matchedVultrInstance = null; $this->selectedVultrTokenId = null; $this->manualVultrInstanceId = null; $this->vultrNoMatchFound = false; $this->vultrSearchError = null; $this->dispatch('success', 'Server successfully linked to Vultr!'); $this->dispatch('close-modal'); $this->dispatch('refreshServerShow'); } catch (\Throwable $e) { return handleError($e, $this); } } public function render() { return view('livewire.server.show'); } }