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

167 lines
10 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<div class="space-y-6">
<div class="animate-rise">
<h1 class="text-2xl font-bold tracking-tight text-ink">{{ __('capacity.title') }}</h1>
<p class="mt-1 max-w-[70ch] text-sm text-muted">{{ __('capacity.subtitle') }}</p>
</div>
{{-- ── The queue ────────────────────────────────────────────────────── --}}
<x-ui.card class="animate-rise [animation-delay:60ms]">
<div class="flex flex-wrap items-center gap-x-4 gap-y-2">
<h2 class="font-semibold text-ink">{{ __('capacity.queue_title') }}</h2>
@if ($demand['count'] > 0)
<x-ui.badge status="warning">{{ trans_choice('capacity.queue_count', $demand['count'], ['count' => $demand['count']]) }}</x-ui.badge>
@else
<x-ui.badge status="active">{{ __('capacity.queue_empty_badge') }}</x-ui.badge>
@endif
<x-ui.button wire:click="recheck" variant="secondary" size="sm" class="ml-auto"
wire:loading.attr="disabled" wire:target="recheck">
<x-ui.icon name="refresh" class="size-4" />{{ __('capacity.recheck') }}
</x-ui.button>
</div>
@if ($demand['count'] === 0)
<p class="mt-4 text-sm text-muted">{{ __('capacity.queue_empty') }}</p>
@else
<table class="mt-5 w-full text-left text-sm">
<thead>
<tr class="border-b border-line">
<th class="lbl pb-2 font-normal">{{ __('capacity.col_customer') }}</th>
<th class="lbl pb-2 font-normal">{{ __('capacity.col_plan') }}</th>
<th class="lbl pb-2 text-right font-normal">{{ __('capacity.col_needs') }}</th>
<th class="lbl pb-2 font-normal">{{ __('capacity.col_datacenter') }}</th>
<th class="lbl pb-2 text-right font-normal">{{ __('capacity.col_waiting') }}</th>
<th class="lbl pb-2 font-normal">{{ __('capacity.col_target') }}</th>
</tr>
</thead>
<tbody>
@foreach ($parked as $entry)
@php
// Only hosts in the order's own datacenter: placement
// is a per-datacenter question, and the step refuses a
// pin from anywhere else. Offering one here would be
// offering a choice that silently does nothing.
$choices = $hosts->where('datacenter', $entry['datacenter']);
$pinnedHost = $entry['pinned_host_id'] === null
? null
: $choices->firstWhere('id', $entry['pinned_host_id']);
@endphp
<tr wire:key="parked-{{ $entry['run']->id }}" class="border-b border-line last:border-0">
<td class="py-3 font-medium text-ink">{{ $entry['order']->customer?->name ?? '—' }}</td>
<td class="py-3 text-body">{{ $entry['name'] }}</td>
<td class="py-3 text-right font-mono tabular-nums text-ink">{{ $entry['needs'] }} GB</td>
<td class="py-3 font-mono text-xs text-muted">{{ $entry['datacenter'] }}</td>
<td class="py-3 text-right text-xs text-muted">{{ $entry['waiting_since']->local()->diffForHumans() }}</td>
{{-- One select, one value, no height change: the row
stays a row (R20's exception), and the decision
is the operator's the placement rule only
takes over again when nothing is chosen. --}}
<td class="py-3">
<select wire:change="pin({{ $entry['run']->id }}, $event.target.value)"
class="rounded-md border border-line-strong bg-surface px-2 py-1 text-xs text-body">
<option value="">{{ __('capacity.target_auto') }}</option>
@foreach ($choices as $host)
<option value="{{ $host->id }}" @selected($entry['pinned_host_id'] === $host->id)>
{{ $host->name }} · {{ $host->availableGb() }} GB
</option>
@endforeach
</select>
@if ($pinnedHost !== null && ! $pinnedHost->canTake($entry['needs']))
{{-- A pin is honoured or it waits never
quietly redirected. So say so, or the row
sits there looking like every other one
while it is the choice, not the estate,
that is holding it up. --}}
<p class="mt-1 flex items-center gap-1.5 text-xs text-warning">
<x-ui.icon name="alert-triangle" class="size-3.5 shrink-0" />{{ __('capacity.target_too_small') }}
</p>
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
{{-- What to buy, in numbers. The largest single package decides the
MINIMUM machine an instance lives on one host, so a server
that cannot take the biggest one leaves it parked however much
total space it has. --}}
<div class="mt-6 rounded-lg border border-accent-border bg-accent-subtle p-5">
<p class="lbl !text-accent-text">{{ __('capacity.needed_title') }}</p>
<p class="mt-3 text-sm leading-relaxed text-body">
{{ __('capacity.needed_body', [
'mix' => collect($demand['by_plan'])->map(fn ($n, $plan) => $n.'× '.$plan)->join(', '),
'largest' => $demand['largest'],
'total' => $demand['total'],
]) }}
</p>
</div>
@endif
</x-ui.card>
{{-- ── What the estate can still take ───────────────────────────────── --}}
<x-ui.card :title="__('capacity.hosts_title')" class="animate-rise [animation-delay:120ms]">
@if ($hosts->isEmpty())
<p class="text-sm text-muted">{{ __('capacity.hosts_empty') }}</p>
@else
<table class="w-full text-left text-sm">
<tbody>
@foreach ($hosts as $host)
<tr class="border-b border-line last:border-0">
<td class="py-3 font-medium text-ink">{{ $host->name }}</td>
<td class="py-3 font-mono text-xs text-muted">{{ $host->datacenter }}</td>
<td class="py-3 text-right font-mono tabular-nums text-body">
{{ $host->availableGb() }} / {{ $host->freeGb() }} GB {{ __('capacity.free') }}
</td>
</tr>
@endforeach
</tbody>
</table>
@endif
@if ($unplaceable !== [])
<x-ui.alert variant="warning" class="mt-5">
{{ __('capacity.unsellable', [
'plans' => collect($unplaceable)->pluck('name')->join(', '),
]) }}
</x-ui.alert>
@endif
</x-ui.card>
{{-- ── What such a machine costs today ──────────────────────────────── --}}
@if ($demand['count'] > 0)
<x-ui.card :title="__('capacity.market_title')" class="animate-rise [animation-delay:180ms]">
<p class="text-sm leading-relaxed text-muted">{{ __('capacity.market_body', ['gb' => $demand['largest']]) }}</p>
@if ($offers === [])
<p class="mt-4 text-sm text-muted">{{ __('capacity.market_none') }}</p>
@else
<table class="mt-4 w-full text-left text-sm">
<thead>
<tr class="border-b border-line">
<th class="lbl pb-2 font-normal">CPU</th>
<th class="lbl pb-2 text-right font-normal">RAM</th>
<th class="lbl pb-2 font-normal">{{ __('capacity.col_disks') }}</th>
<th class="lbl pb-2 text-right font-normal">{{ __('capacity.col_usable') }}</th>
<th class="lbl pb-2 font-normal">{{ __('capacity.col_location') }}</th>
<th class="lbl pb-2 text-right font-normal">{{ __('capacity.col_price') }}</th>
</tr>
</thead>
<tbody>
@foreach ($offers as $offer)
<tr class="border-b border-line last:border-0">
<td class="py-3 text-body">{{ $offer['cpu'] }}</td>
<td class="py-3 text-right font-mono tabular-nums text-body">{{ $offer['ram'] }} GB</td>
<td class="py-3 font-mono text-xs text-muted">{{ $offer['disks'] }}</td>
<td class="py-3 text-right font-mono tabular-nums text-ink">{{ number_format($offer['flash_gb'], 0, ',', '.') }} GB</td>
<td class="py-3 font-mono text-xs text-muted">{{ $offer['datacenter'] }}</td>
<td class="py-3 text-right font-mono tabular-nums font-semibold text-ink">{{ number_format($offer['price'], 2, ',', '.') }} </td>
</tr>
@endforeach
</tbody>
</table>
<p class="mt-3 text-xs text-muted">{{ __('capacity.market_note') }}</p>
@endif
</x-ui.card>
@endif
</div>