> */ public array $containers = []; public bool $connected = false; public bool $ready = false; public function title(): string { return __('docker.title'); } public function load(DockerService $docker): void { $this->containers = []; $this->connected = false; $active = $this->activeServer(); if ($active && $active->credential_exists) { try { $this->containers = $docker->containers($active); $this->connected = true; } catch (Throwable) { $this->connected = false; } } $this->ready = true; } public function action(string $id, string $op, DockerService $docker): void { abort_unless(Auth::user()?->can('operate'), 403); $active = $this->activeServer(); if (! $active || ! $active->credential_exists) { return; } try { $res = $docker->containerAction($active, $id, $op); AuditEvent::create([ 'user_id' => Auth::id(), 'server_id' => $active->id, 'actor' => Auth::user()?->name ?? 'system', 'action' => 'docker.action', 'target' => "{$op} {$id} · {$active->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); $active = $this->activeServer(); if (! $active) { return; } $this->dispatch('openModal', component: 'modals.container-logs', arguments: [ 'serverId' => $active->id, 'ref' => $id, ]); } public function render(): View { return view('livewire.docker.index')->title($this->title()); } }