'required', 'container' => 'required', 'command' => 'required', ]; public function getListeners() { $teamId = auth()->user()->currentTeam()->id; return [ "echo-private:team.{$teamId},ServiceChecked" => '$refresh', ]; } public function mount() { if (! auth()->user()->isAdmin()) { abort(403); } $this->parameters = get_route_parameters(); $this->containers = collect(); $this->servers = collect(); if (data_get($this->parameters, 'application_uuid')) { $this->type = 'application'; $this->resource = Application::whereUuid($this->parameters['application_uuid'])->firstOrFail(); if ($this->resource->destination->server->isFunctional() && $this->resource->destination->server->isTerminalEnabled()) { $this->servers = $this->servers->push($this->resource->destination->server); } foreach ($this->resource->additional_servers as $server) { if ($server->isFunctional() && $server->isTerminalEnabled()) { $this->servers = $this->servers->push($server); } } $this->loadContainers(); } elseif (data_get($this->parameters, 'database_uuid')) { $this->type = 'database'; $resource = getResourceByUuid($this->parameters['database_uuid'], data_get(auth()->user()->currentTeam(), 'id')); if (is_null($resource)) { abort(404); } $this->resource = $resource; if ($this->resource->destination->server->isFunctional() && $this->resource->destination->server->isTerminalEnabled()) { $this->servers = $this->servers->push($this->resource->destination->server); } $this->loadContainers(); } elseif (data_get($this->parameters, 'service_uuid')) { $this->type = 'service'; $this->resource = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail(); if ($this->resource->server->isFunctional() && $this->resource->server->isTerminalEnabled()) { $this->servers = $this->servers->push($this->resource->server); } $this->loadContainers(); } elseif (data_get($this->parameters, 'server_uuid')) { $this->type = 'server'; $this->resource = Server::ownedByCurrentTeam()->whereUuid($this->parameters['server_uuid'])->firstOrFail(); $this->server = $this->resource; $this->containersLoaded = true; // Server doesn't need container loading $this->connectionStatus = 'Waiting for terminal to be ready...'; } } public function loadContainers() { foreach ($this->servers as $server) { if (data_get($this->parameters, 'application_uuid')) { if ($server->isSwarm()) { $containers = collect([ [ 'Names' => $this->resource->uuid.'_'.$this->resource->uuid, ], ]); } else { $containers = getCurrentApplicationContainerStatus($server, $this->resource->id, includePullrequests: true); } foreach ($containers as $container) { // if container state is running if (data_get($container, 'State') === 'running') { $payload = [ 'server' => $server, 'container' => $container, ]; $this->containers = $this->containers->push($payload); } } } elseif (data_get($this->parameters, 'database_uuid')) { if ($this->resource->isRunning()) { $this->containers = $this->containers->push([ 'server' => $server, 'container' => [ 'Names' => $this->resource->uuid, ], ]); } } elseif (data_get($this->parameters, 'service_uuid')) { $this->resource->applications()->get()->each(function ($application) { if ($application->isRunning()) { $this->containers->push([ 'server' => $this->resource->server, 'container' => [ 'Names' => data_get($application, 'name').'-'.data_get($this->resource, 'uuid'), ], ]); } }); $this->resource->databases()->get()->each(function ($database) { if ($database->isRunning()) { $this->containers->push([ 'server' => $this->resource->server, 'container' => [ 'Names' => data_get($database, 'name').'-'.data_get($this->resource, 'uuid'), ], ]); } }); } } if ($this->containers->count() > 0) { $this->container = $this->containers->first(); } if ($this->containers->count() === 1) { $this->selected_container = data_get($this->containers->first(), 'container.Names'); } $this->containersLoaded = true; $this->connectionStatus = 'Waiting for terminal to be ready...'; } public function initializeTerminalConnection() { try { // Only auto-connect if containers are loaded and we haven't attempted before if (! $this->containersLoaded || $this->autoConnectAttempted || $this->isConnecting) { return; } $this->autoConnectAttempted = true; // Ensure component is in a stable state before proceeding $this->skipRender(); $this->isConnecting = true; if ($this->type === 'server') { $this->connectionStatus = 'Establishing connection to server terminal...'; $this->connectToServer(); } elseif ($this->containers->count() === 1) { $this->connectionStatus = 'Establishing connection to container terminal...'; $this->connectToContainer(); } else { $this->isConnecting = false; $this->connectionStatus = ''; } } catch (\Throwable $e) { // Log the error but don't let it bubble up to cause snapshot issues logger()->error('Terminal auto-connection failed', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), 'component_id' => $this->getId(), ]); // Reset state to allow manual connection $this->autoConnectAttempted = false; $this->isConnecting = false; $this->connectionStatus = 'Auto-connection failed. Please use the reconnect button.'; } } #[On('terminalConnected')] public function terminalConnected() { $this->isConnected = true; $this->isConnecting = false; $this->connectionStatus = ''; } #[On('terminalDisconnected')] public function terminalDisconnected() { $this->isConnected = false; $this->isConnecting = false; $this->autoConnectAttempted = false; $this->connectionStatus = 'Connection lost. Click Reconnect to try again.'; } private function checkShellAvailability(Server $server, string $container): bool { $escapedContainer = escapeshellarg($container); try { instant_remote_process([ "docker exec {$escapedContainer} bash -c 'exit 0' 2>/dev/null || ". "docker exec {$escapedContainer} sh -c 'exit 0' 2>/dev/null", ], $server); return true; } catch (\Throwable) { return false; } } #[On('connectToServer')] public function connectToServer() { try { if ($this->server->isForceDisabled()) { throw new \RuntimeException('Server is disabled.'); } if (! $this->server->isTerminalEnabled()) { throw new \RuntimeException('Terminal access is disabled on this server.'); } $this->hasShell = true; $this->isConnecting = true; $this->connectionStatus = 'Establishing connection to server terminal...'; $this->dispatch( 'send-terminal-command', false, data_get($this->server, 'name'), data_get($this->server, 'uuid') ); } catch (\Throwable $e) { $this->isConnecting = false; $this->connectionStatus = 'Connection failed.'; return handleError($e, $this); } } #[On('connectToContainer')] public function connectToContainer() { if ($this->selected_container === 'default') { $this->dispatch('error', 'Please select a container.'); return; } try { // Validate container name format if (! preg_match('/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/', $this->selected_container)) { throw new \InvalidArgumentException('Invalid container name format'); } // Verify container exists in our allowed list $container = collect($this->containers)->firstWhere('container.Names', $this->selected_container); if (is_null($container)) { throw new \RuntimeException('Container not found.'); } // Verify server ownership and status $server = data_get($container, 'server'); if (! $server || ! $server instanceof Server) { throw new \RuntimeException('Invalid server configuration.'); } if ($server->isForceDisabled()) { throw new \RuntimeException('Server is disabled.'); } // Additional ownership verification based on resource type $resourceServer = match ($this->type) { 'application' => $this->resource->destination->server, 'database' => $this->resource->destination->server, 'service' => $this->resource->server, default => throw new \RuntimeException('Invalid resource type.') }; if ($server->id !== $resourceServer->id && ! $this->resource->additional_servers->contains('id', $server->id)) { throw new \RuntimeException('Server ownership verification failed.'); } $this->hasShell = $this->checkShellAvailability($server, data_get($container, 'container.Names')); if (! $this->hasShell) { $this->isConnecting = false; $this->connectionStatus = 'Shell not available in container.'; return; } $this->isConnecting = true; $this->connectionStatus = 'Establishing connection to container terminal...'; $this->dispatch( 'send-terminal-command', true, data_get($container, 'container.Names'), data_get($container, 'server.uuid') ); } catch (\Throwable $e) { $this->isConnecting = false; $this->connectionStatus = 'Connection failed.'; return handleError($e, $this); } } public function render() { return view('livewire.project.shared.execute-container-command'); } }