clusev/resources/views/livewire/wireguard/index.blade.php

137 lines
8.5 KiB
PHP

@php
$fmtBytes = function (int $n): string {
$u = ['B', 'KB', 'MB', 'GB', 'TB'];
$i = 0;
$v = (float) $n;
while ($v >= 1024 && $i < count($u) - 1) { $v /= 1024; $i++; }
return ($i === 0 ? (string) $n : number_format($v, 1)).' '.$u[$i];
};
$ago = function (int $ts): string {
if ($ts <= 0) return __('wireguard.never');
$d = max(0, time() - $ts);
if ($d < 60) return $d.'s';
if ($d < 3600) return intdiv($d, 60).'m';
if ($d < 86400) return intdiv($d, 3600).'h';
return intdiv($d, 86400).'d';
};
@endphp
<div class="space-y-5" wire:poll.5s>
<div>
<p class="font-mono text-[11px] uppercase tracking-[0.2em] text-accent-text">{{ __('wireguard.eyebrow') }}</p>
<h2 class="mt-0.5 font-display text-xl font-semibold text-ink sm:text-2xl">{{ __('wireguard.heading') }}</h2>
<p class="mt-1 text-sm text-ink-3">{{ __('wireguard.subtitle') }}</p>
</div>
@if (! $status['configured'])
<x-panel :title="__('wireguard.not_configured_title')">
<p class="text-sm leading-relaxed text-ink-2">{{ __('wireguard.not_configured_body') }}</p>
<pre class="mt-3 overflow-x-auto rounded-md border border-line bg-void px-3 py-2.5 font-mono text-[11px] text-ink-2">sudo clusev wg setup</pre>
</x-panel>
@else
@if ($status['stale'])
<div class="flex items-center gap-2 rounded-lg border border-warning/30 bg-warning/10 px-4 py-3">
<x-icon name="alert" class="h-4 w-4 shrink-0 text-warning" />
<span class="text-sm text-ink-2">{{ __('wireguard.stale') }}</span>
</div>
@endif
@php
$win = collect($windows)->mapWithKeys(fn ($s) => [$s => match ($s) {
3600 => __('wireguard.window_1h'), 86400 => __('wireguard.window_24h'), default => __('wireguard.window_7d'),
}])->all();
$fmtB = function (int $n): string {
$u = ['B', 'KB', 'MB', 'GB', 'TB']; $i = 0; $v = (float) $n;
while ($v >= 1024 && $i < count($u) - 1) { $v /= 1024; $i++; }
return ($i === 0 ? (string) $n : number_format($v, 1)).' '.$u[$i];
};
$pts = $traffic['points'];
$n = max(1, count($pts) - 1);
$peak = max(1, $traffic['peak_down'], $traffic['peak_up']);
$line = function (string $key) use ($pts, $n, $peak) {
$out = [];
foreach ($pts as $i => $p) {
$x = round($i / $n * 600, 1);
$y = round(140 - ($p[$key] / $peak) * 132, 1);
$out[] = $x.','.$y;
}
return implode(' ', $out);
};
$hasTraffic = $traffic['total_down'] > 0 || $traffic['total_up'] > 0;
@endphp
<x-panel :title="__('wireguard.traffic_title')" :padded="false">
<div class="flex flex-wrap items-center justify-between gap-3 border-b border-line px-4 py-3 sm:px-5">
<div class="flex items-center gap-4 font-mono text-[11px]">
<span class="text-accent-text">{{ __('wireguard.total_down') }}: <span class="text-ink-2">{{ $fmtB($traffic['total_down']) }}</span></span>
<span class="text-cyan">{{ __('wireguard.total_up') }}: <span class="text-ink-2">{{ $fmtB($traffic['total_up']) }}</span></span>
</div>
<div class="flex items-center gap-1">
@foreach ($windows as $w)
<button type="button" wire:click="setWindow({{ $w }})"
@class([
'rounded-md border px-2.5 py-1 font-mono text-[11px] transition-colors',
'border-accent/40 bg-accent/15 text-accent-text' => $window === $w,
'border-line bg-raised text-ink-3 hover:border-accent/30 hover:text-ink' => $window !== $w,
])>{{ $win[$w] }}</button>
@endforeach
</div>
</div>
<div class="px-4 py-4 sm:px-5">
@if ($hasTraffic)
<svg viewBox="0 0 600 140" preserveAspectRatio="none" class="h-32 w-full" role="img" aria-label="{{ __('wireguard.traffic_title') }}">
<g class="text-cyan"><polyline points="{{ $line('up') }}" fill="none" stroke="currentColor" stroke-width="1.5" vector-effect="non-scaling-stroke" /></g>
<g class="text-accent"><polyline points="{{ $line('down') }}" fill="none" stroke="currentColor" stroke-width="1.5" vector-effect="non-scaling-stroke" /></g>
</svg>
@else
<p class="font-mono text-[11px] text-ink-3">{{ __('wireguard.no_traffic') }}</p>
@endif
</div>
</x-panel>
<div class="grid grid-cols-1 gap-5 lg:grid-cols-[minmax(0,1fr)_320px]">
<x-panel :title="__('wireguard.peers_title')" :subtitle="trans_choice('wireguard.peers_count', count($status['peers']), ['count' => count($status['peers'])])" :padded="false">
<div class="divide-y divide-line">
@forelse ($status['peers'] as $peer)
<div class="flex flex-wrap items-center gap-x-4 gap-y-2 px-4 py-3.5 sm:px-5" wire:key="peer-{{ $peer['pubkey'] }}">
<span class="inline-flex items-center gap-2">
<x-status-dot :status="$peer['online'] ? 'online' : 'offline'" />
<span class="font-mono text-sm font-semibold text-ink">{{ $peer['name'] !== '' ? $peer['name'] : \Illuminate\Support\Str::limit($peer['pubkey'], 10, '…') }}</span>
</span>
<span class="font-mono text-[11px] text-ink-4">{{ $peer['online'] ? __('wireguard.online') : __('wireguard.offline') }}</span>
<span class="ml-auto font-mono text-[11px] text-ink-3">{{ __('wireguard.last_handshake') }}: {{ $ago($peer['latest_handshake']) }}</span>
<span class="w-full font-mono text-[11px] text-ink-3">
{{ $peer['endpoint'] !== '' ? $peer['endpoint'] : '—' }}
<span class="ml-2 text-ink-4">{{ __('wireguard.down_up', ['rx' => $fmtBytes($peer['rx']), 'tx' => $fmtBytes($peer['tx'])]) }}</span>
</span>
</div>
@empty
<p class="px-4 py-4 font-mono text-[11px] text-ink-3 sm:px-5">{{ __('wireguard.no_peers') }}</p>
@endforelse
</div>
</x-panel>
<div class="space-y-5">
<x-panel :title="__('wireguard.gate')">
@if ($status['gate']['marker'] && $status['gate']['chain'])
<x-badge tone="cyan">{{ __('wireguard.gate_on') }}</x-badge>
@else
<x-badge tone="neutral">{{ __('wireguard.gate_off') }}</x-badge>
@endif
<p class="mt-2 font-mono text-[11px] text-ink-4">{{ __('wireguard.gate_escape') }}</p>
</x-panel>
<x-panel :title="__('wireguard.server_title')" :padded="false">
<dl class="space-y-2 px-4 py-4 font-mono text-[11px] sm:px-5">
<div class="flex items-center justify-between gap-3"><dt class="text-ink-4">{{ __('wireguard.subnet') }}</dt><dd class="text-ink-2">{{ $status['server']['subnet'] ?: '—' }}</dd></div>
<div class="flex items-center justify-between gap-3"><dt class="text-ink-4">{{ __('wireguard.server_ip') }}</dt><dd class="text-ink-2">{{ $status['server']['server_ip'] ?: '—' }}</dd></div>
<div class="flex items-center justify-between gap-3"><dt class="text-ink-4">{{ __('wireguard.port') }}</dt><dd class="text-ink-2">{{ $status['server']['port'] ?: '—' }}</dd></div>
<div class="flex items-center justify-between gap-3"><dt class="text-ink-4">{{ __('wireguard.endpoint') }}</dt><dd class="truncate text-ink-2">{{ $status['server']['endpoint'] ?: '—' }}</dd></div>
<div class="flex items-center justify-between gap-3"><dt class="text-ink-4">{{ __('wireguard.service') }}</dt><dd class="text-ink-2">{{ $status['wg_quick'] }}</dd></div>
</dl>
</x-panel>
</div>
</div>
@endif
</div>