CluPilotCloud/resources/views/components/admin/vpn-peer-row.blade.php

123 lines
7.1 KiB
PHP

@props(['peer'])
{{--
Eine Zeile der Zugangsliste.
Ausgelagert, als die Liste in zwei Gruppen zerfiel (Personen und Hosts).
Sie zweimal hinzuschreiben hätte geheißen, sie ab dem nächsten Knopf an
zwei Stellen zu pflegen und die zweite fällt erst auf, wenn jemand einen
Host-Zugang sucht und ihn anders aussehen sieht als seinen eigenen.
Ursprünglicher Kopfkommentar der Liste, weil er die Form dieser Zeile
erklärt: Ein Register von Zugängen, keine Tabelle. Sieben Spalten ließen
jede davon zu schmal Namen brachen um, „Letzter Kontakt" brach in seiner
eigenen Überschrift um, und die Zeile passte trotzdem nicht. Das sind
Eigenschaften EINES Zugangs, keine Größen, die jemand über eine Spalte
hinweg vergleicht: also je Zugang eine eigene Zeile, Identität oben,
Messwerte darunter in Mono, wo sie sich von selbst untereinander stellen.
--}}
@php
$status = $peer->status();
$tone = [
'online' => 'border-success-border bg-success-bg text-success',
'idle' => 'border-line-strong bg-surface-2 text-muted',
'blocked' => 'border-danger-border bg-danger-bg text-danger',
'pending' => 'border-warning-border bg-warning-bg text-warning',
][$status];
// Der Strich trägt den Zustand mit, damit ein gesperrter Zugang beim
// Überfliegen auffällt, ohne dass jemand eine Plakette liest.
$rule = [
'online' => 'bg-success-bright',
'idle' => 'bg-line-strong',
'blocked' => 'bg-danger',
'pending' => 'bg-warning',
][$status];
@endphp
<li wire:key="peer-{{ $peer->uuid }}" class="relative flex flex-wrap items-start gap-x-4 gap-y-3 py-4 pl-6 pr-4 transition hover:bg-surface-hover">
<span class="absolute left-0 top-4 bottom-4 w-0.5 rounded-pill {{ $rule }}" aria-hidden="true"></span>
<div class="min-w-0 flex-1">
<div class="flex flex-wrap items-center gap-x-2.5 gap-y-1">
<span class="font-medium text-ink">{{ $peer->name }}</span>
{{-- Die Plakette bleibt, obwohl die Gruppe daneben dasselbe sagt:
eine Zeile wandert beim Suchen aus ihrer Überschrift heraus,
und dann steht sie ohne sie da. --}}
<span class="inline-flex items-center gap-1 rounded-pill border border-line px-2 py-0.5 text-[11px] text-muted">
<x-ui.icon :name="$peer->host ? 'server' : 'users'" class="size-3" />
{{ $peer->host ? __('vpn.host_peer') : __('vpn.operator_peer') }}
</span>
@if ($peer->owner)
<span class="text-sm text-muted">{{ $peer->owner->name }}</span>
@if ($peer->user_id === auth()->id())
<span class="rounded-pill border border-accent-border bg-accent-subtle px-1.5 py-0.5 text-[10px] font-medium text-accent-text">{{ __('vpn.you') }}</span>
@endif
@elseif (! $peer->host)
<span class="text-sm text-muted">{{ __('vpn.no_owner') }}</span>
@endif
</div>
{{-- Die Messwerte, jeder mit eigener Beschriftung: ohne Kopfzeile
müssen sie sich selbst benennen. --}}
<div class="mt-1.5 flex flex-wrap items-center gap-x-5 gap-y-1 font-mono text-xs text-muted">
<span class="text-body">{{ $peer->allowed_ip }}</span>
@if ($peer->endpoint)
<span class="text-muted">{{ $peer->endpoint }}</span>
@endif
<span class="whitespace-nowrap">&darr;&nbsp;{{ \App\Support\Bytes::human($peer->rx_bytes) }}&nbsp;&nbsp;&uarr;&nbsp;{{ \App\Support\Bytes::human($peer->tx_bytes) }}</span>
<span class="whitespace-nowrap">{{ __('vpn.handshake') }}: {{ $peer->last_handshake_at?->diffForHumans() ?? __('vpn.never') }}</span>
</div>
</div>
<div class="flex shrink-0 items-center gap-2">
<span class="inline-flex items-center gap-1.5 whitespace-nowrap rounded-pill border px-2.5 py-0.5 text-xs font-medium {{ $tone }}">
<span class="size-1.5 rounded-pill bg-current" aria-hidden="true"></span>
{{ __('vpn.status_'.$status) }}
</span>
<div class="inline-flex gap-1">
{{-- Eine hinterlegte Konfiguration lässt sich erneut abholen; ohne
eine gibt es nichts auszugeben, und das zu sagen ist besser als
ein fehlender Knopf. --}}
@if (! $peer->hasStoredConfig() && $peer->kind === \App\Models\VpnPeer::KIND_STAFF && $peer->user_id === auth()->id())
<span title="{{ __('vpn.no_stored_config') }}"
class="grid size-8 cursor-help place-items-center rounded-md border border-dashed border-line text-muted">
<x-ui.icon name="download" class="size-4" />
</span>
@endif
@can('downloadConfig', $peer)
<button type="button" aria-label="{{ __('vpn.get_config') }}" title="{{ __('vpn.get_config') }}"
x-on:click="$dispatch('openModal', { component: 'admin.vpn-config-access', arguments: { uuid: '{{ $peer->uuid }}' } })"
class="grid size-8 place-items-center rounded-md border border-line text-muted transition hover:border-accent-border hover:text-accent-text">
<x-ui.icon name="download" class="size-4" />
</button>
@endcan
@can('block', $peer)
<button type="button" wire:click="toggle('{{ $peer->uuid }}')"
aria-label="{{ $peer->enabled ? __('vpn.block') : __('vpn.unblock') }}" title="{{ $peer->enabled ? __('vpn.block') : __('vpn.unblock') }}"
class="grid size-8 place-items-center rounded-md border border-line text-muted transition hover:border-warning-border hover:text-warning">
<x-ui.icon :name="$peer->enabled ? 'lock' : 'unlock'" class="size-4" />
</button>
@endcan
@can('update', $peer)
@if ($peer->kind === \App\Models\VpnPeer::KIND_STAFF)
<button type="button"
x-on:click="$dispatch('openModal', { component: 'admin.confirm-reissue-vpn-peer', arguments: { uuid: '{{ $peer->uuid }}' } })"
title="{{ __('vpn.reissue') }}" aria-label="{{ __('vpn.reissue') }}"
class="grid size-8 place-items-center rounded-md border border-line text-muted transition hover:border-accent-border hover:text-accent-text">
<x-ui.icon name="refresh" class="size-4" />
</button>
@endif
@endcan
@can('delete', $peer)
<button type="button" aria-label="{{ __('vpn.delete') }}" title="{{ __('vpn.delete') }}"
x-on:click="$dispatch('openModal', { component: 'admin.confirm-delete-vpn-peer', arguments: { uuid: '{{ $peer->uuid }}' } })"
class="grid size-8 place-items-center rounded-md border border-line text-muted transition hover:border-danger hover:text-danger">
<x-ui.icon name="trash-2" class="size-4" />
</button>
@endcan
</div>
</div>
</li>