CluPilotCloud/resources/views/livewire/admin/host-detail.blade.php

177 lines
10 KiB
PHP

<div class="space-y-5" @if ($run && in_array($run->status, ['pending', 'running', 'waiting'])) wire:poll.4s @endif>
@php
$badge = ['pending' => 'pending', 'onboarding' => 'provisioning', 'active' => 'active', 'error' => 'failed', 'disabled' => 'suspended'][$host->status] ?? 'info';
@endphp
<div class="flex flex-wrap items-start justify-between gap-4 animate-rise">
<div class="min-w-0">
<a href="{{ route('admin.hosts') }}" wire:navigate class="inline-flex items-center gap-1.5 text-xs font-medium text-muted hover:text-body">
<x-ui.icon name="arrow-left" class="size-4" />{{ __('hosts.back') }}
</a>
<div class="mt-2 flex items-center gap-3">
<h1 class="font-mono text-2xl font-semibold tracking-tight text-ink">{{ $host->name }}</h1>
<x-ui.badge :status="$badge">{{ __('hosts.status.'.$host->status) }}</x-ui.badge>
</div>
<p class="mt-1 font-mono text-xs text-muted">{{ $host->public_ip }} · {{ $host->datacenter }}</p>
</div>
<div class="flex items-center gap-3">
@if ($run && $run->status === 'failed')
<x-ui.button variant="secondary" size="sm" wire:click="retry" wire:loading.attr="disabled">
<x-ui.icon name="rotate-ccw" class="size-4" />{{ __('hosts.retry') }}
</x-ui.button>
@endif
@if (in_array($host->status, ['active', 'disabled'], true))
<x-ui.button variant="secondary" size="sm" wire:click="toggleMaintenance" wire:loading.attr="disabled">
<x-ui.icon name="{{ $host->status === 'active' ? 'shield' : 'check' }}" class="size-4" />
{{ $host->status === 'active' ? __('hosts.detail.drain') : __('hosts.detail.activate') }}
</x-ui.button>
@endif
<x-ui.button variant="danger" size="sm"
x-on:click="$dispatch('openModal', { component: 'admin.confirm-remove-host', arguments: { uuid: '{{ $host->uuid }}' } })">
<x-ui.icon name="trash-2" class="size-4" />{{ __('hosts.remove') }}
</x-ui.button>
</div>
</div>
{{-- Health + resource overview --}}
@php
$hdot = ['online' => 'bg-success-bright', 'stale' => 'bg-warning', 'offline' => 'bg-danger'][$health];
$htone = ['online' => 'text-success', 'stale' => 'text-warning', 'offline' => 'text-danger'][$health];
$usedPct = $host->usedPct();
@endphp
<div class="grid grid-cols-1 gap-4 lg:grid-cols-3 animate-rise">
{{-- Health --}}
<div class="rounded-xl border border-line bg-surface p-5 shadow-xs">
<p class="text-xs font-semibold uppercase tracking-wide text-faint">{{ __('hosts.detail.health') }}</p>
<p class="mt-2 flex items-center gap-2 text-lg font-semibold {{ $htone }}">
<span class="size-2.5 rounded-pill {{ $hdot }} {{ $health === 'online' ? 'animate-pulse' : '' }}" aria-hidden="true"></span>
{{ __('hosts.health.'.$health) }}
</p>
<p class="mt-1 text-xs text-muted">
{{ $host->last_seen_at ? __('hosts.detail.last_seen', ['time' => $host->last_seen_at->diffForHumans()]) : __('hosts.detail.never_seen') }}
</p>
</div>
{{-- Storage capacity --}}
<div class="rounded-xl border border-line bg-surface p-5 shadow-xs">
<p class="text-xs font-semibold uppercase tracking-wide text-faint">{{ __('hosts.detail.storage') }}</p>
@if ($host->total_gb)
<p class="mt-2 font-mono text-lg font-semibold text-ink">{{ $host->availableGb() }} <span class="text-sm font-normal text-muted">/ {{ $host->freeGb() }} GB {{ __('hosts.free') }}</span></p>
<div class="mt-2 h-2 overflow-hidden rounded-pill bg-surface-2">
<div class="h-full rounded-pill {{ $usedPct >= 80 ? 'bg-danger' : 'bg-accent' }}" style="width: {{ $usedPct }}%"></div>
</div>
<div class="mt-2 flex items-center gap-2" x-data="{ r: {{ $host->reserve_pct }} }">
<span class="text-xs text-muted">{{ __('hosts.detail.committed', ['gb' => $host->committedGb()]) }} · {{ __('hosts.detail.reserve_label') }}</span>
<input type="number" min="0" max="90" x-model.number="r" aria-label="{{ __('hosts.detail.reserve_label') }}"
class="w-16 rounded-md border border-line-strong bg-surface px-2 py-1 text-xs text-ink" />
<span class="text-xs text-muted">%</span>
<button type="button" x-on:click="$wire.saveReserve(r)" class="rounded-md border border-line px-2 py-1 text-xs font-semibold text-body hover:border-accent-border hover:text-accent-text">{{ __('hosts.detail.reserve_save') }}</button>
</div>
@else
<p class="mt-2 text-sm text-muted">{{ __('hosts.unknown') }}</p>
@endif
</div>
{{-- Compute --}}
<div class="rounded-xl border border-line bg-surface p-5 shadow-xs">
<p class="text-xs font-semibold uppercase tracking-wide text-faint">{{ __('hosts.detail.compute') }}</p>
<div class="mt-2 grid grid-cols-2 gap-3">
<div>
<p class="font-mono text-lg font-semibold text-ink">{{ $host->cpu_cores ?? '—' }}</p>
<p class="text-xs text-muted">{{ __('hosts.meta.cores') }}</p>
</div>
<div>
<p class="font-mono text-lg font-semibold text-ink">{{ $host->total_ram_mb ? round($host->total_ram_mb / 1024).' GB' : '—' }}</p>
<p class="text-xs text-muted">{{ __('hosts.meta.ram') }}</p>
</div>
</div>
</div>
</div>
{{-- Technical facts --}}
<div class="grid grid-cols-2 gap-3 sm:grid-cols-4 animate-rise [animation-delay:60ms]">
@foreach ([
['meta.wg_ip', $host->wg_ip ?? __('hosts.unknown')],
['detail.node', $host->node ?? __('hosts.unknown')],
['meta.version', $host->pve_version ?? __('hosts.unknown')],
['detail.instances', (string) $instances->count()],
] as [$key, $value])
<div class="rounded-xl border border-line bg-surface p-4 shadow-xs">
<p class="text-xs text-muted">{{ __('hosts.'.$key) }}</p>
<p class="mt-1 truncate font-mono text-sm font-semibold text-ink">{{ $value }}</p>
</div>
@endforeach
</div>
{{-- Hosted instances --}}
<div class="rounded-xl border border-line bg-surface p-5 shadow-xs animate-rise [animation-delay:100ms]">
<h2 class="mb-3 text-sm font-semibold text-ink">{{ __('hosts.detail.hosted') }}</h2>
@if ($instances->isEmpty())
<p class="text-sm text-muted">{{ __('hosts.detail.no_instances') }}</p>
@else
<div class="overflow-x-auto">
<table class="w-full text-sm">
<tbody>
@foreach ($instances as $inst)
@php $ibadge = ['active' => 'active', 'provisioning' => 'provisioning', 'failed' => 'failed', 'suspended' => 'suspended', 'cancellation_scheduled' => 'warning'][$inst->status] ?? 'info'; @endphp
<tr class="border-b border-line last:border-0">
<td class="py-2.5 pr-4 font-mono text-ink">{{ $inst->subdomain ?? '—' }}</td>
<td class="py-2.5 pr-4 text-body">{{ __('billing.plan.'.$inst->plan) }}</td>
<td class="py-2.5 pr-4 font-mono text-xs text-muted">{{ $inst->disk_gb }} GB</td>
<td class="py-2.5 text-right"><x-ui.badge :status="$ibadge">{{ __('hosts.istatus.'.$inst->status) }}</x-ui.badge></td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endif
</div>
@if ($run && $run->status === 'failed')
<div class="flex items-start gap-3 rounded-xl border border-danger-border bg-danger-bg p-4 animate-rise">
<x-ui.icon name="alert-triangle" class="size-5 shrink-0 text-danger" />
<div class="min-w-0">
<p class="text-sm font-semibold text-danger">{{ __('hosts.error_title') }}</p>
<p class="mt-0.5 break-words text-sm text-body">{{ $run->error }}</p>
</div>
</div>
@endif
<div class="grid grid-cols-1 gap-4 lg:grid-cols-5">
<div class="rounded-xl border border-line bg-surface p-5 shadow-xs animate-rise lg:col-span-3">
<h2 class="mb-4 text-sm font-semibold text-ink">{{ __('hosts.progress') }}</h2>
@if ($run)
<x-ui.progress-stepper :steps="$steps" />
@else
<p class="text-sm text-muted">{{ __('hosts.no_run') }}</p>
@endif
</div>
<div class="rounded-xl border border-line bg-surface p-5 shadow-xs animate-rise lg:col-span-2">
<h2 class="mb-4 text-sm font-semibold text-ink">{{ __('hosts.events') }}</h2>
@if ($events->isEmpty())
<p class="text-sm text-muted">{{ __('hosts.no_run') }}</p>
@else
<ol class="space-y-3">
@foreach ($events as $event)
@php
$tone = ['advanced' => 'text-success', 'retry' => 'text-warning', 'failed' => 'text-danger', 'info' => 'text-muted'][$event->outcome] ?? 'text-muted';
@endphp
<li class="flex items-start gap-2.5 text-sm">
<span class="mt-1.5 size-1.5 shrink-0 rounded-pill bg-current {{ $tone }}" aria-hidden="true"></span>
<div class="min-w-0">
<p class="text-body">{{ __('hosts.step.'.$event->step) }}</p>
<p class="text-xs text-faint">
{{ __('hosts.outcome.'.$event->outcome) }}
· {{ __('hosts.attempt') }} {{ $event->attempt }}
@if ($event->message) · <span class="break-words">{{ $event->message }}</span> @endif
</p>
</div>
</li>
@endforeach
</ol>
@endif
</div>
</div>
</div>