> */ public array $containers = []; public bool $connected = false; public bool $ready = false; /** Docker binary absent on the target (e.g. a native mail server) — an honest state, not an error. */ public bool $notInstalled = false; /** Host target selected but no host SSH login configured yet — points the operator to setup. */ public bool $hostNotConfigured = false; /** The docker error (daemon down / permission / rootless), surfaced so "empty" isn't misleading. */ public ?string $error = null; public function title(): string { return __('docker.title'); } /** Whether a Clusev-host SSH login exists (controls the target switch + the default target). */ public function hostConfigured(): bool { return HostCredential::current() !== null; } /** Resolve '' to a concrete target: the host when one is configured, otherwise a fleet server. */ public function effectiveTarget(): string { if ($this->target === 'host' || $this->target === 'server') { return $this->target; } return $this->hostConfigured() ? 'host' : 'server'; } /** The Server the current target resolves to (transient host server, or the active fleet server). */ private function targetServer(): ?Server { return $this->effectiveTarget() === 'host' ? HostCredential::current()?->toServer() : $this->activeServer(); } /** Switch between the host and the active fleet server, then reload. */ public function setTarget(string $target, DockerService $docker): void { $this->target = in_array($target, ['host', 'server'], true) ? $target : ''; $this->ready = false; $this->load($docker); } public function load(DockerService $docker): void { $this->containers = []; $this->connected = false; $this->notInstalled = false; $this->hostNotConfigured = false; $this->error = null; $isHost = $this->effectiveTarget() === 'host'; $server = $this->targetServer(); if ($isHost && ! $server) { $this->hostNotConfigured = true; $this->ready = true; return; } // Fleet servers need a stored credential; the host server always carries the host login. if ($server && ($isHost || $server->credential_exists)) { try { // Probe first: a target with no docker binary (native mail/web server) gets a clean // "not installed" panel instead of the raw "sh: docker: not found" shell error. if (! $docker->available($server)) { $this->notInstalled = true; } else { $this->containers = $docker->containers($server); $this->connected = true; } } catch (Throwable $e) { $this->connected = false; $this->error = mb_scrub(mb_strcut($e->getMessage(), 0, 300), 'UTF-8'); } } $this->ready = true; } public function action(string $id, string $op, DockerService $docker): void { abort_unless(Auth::user()?->can('operate'), 403); $isHost = $this->effectiveTarget() === 'host'; $server = $this->targetServer(); if (! $server || (! $isHost && ! $server->credential_exists)) { return; } try { $res = $docker->containerAction($server, $id, $op); AuditEvent::create([ 'user_id' => Auth::id(), 'server_id' => $server->id, // null for the host (transient) — the target label names it 'actor' => Auth::user()?->name ?? 'system', 'action' => 'docker.action', 'target' => "{$op} {$id} · {$server->name}", 'ip' => request()->ip(), ]); $this->dispatch('notify', message: $res['ok'] ? __('docker.action_ok', ['op' => $op, 'ref' => $id]) : __('docker.action_failed', ['error' => $res['output']])); } catch (InvalidArgumentException) { $this->dispatch('notify', message: __('docker.invalid_ref')); } $this->load($docker); } /** Open the read-only logs modal for a container (the ref is re-validated in DockerService). */ public function viewLogs(string $id): void { // Container logs can contain secrets (env dumps, tokens in traces), so reading them is an // operate action — consistent with gating file CONTENT reads. The list itself stays open. abort_unless(Auth::user()?->can('operate'), 403); $isHost = $this->effectiveTarget() === 'host'; $server = $this->targetServer(); if (! $server) { return; } $this->dispatch('openModal', component: 'modals.container-logs', arguments: [ // 0 is the sentinel the logs modal resolves to the Clusev host (no persisted id). 'serverId' => $isHost ? 0 : (int) $server->id, 'ref' => $id, ]); } public function render(): View { return view('livewire.docker.index')->title($this->title()); } }