feat(servers): show firewall/fail2ban only when installed, in a grid
Each panel renders only when its tool is actually installed (the Sicherheit checklist carries the install/Aktivieren path otherwise), removing the redundant "nicht installiert" boxes. Both shown -> 2-col responsive grid (lg, items-start); one shown -> full width. Trash uses danger-soft, unban uses secondary. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>feat/v1-foundation
parent
a9334d11de
commit
21ee81f742
|
|
@ -231,7 +231,7 @@
|
|||
</x-panel>
|
||||
</div>
|
||||
|
||||
{{-- Firewall-Regeln --}}
|
||||
{{-- Firewall + fail2ban: only the tools actually installed, side-by-side on lg --}}
|
||||
@php
|
||||
$fw = $firewall ?? [];
|
||||
$fwTool = $fw['tool'] ?? 'ufw';
|
||||
|
|
@ -245,105 +245,7 @@
|
|||
$fwRules = $fw['rules'] ?? [];
|
||||
$fwDefaults = $fw['defaults'] ?? [];
|
||||
$actTone = ['ALLOW' => 'online', 'LIMIT' => 'warning', 'DENY' => 'offline', 'REJECT' => 'offline'];
|
||||
@endphp
|
||||
<x-panel :title="__('servers.firewall_title')"
|
||||
:subtitle="$fwSupported ? ($fwToolLabel . ($fwInstalled ? ($fwActive ? __('servers.firewall_sub_active') : __('servers.firewall_sub_inactive')) : __('servers.firewall_sub_not_installed'))) : __('servers.firewall_unavailable')"
|
||||
:padded="false">
|
||||
@if ($fwSupported && $fwManageable)
|
||||
<x-slot:actions>
|
||||
<x-btn variant="accent" size="sm"
|
||||
wire:click="$dispatch('openModal', { component: 'modals.firewall-rule', arguments: { serverId: {{ $server->id }}, tool: '{{ $fwTool }}' } })">
|
||||
<x-icon name="plus" class="h-3.5 w-3.5" /> {{ __('servers.firewall_add_rule') }}
|
||||
</x-btn>
|
||||
</x-slot:actions>
|
||||
@endif
|
||||
|
||||
@if (! $fwSupported)
|
||||
<div class="px-4 py-4 sm:px-5">
|
||||
<p class="font-mono text-[11px] leading-relaxed text-ink-3">{{ $fw['reason'] ?? __('servers.firewall_not_supported') }}</p>
|
||||
</div>
|
||||
@elseif ($fwReadError)
|
||||
<div class="flex flex-wrap items-center gap-2.5 px-4 py-4 sm:px-5">
|
||||
<x-icon name="alert" class="h-4 w-4 shrink-0 text-warning" />
|
||||
<p class="font-mono text-[11px] text-ink-3">{{ __('servers.firewall_read_error') }}</p>
|
||||
</div>
|
||||
@elseif (! $fwInstalled)
|
||||
<div class="flex flex-wrap items-center justify-between gap-3 px-4 py-4 sm:px-5">
|
||||
<p class="font-mono text-[11px] text-ink-3">{{ __('servers.firewall_tool_not_installed', ['tool' => $fwToolLabel]) }}</p>
|
||||
<x-btn variant="secondary" size="sm"
|
||||
wire:click="$dispatch('openModal', { component: 'modals.hardening-action', arguments: { serverId: {{ $server->id }}, action: 'firewall', enable: true } })">
|
||||
{{ __('common.enable') }}
|
||||
</x-btn>
|
||||
</div>
|
||||
@elseif ($fwTool === 'firewalld' && ! $fwActive)
|
||||
<div class="flex flex-wrap items-center justify-between gap-3 px-4 py-4 sm:px-5">
|
||||
<p class="font-mono text-[11px] text-ink-3">{{ __('servers.firewalld_inactive') }}</p>
|
||||
<x-btn variant="secondary" size="sm"
|
||||
wire:click="$dispatch('openModal', { component: 'modals.hardening-action', arguments: { serverId: {{ $server->id }}, action: 'firewall', enable: true } })">
|
||||
{{ __('common.enable') }}
|
||||
</x-btn>
|
||||
</div>
|
||||
@else
|
||||
<div class="flex flex-wrap items-center gap-2 border-b border-line px-4 py-2.5 sm:px-5">
|
||||
@if ($fwTool === 'ufw')
|
||||
<span class="font-mono text-[10px] uppercase tracking-wider text-ink-4">{{ __('servers.firewall_default') }}</span>
|
||||
<x-badge tone="neutral">{{ __('servers.firewall_incoming', ['value' => $fwDefaults['incoming'] ?? '—']) }}</x-badge>
|
||||
<x-badge tone="neutral">{{ __('servers.firewall_outgoing', ['value' => $fwDefaults['outgoing'] ?? '—']) }}</x-badge>
|
||||
@else
|
||||
<span class="font-mono text-[10px] uppercase tracking-wider text-ink-4">{{ __('servers.firewall_zone') }}</span>
|
||||
<x-badge tone="neutral">{{ $fwDefaults['zone'] ?? '—' }}</x-badge>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if ($fwReadOnly)
|
||||
<div class="flex items-start gap-2.5 border-b border-line bg-inset px-4 py-2.5 sm:px-5">
|
||||
<x-icon name="lock" class="mt-0.5 h-3.5 w-3.5 shrink-0 text-ink-4" />
|
||||
<p class="font-mono text-[11px] leading-relaxed text-ink-3">{{ __('servers.firewall_readonly') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="divide-y divide-line">
|
||||
@forelse ($fwRules as $rule)
|
||||
{{-- $ruleTone, NOT $tone: the page-level $tone is a closure used by the
|
||||
gauges/Volumes panel; reusing the name here would clobber it. --}}
|
||||
@php $ruleTone = $actTone[$rule['action'] ?? 'ALLOW'] ?? 'online'; @endphp
|
||||
<div class="flex items-center gap-3 px-4 py-2.5 sm:px-5">
|
||||
<span @class([
|
||||
'h-2 w-2 shrink-0 rounded-full',
|
||||
'bg-online' => $ruleTone === 'online',
|
||||
'bg-warning' => $ruleTone === 'warning',
|
||||
'bg-offline' => $ruleTone === 'offline',
|
||||
])></span>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate font-mono text-sm text-ink">{{ $rule['label'] ?? ($rule['to'] ?? $rule['raw']) }}</p>
|
||||
@if ($fwTool === 'ufw' && ($rule['from'] ?? '') !== '' && ! \Illuminate\Support\Str::contains($rule['from'], 'Anywhere'))
|
||||
<p class="truncate font-mono text-[11px] text-ink-3">{{ __('servers.firewall_rule_from', ['value' => $rule['from']]) }}</p>
|
||||
@endif
|
||||
</div>
|
||||
<span @class([
|
||||
'shrink-0 font-mono text-[10px] uppercase tracking-wider',
|
||||
'text-online' => $ruleTone === 'online',
|
||||
'text-warning' => $ruleTone === 'warning',
|
||||
'text-offline' => $ruleTone === 'offline',
|
||||
])>{{ strtolower($rule['action'] ?? 'allow') }}</span>
|
||||
@unless ($fwReadOnly)
|
||||
<x-btn variant="danger-soft" size="sm" icon class="shrink-0" title="{{ __('servers.firewall_remove_rule') }}"
|
||||
wire:click="confirmDeleteRule({{ $loop->index }})">
|
||||
<x-icon name="trash" class="h-3.5 w-3.5" />
|
||||
</x-btn>
|
||||
@endunless
|
||||
</div>
|
||||
@empty
|
||||
<div class="px-4 py-4 sm:px-5">
|
||||
<p class="font-mono text-[11px] text-ink-3">{{ __('servers.firewall_no_rules') }}</p>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
@endif
|
||||
</x-panel>
|
||||
|
||||
{{-- fail2ban-Status --}}
|
||||
@php
|
||||
$f2b = $fail2ban ?? [];
|
||||
$f2bSupported = $f2b['supported'] ?? false;
|
||||
$f2bReadError = $f2b['readError'] ?? false;
|
||||
|
|
@ -352,90 +254,188 @@
|
|||
$f2bJails = $f2b['jails'] ?? [];
|
||||
$f2bIgnore = $f2b['ignoreip'] ?? [];
|
||||
$f2bBanned = array_sum(array_map(fn ($j) => $j['currentlyBanned'] ?? 0, $f2bJails));
|
||||
@endphp
|
||||
<x-panel :title="__('servers.fail2ban_title')"
|
||||
:subtitle="$f2bSupported ? ($f2bInstalled ? ($f2bActive ? __('servers.fail2ban_sub_active', ['count' => $f2bBanned]) : __('servers.fail2ban_sub_installed_inactive')) : __('servers.fail2ban_sub_not_installed')) : __('servers.fail2ban_unavailable')"
|
||||
:padded="false">
|
||||
@if ($f2bSupported && $f2bInstalled && $f2bActive)
|
||||
<x-slot:actions>
|
||||
<x-btn variant="secondary" size="sm" icon title="{{ __('servers.fail2ban_configure') }}"
|
||||
wire:click="$dispatch('openModal', { component: 'modals.fail2ban-config', arguments: { serverId: {{ $server->id }} } })">
|
||||
<x-icon name="settings" class="h-3.5 w-3.5" />
|
||||
</x-btn>
|
||||
<x-btn variant="accent" size="sm"
|
||||
wire:click="$dispatch('openModal', { component: 'modals.fail2ban-ban', arguments: { serverId: {{ $server->id }}, jails: {{ \Illuminate\Support\Js::from(array_map(fn ($j) => $j['name'], $f2bJails)) }} } })">
|
||||
<x-icon name="lock" class="h-3.5 w-3.5" /> {{ __('servers.fail2ban_ban_ip') }}
|
||||
</x-btn>
|
||||
</x-slot:actions>
|
||||
@endif
|
||||
|
||||
@if (! $f2bSupported)
|
||||
<div class="px-4 py-4 sm:px-5">
|
||||
<p class="font-mono text-[11px] leading-relaxed text-ink-3">{{ $f2b['reason'] ?? __('servers.fail2ban_not_supported') }}</p>
|
||||
</div>
|
||||
@elseif ($f2bReadError)
|
||||
<div class="flex flex-wrap items-center gap-2.5 px-4 py-4 sm:px-5">
|
||||
<x-icon name="alert" class="h-4 w-4 shrink-0 text-warning" />
|
||||
<p class="font-mono text-[11px] text-ink-3">{{ __('servers.fail2ban_read_error') }}</p>
|
||||
</div>
|
||||
@elseif (! $f2bInstalled || ! $f2bActive)
|
||||
<div class="flex flex-wrap items-center justify-between gap-3 px-4 py-4 sm:px-5">
|
||||
<p class="font-mono text-[11px] text-ink-3">{{ $f2bInstalled ? __('servers.fail2ban_state_installed_inactive') : __('servers.fail2ban_state_not_installed') }}</p>
|
||||
<x-btn variant="secondary" size="sm"
|
||||
wire:click="$dispatch('openModal', { component: 'modals.hardening-action', arguments: { serverId: {{ $server->id }}, action: 'fail2ban', enable: true } })">
|
||||
{{ __('common.enable') }}
|
||||
</x-btn>
|
||||
</div>
|
||||
@else
|
||||
<div class="divide-y divide-line">
|
||||
@foreach ($f2bJails as $jail)
|
||||
<div class="px-4 py-3 sm:px-5">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<p class="font-mono text-sm text-ink">{{ $jail['name'] }}</p>
|
||||
<p class="font-mono text-[11px] text-ink-3">
|
||||
<span @class(['text-offline' => ($jail['currentlyBanned'] ?? 0) > 0])>{{ __('servers.fail2ban_banned', ['count' => $jail['currentlyBanned'] ?? 0]) }}</span>
|
||||
· {{ __('servers.fail2ban_failed', ['count' => $jail['currentlyFailed'] ?? 0]) }}
|
||||
</p>
|
||||
// Show a panel ONLY when its tool is actually installed (active or inactive).
|
||||
// When absent, the Sicherheit checklist row carries the "Aktivieren" install
|
||||
// path — so the redundant "nicht installiert" box is gone.
|
||||
$showFw = $fwSupported && $fwInstalled;
|
||||
$showF2b = $f2bSupported && $f2bInstalled;
|
||||
$panelCount = ($showFw ? 1 : 0) + ($showF2b ? 1 : 0);
|
||||
@endphp
|
||||
|
||||
@if ($panelCount > 0)
|
||||
<div @class(['grid grid-cols-1 gap-3', 'lg:grid-cols-2 lg:items-start' => $panelCount === 2])>
|
||||
@if ($showFw)
|
||||
{{-- Firewall-Regeln --}}
|
||||
<x-panel :title="__('servers.firewall_title')"
|
||||
:subtitle="$fwToolLabel . ($fwActive ? __('servers.firewall_sub_active') : __('servers.firewall_sub_inactive'))"
|
||||
:padded="false">
|
||||
@if ($fwManageable)
|
||||
<x-slot:actions>
|
||||
<x-btn variant="accent" size="sm"
|
||||
wire:click="$dispatch('openModal', { component: 'modals.firewall-rule', arguments: { serverId: {{ $server->id }}, tool: '{{ $fwTool }}' } })">
|
||||
<x-icon name="plus" class="h-3.5 w-3.5" /> {{ __('servers.firewall_add_rule') }}
|
||||
</x-btn>
|
||||
</x-slot:actions>
|
||||
@endif
|
||||
|
||||
@if ($fwReadError)
|
||||
<div class="flex flex-wrap items-center gap-2.5 px-4 py-4 sm:px-5">
|
||||
<x-icon name="alert" class="h-4 w-4 shrink-0 text-warning" />
|
||||
<p class="font-mono text-[11px] text-ink-3">{{ __('servers.firewall_read_error') }}</p>
|
||||
</div>
|
||||
@if (count($jail['bannedIps'] ?? []))
|
||||
<div class="mt-2 space-y-1">
|
||||
@foreach ($jail['bannedIps'] as $ip)
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<span class="font-mono text-[11px] text-ink-2">{{ $ip }}</span>
|
||||
<x-btn variant="secondary" size="sm" wire:click="unbanIp({{ \Illuminate\Support\Js::from($jail['name']) }}, {{ \Illuminate\Support\Js::from($ip) }})">{{ __('common.unlock') }}</x-btn>
|
||||
@elseif ($fwTool === 'firewalld' && ! $fwActive)
|
||||
<div class="flex flex-wrap items-center justify-between gap-3 px-4 py-4 sm:px-5">
|
||||
<p class="font-mono text-[11px] text-ink-3">{{ __('servers.firewalld_inactive') }}</p>
|
||||
<x-btn variant="secondary" size="sm"
|
||||
wire:click="$dispatch('openModal', { component: 'modals.hardening-action', arguments: { serverId: {{ $server->id }}, action: 'firewall', enable: true } })">
|
||||
{{ __('common.enable') }}
|
||||
</x-btn>
|
||||
</div>
|
||||
@else
|
||||
<div class="flex flex-wrap items-center gap-2 border-b border-line px-4 py-2.5 sm:px-5">
|
||||
@if ($fwTool === 'ufw')
|
||||
<span class="font-mono text-[10px] uppercase tracking-wider text-ink-4">{{ __('servers.firewall_default') }}</span>
|
||||
<x-badge tone="neutral">{{ __('servers.firewall_incoming', ['value' => $fwDefaults['incoming'] ?? '—']) }}</x-badge>
|
||||
<x-badge tone="neutral">{{ __('servers.firewall_outgoing', ['value' => $fwDefaults['outgoing'] ?? '—']) }}</x-badge>
|
||||
@else
|
||||
<span class="font-mono text-[10px] uppercase tracking-wider text-ink-4">{{ __('servers.firewall_zone') }}</span>
|
||||
<x-badge tone="neutral">{{ $fwDefaults['zone'] ?? '—' }}</x-badge>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if ($fwReadOnly)
|
||||
<div class="flex items-start gap-2.5 border-b border-line bg-inset px-4 py-2.5 sm:px-5">
|
||||
<x-icon name="lock" class="mt-0.5 h-3.5 w-3.5 shrink-0 text-ink-4" />
|
||||
<p class="font-mono text-[11px] leading-relaxed text-ink-3">{{ __('servers.firewall_readonly') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="divide-y divide-line">
|
||||
@forelse ($fwRules as $rule)
|
||||
{{-- $ruleTone, NOT $tone: the page-level $tone is a closure used by the
|
||||
gauges/Volumes panel; reusing the name here would clobber it. --}}
|
||||
@php $ruleTone = $actTone[$rule['action'] ?? 'ALLOW'] ?? 'online'; @endphp
|
||||
<div class="flex items-center gap-3 px-4 py-2.5 sm:px-5">
|
||||
<span @class([
|
||||
'h-2 w-2 shrink-0 rounded-full',
|
||||
'bg-online' => $ruleTone === 'online',
|
||||
'bg-warning' => $ruleTone === 'warning',
|
||||
'bg-offline' => $ruleTone === 'offline',
|
||||
])></span>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate font-mono text-sm text-ink">{{ $rule['label'] ?? ($rule['to'] ?? $rule['raw']) }}</p>
|
||||
@if ($fwTool === 'ufw' && ($rule['from'] ?? '') !== '' && ! \Illuminate\Support\Str::contains($rule['from'], 'Anywhere'))
|
||||
<p class="truncate font-mono text-[11px] text-ink-3">{{ __('servers.firewall_rule_from', ['value' => $rule['from']]) }}</p>
|
||||
@endif
|
||||
</div>
|
||||
<span @class([
|
||||
'shrink-0 font-mono text-[10px] uppercase tracking-wider',
|
||||
'text-online' => $ruleTone === 'online',
|
||||
'text-warning' => $ruleTone === 'warning',
|
||||
'text-offline' => $ruleTone === 'offline',
|
||||
])>{{ strtolower($rule['action'] ?? 'allow') }}</span>
|
||||
@unless ($fwReadOnly)
|
||||
<x-btn variant="danger-soft" size="sm" icon class="shrink-0" title="{{ __('servers.firewall_remove_rule') }}"
|
||||
wire:click="confirmDeleteRule({{ $loop->index }})">
|
||||
<x-icon name="trash" class="h-3.5 w-3.5" />
|
||||
</x-btn>
|
||||
@endunless
|
||||
</div>
|
||||
@empty
|
||||
<div class="px-4 py-4 sm:px-5">
|
||||
<p class="font-mono text-[11px] text-ink-3">{{ __('servers.firewall_no_rules') }}</p>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
@endif
|
||||
</x-panel>
|
||||
@endif
|
||||
|
||||
@if ($showF2b)
|
||||
{{-- fail2ban-Status --}}
|
||||
<x-panel :title="__('servers.fail2ban_title')"
|
||||
:subtitle="$f2bActive ? __('servers.fail2ban_sub_active', ['count' => $f2bBanned]) : __('servers.fail2ban_sub_installed_inactive')"
|
||||
:padded="false">
|
||||
@if ($f2bActive)
|
||||
<x-slot:actions>
|
||||
<x-btn variant="secondary" size="sm" icon title="{{ __('servers.fail2ban_configure') }}"
|
||||
wire:click="$dispatch('openModal', { component: 'modals.fail2ban-config', arguments: { serverId: {{ $server->id }} } })">
|
||||
<x-icon name="settings" class="h-3.5 w-3.5" />
|
||||
</x-btn>
|
||||
<x-btn variant="accent" size="sm"
|
||||
wire:click="$dispatch('openModal', { component: 'modals.fail2ban-ban', arguments: { serverId: {{ $server->id }}, jails: {{ \Illuminate\Support\Js::from(array_map(fn ($j) => $j['name'], $f2bJails)) }} } })">
|
||||
<x-icon name="lock" class="h-3.5 w-3.5" /> {{ __('servers.fail2ban_ban_ip') }}
|
||||
</x-btn>
|
||||
</x-slot:actions>
|
||||
@endif
|
||||
|
||||
@if ($f2bReadError)
|
||||
<div class="flex flex-wrap items-center gap-2.5 px-4 py-4 sm:px-5">
|
||||
<x-icon name="alert" class="h-4 w-4 shrink-0 text-warning" />
|
||||
<p class="font-mono text-[11px] text-ink-3">{{ __('servers.fail2ban_read_error') }}</p>
|
||||
</div>
|
||||
@elseif (! $f2bActive)
|
||||
<div class="flex flex-wrap items-center justify-between gap-3 px-4 py-4 sm:px-5">
|
||||
<p class="font-mono text-[11px] text-ink-3">{{ __('servers.fail2ban_state_installed_inactive') }}</p>
|
||||
<x-btn variant="secondary" size="sm"
|
||||
wire:click="$dispatch('openModal', { component: 'modals.hardening-action', arguments: { serverId: {{ $server->id }}, action: 'fail2ban', enable: true } })">
|
||||
{{ __('common.enable') }}
|
||||
</x-btn>
|
||||
</div>
|
||||
@else
|
||||
<div class="divide-y divide-line">
|
||||
@foreach ($f2bJails as $jail)
|
||||
<div class="px-4 py-3 sm:px-5">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<p class="font-mono text-sm text-ink">{{ $jail['name'] }}</p>
|
||||
<p class="font-mono text-[11px] text-ink-3">
|
||||
<span @class(['text-offline' => ($jail['currentlyBanned'] ?? 0) > 0])>{{ __('servers.fail2ban_banned', ['count' => $jail['currentlyBanned'] ?? 0]) }}</span>
|
||||
· {{ __('servers.fail2ban_failed', ['count' => $jail['currentlyFailed'] ?? 0]) }}
|
||||
</p>
|
||||
</div>
|
||||
@if (count($jail['bannedIps'] ?? []))
|
||||
<div class="mt-2 space-y-1">
|
||||
@foreach ($jail['bannedIps'] as $ip)
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<span class="font-mono text-[11px] text-ink-2">{{ $ip }}</span>
|
||||
<x-btn variant="secondary" size="sm" wire:click="unbanIp({{ \Illuminate\Support\Js::from($jail['name']) }}, {{ \Illuminate\Support\Js::from($ip) }})">{{ __('common.unlock') }}</x-btn>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<p class="mt-1 font-mono text-[11px] text-ink-4">{{ __('servers.fail2ban_no_banned') }}</p>
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
{{-- Whitelist (ignoreip) --}}
|
||||
<div class="border-t border-line px-4 py-3 sm:px-5">
|
||||
<p class="mb-2 font-mono text-[10px] uppercase tracking-wider text-ink-4">{{ __('servers.whitelist_label') }}</p>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
@foreach ($f2bIgnore as $ip)
|
||||
<span class="inline-flex items-center gap-1.5 rounded-sm border border-line bg-inset px-2 py-1 font-mono text-[11px] text-ink-2">
|
||||
{{ $ip }}
|
||||
@unless (in_array($ip, ['127.0.0.1/8', '::1'], true))
|
||||
<button type="button" wire:click="removeIgnore({{ \Illuminate\Support\Js::from($ip) }})" class="text-ink-4 hover:text-offline" title="{{ __('common.remove') }}">
|
||||
<x-icon name="x" class="h-3 w-3" />
|
||||
</button>
|
||||
@endunless
|
||||
</span>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<p class="mt-1 font-mono text-[11px] text-ink-4">{{ __('servers.fail2ban_no_banned') }}</p>
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
{{-- Whitelist (ignoreip) --}}
|
||||
<div class="border-t border-line px-4 py-3 sm:px-5">
|
||||
<p class="mb-2 font-mono text-[10px] uppercase tracking-wider text-ink-4">{{ __('servers.whitelist_label') }}</p>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
@foreach ($f2bIgnore as $ip)
|
||||
<span class="inline-flex items-center gap-1.5 rounded-sm border border-line bg-inset px-2 py-1 font-mono text-[11px] text-ink-2">
|
||||
{{ $ip }}
|
||||
@unless (in_array($ip, ['127.0.0.1/8', '::1'], true))
|
||||
<button type="button" wire:click="removeIgnore({{ \Illuminate\Support\Js::from($ip) }})" class="text-ink-4 hover:text-offline" title="{{ __('common.remove') }}">
|
||||
<x-icon name="x" class="h-3 w-3" />
|
||||
</button>
|
||||
@endunless
|
||||
</span>
|
||||
@endforeach
|
||||
</div>
|
||||
<div class="mt-2.5 flex items-center gap-2">
|
||||
<input wire:model="newIgnoreIp" wire:keydown.enter="addIgnore" type="text" placeholder="{{ __('servers.whitelist_placeholder') }}"
|
||||
class="h-8 w-full max-w-xs rounded-md border border-line bg-inset px-2.5 font-mono text-[11px] text-ink placeholder:text-ink-4 focus:border-accent/40 focus:outline-none" />
|
||||
<x-btn variant="secondary" size="sm" wire:click="addIgnore">{{ __('common.add') }}</x-btn>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</x-panel>
|
||||
<div class="mt-2.5 flex items-center gap-2">
|
||||
<input wire:model="newIgnoreIp" wire:keydown.enter="addIgnore" type="text" placeholder="{{ __('servers.whitelist_placeholder') }}"
|
||||
class="h-8 w-full max-w-xs rounded-md border border-line bg-inset px-2.5 font-mono text-[11px] text-ink placeholder:text-ink-4 focus:border-accent/40 focus:outline-none" />
|
||||
<x-btn variant="secondary" size="sm" wire:click="addIgnore">{{ __('common.add') }}</x-btn>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</x-panel>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Volumes + Netzwerk-Interfaces --}}
|
||||
<div class="grid grid-cols-1 gap-3 lg:grid-cols-2">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Livewire\Servers\Show;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Livewire\Livewire;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ServerShowPanelsTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private function server(): Server
|
||||
{
|
||||
return Server::create(['name' => 's', 'ip' => '10.0.0.9', 'ssh_port' => 22, 'status' => 'online']);
|
||||
}
|
||||
|
||||
public function test_firewall_and_fail2ban_panels_hidden_when_not_installed(): void
|
||||
{
|
||||
$this->actingAs(User::factory()->create());
|
||||
|
||||
Livewire::test(Show::class, ['server' => $this->server()])
|
||||
->set('ready', true)
|
||||
->set('firewall', ['supported' => true, 'installed' => false])
|
||||
->set('fail2ban', ['supported' => true, 'installed' => false])
|
||||
->assertDontSee(__('servers.firewall_title'))
|
||||
->assertDontSee(__('servers.fail2ban_title'));
|
||||
}
|
||||
|
||||
public function test_both_panels_shown_when_installed(): void
|
||||
{
|
||||
$this->actingAs(User::factory()->create());
|
||||
|
||||
Livewire::test(Show::class, ['server' => $this->server()])
|
||||
->set('ready', true)
|
||||
->set('firewall', ['supported' => true, 'installed' => true, 'active' => true, 'tool' => 'ufw', 'manageable' => true, 'rules' => [], 'defaults' => []])
|
||||
->set('fail2ban', ['supported' => true, 'installed' => true, 'active' => true, 'jails' => [], 'ignoreip' => []])
|
||||
->assertSee(__('servers.firewall_title'))
|
||||
->assertSee(__('servers.fail2ban_title'));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue