@php
// Threshold tone for gauges/bars: >=90 offline, >=75 warning, else online.
$tone = fn (int $v): string => $v >= 90 ? 'offline' : ($v >= 75 ? 'warning' : 'online');
$barColor = ['online' => 'bg-online', 'warning' => 'bg-warning', 'offline' => 'bg-offline'];
$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 ?? [];
$cores = $specs['cores'] ?? null;
$ramGb = $specs['ram_gb'] ?? null;
$diskGb = $specs['disk_gb'] ?? null;
$ramUsed = $ramGb !== null ? round((float) $ramGb * $server->mem / 100, 1) : null;
$diskUsed = $diskGb !== null ? (int) round((float) $diskGb * $server->disk / 100) : null;
$specRows = [
[__('servers.spec_arch'), $specs['arch'] ?? '—'],
[__('servers.spec_kernel'), $specs['kernel'] ?? '—'],
[__('servers.spec_virt'), $specs['virt'] ?? '—'],
[__('servers.spec_cores'), $cores ?? '—'],
[__('servers.spec_ram'), $ramGb !== null ? __('servers.gb', ['value' => $ramGb]) : '—'],
[__('servers.spec_disk'), $diskGb !== null ? __('servers.gb', ['value' => $diskGb]) : '—'],
];
// Detected OS tooling (populated once the live profile loads).
if (! empty($os)) {
$pm = ($os['packageManager'] ?? 'none') !== 'none' ? $os['packageManager'] : '—';
$fw = ($os['firewallTool'] ?? 'none') !== 'none' ? $os['firewallTool'] : '—';
$specRows[] = [__('servers.spec_system'), $os['familyLabel'] ?? '—'];
$specRows[] = [__('servers.spec_package_manager'), $pm];
$specRows[] = [__('servers.spec_firewall'), $fw];
}
$vitals = [
['label' => __('servers.vital_cpu'), 'icon' => 'cpu', 'val' => (int) $server->cpu, 'sub' => ($loadAvg > 0 ? __('servers.load', ['value' => number_format($loadAvg, 2)]).' · ' : '').($cores ? __('servers.cores_count', ['count' => $cores]) : '—')],
['label' => __('servers.vital_ram'), 'icon' => 'activity', 'val' => (int) $server->mem, 'sub' => $ramUsed !== null ? __('servers.ram_used_of_total', ['used' => $ramUsed, 'total' => $ramGb]) : '—'],
['label' => __('servers.vital_disk'), 'icon' => 'server', 'val' => (int) $server->disk, 'sub' => $diskUsed !== null ? __('servers.disk_used_of_total', ['used' => $diskUsed, 'total' => $diskGb]) : '—'],
];
@endphp
{{ __('servers.back_to_fleet') }}
{{-- Hero header --}}
$server->status === 'online',
'border-warning/30 bg-warning/10 text-warning' => $server->status === 'warning',
'border-offline/30 bg-offline/10 text-offline' => $server->status === 'offline',
'border-cyan/30 bg-cyan/10 text-cyan' => $server->status === 'pending',
])>
{{ $server->name }}
{{ $statusLabel }}
{{ $server->hostname }}
·
{{ $server->ip }}
·
{{ $server->os }}
·
{{ __('servers.uptime', ['value' => $server->uptime ?: '—']) }}
·
{{ __('servers.last_seen', ['value' => optional($server->last_seen_at)->diffForHumans() ?? '—']) }}
{{ __('servers.system_updates') }}
{{ __('servers.files') }}
@if ($ready && ! $connected)
{{ __('servers.offline_banner') }}
@endif
{{-- SSH-Zugang (Credential) --}}
@php
$cred = $server->credential;
$credStatus = $cred ? ($cred->disabled ? 'offline' : 'online') : null;
$credStatusLabel = $cred ? ($cred->disabled ? __('servers.cred_locked') : __('servers.cred_active')) : null;
$credAuthLabel = $cred ? ($cred->auth_type === 'key' ? __('servers.cred_auth_key') : __('servers.cred_auth_password')) : null;
@endphp
@if ($cred)
! $cred->disabled,
'border-offline/30 bg-offline/10 text-offline' => $cred->disabled,
])>
{{ $cred->name ?: $cred->username }}
{{ $credStatusLabel }}
{{ $credAuthLabel }}
{{ $cred->username }} @{{ $server->ip }}
{{ __('common.edit') }}
{{ $cred->disabled ? __('common.unlock') : __('common.lock') }}
{{ __('common.delete') }}
@else
{{ __('servers.cred_none_title') }}
{{ __('servers.cred_none_hint') }}
{{ __('servers.cred_deposit') }}
@endif
{{-- Vitals: live gauges, full width, dense --}}
@foreach ($vitals as $v)
{{ $v['label'] }}
{{ $v['sub'] }}
@endforeach
@if ($ready)
{{-- Spezifikationen + Sicherheit --}}
@foreach ($specRows as [$key, $val])
{{ $key }}
{{ $val }}
@endforeach
@foreach ($hardening as $check)
@php
$supported = $check['supported'] ?? true;
// Neutral rows (auto-updates) are a preference, not a security
// verdict: muted glyph + aktiv/inaktiv, never SICHER/OFFEN.
$neutral = $check['neutral'] ?? false;
@endphp
{{-- One calm state signal: a lock glyph (geschlossen = sicher, offen = offen);
unsupported/neutral items render muted, no warning tone. --}}
$supported && ! $neutral && $check['secure'],
'border-warning/25 bg-warning/10 text-warning' => $supported && ! $neutral && ! $check['secure'],
'border-line bg-raised text-ink-4' => ! $supported || $neutral,
])>
{{ $check['label'] }}
@if (! $supported)
{{ __('common.unsupported') }}
@elseif ($neutral)
{{ $check['featureOn'] ? __('common.active') : __('common.inactive') }}
@else
$check['secure'],
'text-warning' => ! $check['secure'],
])>{{ $check['secure'] ? __('servers.check_secure') : __('servers.check_open') }}
@endif
{{ $supported ? $check['detail'] : $check['reason'] }}
{{-- Key-only warning: disabling password login leaves SSH-key access only. --}}
@if ($supported && $check['key'] === 'ssh_password' && $check['featureOn'])
{{ __('servers.ssh_key_only_hint') }}
@endif
@if ($supported)
@if ($check['key'] === 'fail2ban')
@endif
{{-- Uniform style across all rows (R10): always the bordered secondary button. --}}
{{ $check['featureOn'] ? __('common.disable') : __('common.enable') }}
@endif
@endforeach
{{-- Firewall + fail2ban: only the tools actually installed, side-by-side on lg --}}
@php
$fw = $firewall ?? [];
$fwTool = $fw['tool'] ?? 'ufw';
$fwToolLabel = $fwTool === 'firewalld' ? 'firewalld' : 'UFW';
$fwSupported = $fw['supported'] ?? false;
$fwInstalled = $fw['installed'] ?? false;
$fwActive = $fw['active'] ?? false;
$fwManageable = $fw['manageable'] ?? false;
$fwReadOnly = $fw['readOnly'] ?? false;
$fwReadError = $fw['readError'] ?? false;
$fwRules = $fw['rules'] ?? [];
$fwDefaults = $fw['defaults'] ?? [];
$actTone = ['ALLOW' => 'online', 'LIMIT' => 'warning', 'DENY' => 'offline', 'REJECT' => 'offline'];
$f2b = $fail2ban ?? [];
$f2bSupported = $f2b['supported'] ?? false;
$f2bReadError = $f2b['readError'] ?? false;
$f2bInstalled = $f2b['installed'] ?? false;
$f2bActive = $f2b['active'] ?? false;
$f2bJails = $f2b['jails'] ?? [];
$f2bIgnore = $f2b['ignoreip'] ?? [];
$f2bBanned = array_sum(array_map(fn ($j) => $j['currentlyBanned'] ?? 0, $f2bJails));
// Show a panel when its tool is actually installed (active or inactive), OR
// when the privileged read FAILED (readError) — a failed probe must stay
// visible so the operator can tell it apart from a genuinely-absent tool
// (both report installed=false). When truly absent, the Sicherheit checklist
// 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);
@endphp
@if ($panelCount > 0)
{{-- Two tools → side-by-side; a lone tool is capped so it doesn't stretch edge-to-edge. --}}
$panelCount === 2,
'lg:max-w-2xl' => $panelCount === 1,
])>
@if ($showFw)
{{-- Firewall-Regeln --}}
@if ($fwManageable)
{{ __('servers.firewall_add_rule') }}
@endif
@if ($fwReadError)
{{ __('servers.firewall_read_error') }}
@elseif ($fwTool === 'firewalld' && ! $fwActive)
{{ __('servers.firewalld_inactive') }}
{{ __('common.enable') }}
@else
@if ($fwTool === 'ufw')
{{ __('servers.firewall_default') }}
{{ __('servers.firewall_incoming', ['value' => $fwDefaults['incoming'] ?? '—']) }}
{{ __('servers.firewall_outgoing', ['value' => $fwDefaults['outgoing'] ?? '—']) }}
@else
{{ __('servers.firewall_zone') }}
{{ $fwDefaults['zone'] ?? '—' }}
@endif
@if ($fwReadOnly)
{{ __('servers.firewall_readonly') }}
@endif
@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
$ruleTone === 'online',
'bg-warning' => $ruleTone === 'warning',
'bg-offline' => $ruleTone === 'offline',
])>
{{ $rule['label'] ?? ($rule['to'] ?? $rule['raw']) }}
@if ($fwTool === 'ufw' && ($rule['from'] ?? '') !== '' && ! \Illuminate\Support\Str::contains($rule['from'], 'Anywhere'))
{{ __('servers.firewall_rule_from', ['value' => $rule['from']]) }}
@endif
$ruleTone === 'online',
'text-warning' => $ruleTone === 'warning',
'text-offline' => $ruleTone === 'offline',
])>{{ strtolower($rule['action'] ?? 'allow') }}
@unless ($fwReadOnly)
@endunless
@empty
{{ __('servers.firewall_no_rules') }}
@endforelse
@endif
@endif
@if ($showF2b)
{{-- fail2ban-Status --}}
@if ($f2bActive)
{{ __('servers.fail2ban_ban_ip') }}
@endif
@if ($f2bReadError)
{{ __('servers.fail2ban_read_error') }}
@elseif (! $f2bActive)
{{ __('servers.fail2ban_state_installed_inactive') }}
{{ __('common.enable') }}
@else
@foreach ($f2bJails as $jail)
{{ $jail['name'] }}
($jail['currentlyBanned'] ?? 0) > 0])>{{ __('servers.fail2ban_banned', ['count' => $jail['currentlyBanned'] ?? 0]) }}
· {{ __('servers.fail2ban_failed', ['count' => $jail['currentlyFailed'] ?? 0]) }}
@if (count($jail['bannedIps'] ?? []))
@foreach ($jail['bannedIps'] as $ip)
{{ $ip }}
{{ __('common.unlock') }}
@endforeach
@else
{{ __('servers.fail2ban_no_banned') }}
@endif
@endforeach
{{-- Whitelist (ignoreip) --}}
{{ __('servers.whitelist_label') }}
@foreach ($f2bIgnore as $ip)
{{ $ip }}
@unless (in_array($ip, ['127.0.0.1/8', '::1'], true))
@endunless
@endforeach
{{ __('common.add') }}
@endif
@endif
@endif
{{-- Volumes + Netzwerk-Interfaces --}}
@forelse ($volumes as $vol)
@php $vt = $tone((int) $vol['used']); @endphp
{{ $vol['mount'] }}
{{ $vol['fs'] }} · {{ $vol['size'] }}
@empty
{{ __('servers.no_volumes') }}
@endforelse
{{ __('servers.col_name') }}
{{ __('servers.col_ip') }}
{{ __('servers.col_mac') }}
{{ __('servers.col_rx') }}
{{ __('servers.col_tx') }}
@foreach ($interfaces as $if)
{{ $if['name'] }}
{{ $if['ip'] }}
{{ $if['mac'] }}
{{ $if['rx'] }}
{{ $if['tx'] }}
@endforeach
{{-- SSH keys --}}
{{ __('servers.ssh_keys_add') }}
@forelse ($sshKeys as $key)
{{ $key['comment'] }}
{{ $key['type'] }}
{{ $key['fingerprint'] }}
@empty
{{ __('servers.ssh_keys_none') }}
@endforelse
@else
{{-- skeletons for the SSH-loaded sections --}}
@foreach ([__('servers.specs_title'), __('servers.security_title')] as $t)
@endforeach
@foreach ([__('servers.volumes_title'), __('servers.interfaces_title')] as $t)
@endforeach
@endif