CluPilotCloud/resources/views/livewire/admin/readiness.blade.php

156 lines
9.5 KiB
PHP

<div class="space-y-5">
<div class="animate-rise">
<h1 class="text-2xl font-bold tracking-tight text-ink">{{ __('readiness.page_title') }}</h1>
<p class="mt-1 text-sm text-muted">{{ __('readiness.page_sub') }}</p>
</div>
{{-- The one line that matters. Everything below is detail. --}}
<div class="flex items-center gap-3 rounded-lg border p-5 shadow-xs animate-rise
{{ $ready ? 'border-success-border bg-success-bg' : 'border-warning-border bg-warning-bg' }}">
<x-ui.icon :name="$ready ? 'shield-check' : 'alert-triangle'"
class="{{ $ready ? 'text-success' : 'text-warning' }}" />
<p class="text-lg font-semibold {{ $ready ? 'text-success' : 'text-warning' }}">
@if ($ready)
{{ $mode->isTest() ? __('readiness.ready_test') : __('readiness.ready_live') }}
@else
{{ trans_choice('readiness.not_ready', $blockingCount, ['n' => $blockingCount]) }}
@endif
</p>
</div>
@foreach ($groups as $groupKey => $checks)
<div class="rounded-lg border border-line bg-surface p-5 shadow-xs animate-rise">
<h2 class="font-semibold text-ink">{{ __('readiness.group.'.$groupKey) }}</h2>
<ul class="mt-3 divide-y divide-line">
@foreach ($checks as $check)
@php
$result = $results[$check->key] ?? null;
// DIE MESSUNG GEWINNT.
//
// Der Haken kam bisher allein aus `satisfied` — der
// passiven Prüfung, die nur feststellt, dass etwas
// eingetragen IST. Lief daneben eine echte Messung und
// scheiterte, stand grünes „Erfüllt" über rotem „nicht
// in Ordnung", in derselben Zeile. Auf dem Live-Server
// gleich zweimal: beim DNS-Token und bei der
// VM-Vorlage.
//
// Das ist die Attrappe aus R19 in ihrer teuersten Form.
// Wer die Liste überfliegt, liest die Plakette, nicht
// die Kleinschrift — und geht mit „alles grün" weiter,
// während gemessen wurde, dass es nicht geht.
$erfuellt = $result !== null ? (bool) $result['ok'] : $check->satisfied;
$badgeStatus = $erfuellt ? 'active' : ($check->isBlocking() ? 'failed' : 'warning');
$statusLabel = $erfuellt
? __('readiness.status.satisfied')
: ($check->isBlocking() ? __('readiness.status.blocking') : __('readiness.status.warning'));
$iconName = $erfuellt ? 'check' : ($check->isBlocking() ? 'x' : 'alert-triangle');
$iconTone = $erfuellt ? 'text-success' : ($check->isBlocking() ? 'text-danger' : 'text-warning');
$heartbeat = $heartbeats[$check->key] ?? null;
@endphp
<li class="flex flex-wrap items-start gap-3 py-3">
<x-ui.icon :name="$iconName" class="mt-0.5 size-4 shrink-0 {{ $iconTone }}" />
<div class="min-w-0 flex-1">
<div class="flex flex-wrap items-center gap-2">
<span class="font-medium text-ink">{{ $check->label }}</span>
<x-ui.badge :status="$badgeStatus">{{ $statusLabel }}</x-ui.badge>
</div>
@unless ($erfuellt)
<p class="mt-1 text-sm text-muted">{{ $check->breaks }}</p>
@endunless
@if ($heartbeat !== null)
<p class="mt-1 font-mono text-xs text-faint">
{{ __('readiness.last_heartbeat', ['when' => $heartbeat]) }}
</p>
@endif
@if ($result !== null)
<p class="mt-1 text-xs {{ $result['ok'] ? 'text-success' : 'text-danger' }}">
{{ $result['ok'] ? __('readiness.check_ok') : __('readiness.check_failed') }}
@if (($result['reason'] ?? null) !== null)
<span class="font-mono">{{ $result['reason'] }}</span>
@endif
</p>
{{-- StripeCheck rechnet die Kontoart des Schlüssels längst aus,
und diese Seite hat sie bislang weggeworfen: wer „Prüfen"
drückte, las „Geprüft: in Ordnung" über einen Live-Schlüssel
im Testplatz. Die Prüfung selbst blockiert dafür jetzt
(BillingChecks); hier steht, was der Knopf gerade gesehen
hat, damit er ihr nicht widerspricht. --}}
{{-- Bei `zone_not_found` steht der Betreiber sonst vor einer
Sackgasse: er liest es als „Token falsch" und tauscht ihn
aus, obwohl der Token gerade eben die Zonenliste geholt hat.
Gesucht und gefunden nebeneinander beantwortet die Frage
selbst. --}}
@if (($result['reason'] ?? null) === 'zone_list_unreadable')
<p class="mt-1 text-xs text-muted">
{{ __('readiness.zone_list_unreadable', ['status' => $result['status'] ?? '?', 'body' => $result['body'] ?? '—']) }}
</p>
@endif
@if (($result['reason'] ?? null) === 'zone_list_failed')
<p class="mt-1 text-xs text-muted">
{{ __('readiness.zone_list_unclear', ['status' => $result['status'] ?? '?']) }}
</p>
@endif
@if (($result['reason'] ?? null) === 'zone_not_found')
<p class="mt-1 text-xs text-muted">
{{ __('readiness.zone_wanted', ['zone' => $result['zone'] ?? '—']) }}
@if (! empty($result['available']))
{{ __('readiness.zone_available', ['zones' => implode(', ', $result['available'])]) }}
@else
{{ __('readiness.zone_none') }}
@endif
</p>
@endif
@if (array_key_exists('live', $result))
@php $keyMode = $result['live'] ? 'live' : 'test'; @endphp
<p class="mt-1 text-xs {{ $keyMode === $mode->value ? 'text-muted' : 'text-danger' }}">
{{ __('readiness.check_key_mode', ['mode' => __('readiness.mode.'.$keyMode)]) }}
@unless ($keyMode === $mode->value)
{{ __('readiness.check_key_mode_conflict', ['mode' => __('readiness.mode.'.$mode->value)]) }}
@endunless
</p>
@endif
@endif
</div>
<div class="flex shrink-0 items-center gap-3">
{{-- mount() only requires hosts.manage OR secrets.manage, so an
Admin (hosts.manage alone) can reach this page — but runCheck()
itself demands secrets.manage and 403s otherwise. A button that
is visible and throws when pressed is worse than no button. --}}
@if (in_array($check->key, $runnable, true))
@can('secrets.manage')
<x-ui.button variant="secondary" size="sm"
wire:click="runCheck('{{ $check->key }}')"
wire:loading.attr="disabled"
wire:target="runCheck('{{ $check->key }}')">
<x-ui.icon name="refresh" class="size-4" />
{{ __('readiness.run_check') }}
</x-ui.button>
@endcan
@endif
<a href="{{ $this->checkUrl($check) }}" wire:navigate
class="inline-flex items-center gap-1 text-xs font-semibold text-accent-text hover:underline">
{{ __('readiness.fix_link') }}
<x-ui.icon name="external-link" class="size-4" />
</a>
</div>
</li>
@endforeach
</ul>
</div>
@endforeach
</div>