CluPilotCloud/resources/views/livewire/admin/proxy-hosts.blade.php

145 lines
7.8 KiB
PHP

<div class="space-y-6">
<header class="animate-rise">
<p class="lbl">{{ __('proxy.eyebrow') }}</p>
<h1 class="mt-[7px] text-[23px] font-bold leading-[1.12] tracking-[-0.03em] text-ink min-[901px]:text-[30px]">{{ __('proxy.title') }}</h1>
<p class="mt-2 max-w-[76ch] text-sm leading-relaxed text-muted">{{ __('proxy.subtitle') }}</p>
</header>
@if ($notice)
<x-ui.alert variant="info" class="animate-rise">{{ $notice }}</x-ui.alert>
@endif
{{-- Die Übersicht in Zahlen, vor der Liste. Was ein Betreiber wissen will,
bevor er zwölf Zeilen liest: ist etwas kaputt, läuft etwas bald ab. --}}
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-4 animate-rise [animation-delay:20ms]">
<x-ui.stat-tile :label="__('proxy.stat.total')" :value="$summary['total']" />
<x-ui.stat-tile :label="__('proxy.stat.valid')" :value="$summary['valid']" />
<x-ui.stat-tile :label="__('proxy.stat.soon')" :value="$summary['soon']"
:tone="$summary['soon'] > 0 ? 'warning' : null" />
<x-ui.stat-tile :label="__('proxy.stat.failing')" :value="$summary['failing']"
:tone="$summary['failing'] > 0 ? 'danger' : null" />
</div>
@if ($summary['unchecked'] > 0)
<x-ui.alert variant="info" class="animate-rise [animation-delay:30ms]">
{{ __('proxy.never_checked', ['count' => $summary['unchecked']]) }}
</x-ui.alert>
@endif
<section class="animate-rise [animation-delay:40ms]">
<div class="flex flex-wrap items-end justify-between gap-3">
<div>
<h2 class="text-md font-bold tracking-[-0.01em] text-ink">{{ __('proxy.list_title') }}</h2>
<p class="mt-1 max-w-[70ch] text-sm leading-relaxed text-muted">{{ __('proxy.list_sub') }}</p>
</div>
<div class="flex gap-2">
<x-ui.button variant="secondary" size="sm" wire:click="checkCertificates" wire:loading.attr="disabled">
<x-slot:icon><x-ui.icon name="refresh" class="size-4" /></x-slot:icon>
{{ __('proxy.check') }}
</x-ui.button>
<x-ui.button variant="primary" size="sm" wire:click="apply" wire:loading.attr="disabled">
<x-slot:icon><x-ui.icon name="send" class="size-4" /></x-slot:icon>
{{ __('proxy.apply') }}
</x-ui.button>
</div>
</div>
<x-ui.panel class="mt-4">
@forelse ($hosts as $host)
<div class="grid gap-x-6 gap-y-2 px-6 py-4 sm:grid-cols-[minmax(0,1fr)_auto] sm:items-center">
<div class="min-w-0">
<div class="flex flex-wrap items-center gap-2">
<span class="truncate font-mono text-sm font-medium text-ink">{{ $host->hostname }}</span>
@if ($host->isConsole())
<x-ui.badge status="warning">{{ __('proxy.purpose.console') }}</x-ui.badge>
@endif
@if ($host->isFromConfig())
<span class="text-xs text-muted">{{ __('proxy.from_config') }}</span>
@endif
</div>
{{-- Der Zustand steht als Satz, nicht als Farbe: ein
Punkt in Grün oder Rot sagt nicht, wie lange noch,
und genau das ist hier die Frage. --}}
<p class="mt-1 text-xs leading-relaxed text-muted">
@if ($host->certificate_checked_at === null)
{{ __('proxy.cert.unknown') }}
@elseif ($host->certificate_error)
<span class="text-danger">{{ __('proxy.cert.error', ['error' => $host->certificate_error]) }}</span>
@elseif ($host->daysLeft() === null)
{{ __('proxy.cert.unknown') }}
@elseif ($host->isExpiringSoon())
<span class="text-danger">{{ __('proxy.cert.soon', ['days' => $host->daysLeft(), 'date' => $host->certificate_expires_at->local()->isoFormat('D. MMM YYYY')]) }}</span>
@else
{{ __('proxy.cert.valid', ['days' => $host->daysLeft(), 'date' => $host->certificate_expires_at->local()->isoFormat('D. MMM YYYY')]) }}
@endif
@if ($host->note)
· {{ $host->note }}
@endif
</p>
</div>
@unless ($host->isFromConfig())
<button type="button" wire:click="remove({{ $host->id }})"
class="justify-self-start text-xs font-medium text-muted transition hover:text-danger sm:justify-self-end">
{{ __('proxy.remove') }}
</button>
@endunless
</div>
@empty
<div class="px-6 py-8 text-center text-sm text-muted">{{ __('proxy.empty') }}</div>
@endforelse
</x-ui.panel>
<p class="mt-2 text-xs leading-relaxed text-muted">
@if ($summary['checkedAt'])
{{ __('proxy.last_checked', ['when' => $summary['checkedAt']->local()->isoFormat('D. MMM YYYY, HH:mm')]) }}
@endif
{{ __('proxy.apply_hint') }}
</p>
</section>
<form wire:submit="add" class="animate-rise [animation-delay:60ms]">
<h2 class="text-md font-bold tracking-[-0.01em] text-ink">{{ __('proxy.add_title') }}</h2>
<p class="mt-1 max-w-[70ch] text-sm leading-relaxed text-muted">{{ __('proxy.add_sub') }}</p>
<x-ui.panel class="mt-4">
<x-ui.row :label="__('proxy.field.hostname')" :hint="__('proxy.field.hostname_hint')" for="hostname">
<x-ui.input name="hostname" wire:model="hostname" placeholder="files.clupilot.com" />
</x-ui.row>
<x-ui.row :label="__('proxy.field.purpose')" :hint="__('proxy.field.purpose_hint')" for="purpose">
<select id="purpose" wire:model="purpose"
class="block w-full rounded border border-line bg-surface px-3 py-2 text-sm text-ink transition">
@foreach ($purposes as $option)
<option value="{{ $option }}">{{ __('proxy.purpose.'.$option) }}</option>
@endforeach
</select>
</x-ui.row>
<x-ui.row :label="__('proxy.field.note')" for="note">
<x-ui.input name="note" wire:model="note" />
</x-ui.row>
</x-ui.panel>
<div class="mt-4 flex justify-end">
<x-ui.button variant="primary" type="submit">{{ __('proxy.add') }}</x-ui.button>
</div>
</form>
<form wire:submit="saveAcmeEmail" class="animate-rise [animation-delay:80ms]">
<h2 class="text-md font-bold tracking-[-0.01em] text-ink">{{ __('proxy.acme_title') }}</h2>
<p class="mt-1 max-w-[70ch] text-sm leading-relaxed text-muted">{{ __('proxy.acme_sub') }}</p>
<x-ui.panel class="mt-4">
<x-ui.row :label="__('proxy.field.acme_email')"
:hint="$fallbackEmail ? __('proxy.field.acme_email_hint', ['email' => $fallbackEmail]) : __('proxy.field.acme_email_hint_none')"
for="acmeEmail">
<x-ui.input name="acmeEmail" wire:model="acmeEmail" type="email" :placeholder="$fallbackEmail" />
</x-ui.row>
</x-ui.panel>
<div class="mt-4 flex justify-end">
<x-ui.button variant="secondary" type="submit">{{ __('proxy.save') }}</x-ui.button>
</div>
</form>
</div>