diff --git a/app/Livewire/Docker/Index.php b/app/Livewire/Docker/Index.php index fa20c6a..da1fbfa 100644 --- a/app/Livewire/Docker/Index.php +++ b/app/Livewire/Docker/Index.php @@ -29,6 +29,9 @@ class Index extends Component public bool $ready = false; + /** Docker binary absent on the host (e.g. a native mail server) — an honest state, not an error. */ + public bool $notInstalled = false; + /** The docker error (daemon down / permission / rootless), surfaced so "empty" isn't misleading. */ public ?string $error = null; @@ -41,13 +44,20 @@ class Index extends Component { $this->containers = []; $this->connected = false; + $this->notInstalled = false; $this->error = null; $active = $this->activeServer(); if ($active && $active->credential_exists) { try { - $this->containers = $docker->containers($active); - $this->connected = true; + // Probe first: a host 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($active)) { + $this->notInstalled = true; + } else { + $this->containers = $docker->containers($active); + $this->connected = true; + } } catch (Throwable $e) { $this->connected = false; $this->error = mb_scrub(mb_strcut($e->getMessage(), 0, 300), 'UTF-8'); diff --git a/lang/de/docker.php b/lang/de/docker.php index 3518b62..b84d29e 100644 --- a/lang/de/docker.php +++ b/lang/de/docker.php @@ -8,7 +8,9 @@ return [ 'disconnected' => 'Getrennt', 'unavailable_title' => 'Docker nicht verfügbar', - 'unavailable_hint' => 'Kein Docker auf diesem Server, Daemon aus, oder keine Berechtigung.', + 'unavailable_hint' => 'Daemon aus oder keine Berechtigung.', + 'not_installed_title' => 'Docker nicht installiert', + 'not_installed_hint' => 'Auf diesem Server ist kein Docker installiert – es gibt keine Container zum Anzeigen.', 'empty_title' => 'Keine Container', 'empty_hint' => 'Auf diesem Server laufen keine Container.', diff --git a/lang/en/docker.php b/lang/en/docker.php index 401bde9..58f7d92 100644 --- a/lang/en/docker.php +++ b/lang/en/docker.php @@ -8,7 +8,9 @@ return [ 'disconnected' => 'Disconnected', 'unavailable_title' => 'Docker unavailable', - 'unavailable_hint' => 'No Docker on this server, the daemon is down, or no permission.', + 'unavailable_hint' => 'The daemon is down, or no permission.', + 'not_installed_title' => 'Docker not installed', + 'not_installed_hint' => 'This server has no Docker installed – there are no containers to show.', 'empty_title' => 'No containers', 'empty_hint' => 'No containers are running on this server.', diff --git a/resources/views/livewire/docker/index.blade.php b/resources/views/livewire/docker/index.blade.php index 500351f..9ca3166 100644 --- a/resources/views/livewire/docker/index.blade.php +++ b/resources/views/livewire/docker/index.blade.php @@ -26,6 +26,15 @@
@endfor + @elseif ($notInstalled) + {{-- Honest, neutral state: the host simply has no docker (not a fault). --}} +{{ __('docker.not_installed_title') }}
+{{ __('docker.not_installed_hint') }}
+{{ __('docker.unavailable_title') }}
diff --git a/tests/Feature/DockerComponentTest.php b/tests/Feature/DockerComponentTest.php index eb85dbf..3de3844 100644 --- a/tests/Feature/DockerComponentTest.php +++ b/tests/Feature/DockerComponentTest.php @@ -49,6 +49,7 @@ class DockerComponentTest extends TestCase private function stubDocker(array $containers = []): DockerService { $docker = Mockery::mock(DockerService::class); + $docker->shouldReceive('available')->andReturn(true); $docker->shouldReceive('containers')->andReturn($containers); app()->instance(DockerService::class, $docker); @@ -68,6 +69,28 @@ class DockerComponentTest extends TestCase ->assertSee('web'); } + public function test_shows_a_clean_not_installed_state_when_docker_is_absent(): void + { + // A host without a container runtime (e.g. a native postfix/dovecot mail server) must NOT + // render the raw "sh: 1: docker: not found" shell error — that reads as a broken feature. + // available() is probed first; false => a clean, honest "not installed" panel. + $this->actingAs($this->viewer()); + $this->activeServer(); + $docker = Mockery::mock(DockerService::class); + $docker->shouldReceive('available')->once()->andReturn(false); + $docker->shouldReceive('containers')->never(); // never even attempt ps when docker is absent + app()->instance(DockerService::class, $docker); + + Livewire::test(Index::class) + ->call('load') + ->assertOk() + ->assertSet('connected', false) + ->assertSet('notInstalled', true) + ->assertSet('error', null) + ->assertSee(__('docker.not_installed_title')) + ->assertDontSee('not found'); + } + public function test_viewer_cannot_run_a_container_action(): void { $this->actingAs($this->viewer());