fix(servers): keep read-error panels visible + pending header support

Address Codex review of the panel-grid + pending-status work:
- Firewall/fail2ban services report installed=false together with readError=true,
  so gating panel visibility on `installed` alone hid the read-error message and
  made a failed probe indistinguishable from an absent tool. Show the panel when
  installed OR readError.
- The server-details hero only mapped online/warning/offline, so a freshly
  created `pending` server rendered an untranslated "Pending" label and an
  uncolored icon. Add the localized cyan pending state to $statusLabel + hero.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat/v1-foundation
boban 2026-06-14 10:41:04 +02:00
parent a5c518822b
commit bc0691a730
2 changed files with 36 additions and 6 deletions

View File

@ -2,7 +2,7 @@
// Threshold tone for gauges/bars: >=90 offline, >=75 warning, else online. // Threshold tone for gauges/bars: >=90 offline, >=75 warning, else online.
$tone = fn (int $v): string => $v >= 90 ? 'offline' : ($v >= 75 ? 'warning' : 'online'); $tone = fn (int $v): string => $v >= 90 ? 'offline' : ($v >= 75 ? 'warning' : 'online');
$barColor = ['online' => 'bg-online', 'warning' => 'bg-warning', 'offline' => 'bg-offline']; $barColor = ['online' => 'bg-online', 'warning' => 'bg-warning', 'offline' => 'bg-offline'];
$statusLabel = ['online' => __('servers.status_online'), 'warning' => __('servers.status_warning'), 'offline' => __('servers.status_offline')][$server->status] ?? ucfirst($server->status); $statusLabel = ['online' => __('servers.status_online'), 'warning' => __('servers.status_warning'), 'offline' => __('servers.status_offline'), 'pending' => __('servers.status_pending')][$server->status] ?? ucfirst($server->status);
$specs = $server->specs ?? []; $specs = $server->specs ?? [];
$cores = $specs['cores'] ?? null; $cores = $specs['cores'] ?? null;
@ -51,6 +51,7 @@
'border-online/30 bg-online/10 text-online' => $server->status === 'online', 'border-online/30 bg-online/10 text-online' => $server->status === 'online',
'border-warning/30 bg-warning/10 text-warning' => $server->status === 'warning', 'border-warning/30 bg-warning/10 text-warning' => $server->status === 'warning',
'border-offline/30 bg-offline/10 text-offline' => $server->status === 'offline', 'border-offline/30 bg-offline/10 text-offline' => $server->status === 'offline',
'border-cyan/30 bg-cyan/10 text-cyan' => $server->status === 'pending',
])> ])>
<x-icon name="server" class="h-6 w-6" /> <x-icon name="server" class="h-6 w-6" />
</span> </span>
@ -259,11 +260,14 @@
$f2bIgnore = $f2b['ignoreip'] ?? []; $f2bIgnore = $f2b['ignoreip'] ?? [];
$f2bBanned = array_sum(array_map(fn ($j) => $j['currentlyBanned'] ?? 0, $f2bJails)); $f2bBanned = array_sum(array_map(fn ($j) => $j['currentlyBanned'] ?? 0, $f2bJails));
// Show a panel ONLY when its tool is actually installed (active or inactive). // Show a panel when its tool is actually installed (active or inactive), OR
// When absent, the Sicherheit checklist row carries the "Aktivieren" install // when the privileged read FAILED (readError) — a failed probe must stay
// path — so the redundant "nicht installiert" box is gone. // visible so the operator can tell it apart from a genuinely-absent tool
$showFw = $fwSupported && $fwInstalled; // (both report installed=false). When truly absent, the Sicherheit checklist
$showF2b = $f2bSupported && $f2bInstalled; // row carries the "Aktivieren" install path — so the redundant "nicht
// installiert" box is gone.
$showFw = $fwSupported && ($fwInstalled || $fwReadError);
$showF2b = $f2bSupported && ($f2bInstalled || $f2bReadError);
$panelCount = ($showFw ? 1 : 0) + ($showF2b ? 1 : 0); $panelCount = ($showFw ? 1 : 0) + ($showF2b ? 1 : 0);
@endphp @endphp

View File

@ -41,4 +41,30 @@ class ServerShowPanelsTest extends TestCase
->assertSee(__('servers.firewall_title')) ->assertSee(__('servers.firewall_title'))
->assertSee(__('servers.fail2ban_title')); ->assertSee(__('servers.fail2ban_title'));
} }
public function test_panel_stays_visible_on_read_error_even_when_not_installed(): void
{
// A failed privileged read reports installed=false + readError=true; it must
// stay visible so the operator can tell "probe failed" from "tool absent".
$this->actingAs(User::factory()->create());
Livewire::test(Show::class, ['server' => $this->server()])
->set('ready', true)
->set('firewall', ['supported' => true, 'installed' => false, 'readError' => true])
->set('fail2ban', ['supported' => true, 'installed' => false, 'readError' => true])
->assertSee(__('servers.firewall_title'))
->assertSee(__('servers.firewall_read_error'))
->assertSee(__('servers.fail2ban_title'))
->assertSee(__('servers.fail2ban_read_error'));
}
public function test_pending_server_header_uses_localized_label(): void
{
$this->actingAs(User::factory()->create());
$server = Server::create(['name' => 's', 'ip' => '10.0.0.9', 'ssh_port' => 22, 'status' => 'pending']);
Livewire::test(Show::class, ['server' => $server])
->assertSee(__('servers.status_pending'))
->assertDontSee('Pending');
}
} }