> */ public array $containers = []; public bool $connected = false; public bool $ready = false; /** Docker binary absent on the host — an honest state, not an error. */ public bool $notInstalled = false; /** No host SSH login configured yet — point 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 mount(): void { // The host is the control-plane machine — admin-only, like the host terminal. abort_unless(Auth::user()?->can('manage-fleet'), 403); } public function title(): string { return __('docker.title'); } /** A transient Server pointing at the Clusev host (built from the host login), or null if unset. */ private function hostServer(): ?Server { return HostCredential::current()?->toServer(); } public function load(DockerService $docker): void { $this->containers = []; $this->connected = false; $this->notInstalled = false; $this->hostNotConfigured = false; $this->error = null; $server = $this->hostServer(); if (! $server) { $this->hostNotConfigured = true; $this->ready = true; return; } try { // ONE SSH round-trip: containers() derives the "not installed" state from the docker ps // call itself (a missing binary throws DockerNotInstalled), so no separate available() probe. $this->containers = $docker->containers($server); $this->connected = true; } catch (DockerNotInstalled) { $this->notInstalled = 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('manage-fleet'), 403); $server = $this->hostServer(); if (! $server) { return; } try { $res = $docker->containerAction($server, $id, $op); AuditEvent::create([ 'user_id' => Auth::id(), 'server_id' => null, // the host is transient (no fleet row); 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 host container (ref re-validated in DockerService). */ public function viewLogs(string $id, string $name = ''): void { abort_unless(Auth::user()?->can('manage-fleet'), 403); $this->dispatch('openModal', component: 'modals.container-logs', arguments: [ // 0 is the sentinel the logs modal resolves to the Clusev host (no persisted id). 'serverId' => 0, 'ref' => $id, 'name' => $name, ]); } public function render(): View { return view('livewire.docker.index')->title($this->title()); } }