fix(docker): honest "not installed" state instead of a raw shell error
When a fleet host has no container runtime at all (e.g. a native postfix/dovecot mail server), `docker ps` returns "sh: docker: not found". The page surfaced that raw shell text inside a red error box, which reads as a broken feature — the user reasonably expected the tab to show containers. Probe `available()` first: if the docker binary is genuinely absent, render a clean, neutral "Docker nicht installiert — es gibt keine Container zum Anzeigen" panel (box icon, no alarm styling) rather than the shell error. A real fault (daemon down / permission) still shows the actual error as before. Verified end-to-end against a real SSH host with no runtime (the honest state) and against a real docker host (full container list + Logs/Restart/Stop actions render). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>feat/v1-foundation
parent
4ab255832d
commit
7e6c01d5fa
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,15 @@
|
|||
<div class="h-12 animate-pulse rounded-md bg-raised/50"></div>
|
||||
@endfor
|
||||
</div>
|
||||
@elseif ($notInstalled)
|
||||
{{-- Honest, neutral state: the host simply has no docker (not a fault). --}}
|
||||
<div class="px-4 py-10 text-center sm:px-5">
|
||||
<div class="mx-auto flex h-10 w-10 items-center justify-center rounded-full bg-raised text-ink-3">
|
||||
<x-icon name="box" class="h-5 w-5" />
|
||||
</div>
|
||||
<p class="mt-3 text-sm text-ink-2">{{ __('docker.not_installed_title') }}</p>
|
||||
<p class="mx-auto mt-1 max-w-md font-mono text-[11px] text-ink-4">{{ __('docker.not_installed_hint') }}</p>
|
||||
</div>
|
||||
@elseif (! $connected)
|
||||
<div class="px-4 py-10 text-center sm:px-5">
|
||||
<p class="text-sm text-ink-2">{{ __('docker.unavailable_title') }}</p>
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
Loading…
Reference in New Issue