Files
Virtnet.bond/extensions/Servers/Proxmox/views/statistics.blade.php
Joseph.Rawlings 9e899cb947 feat(proxmox): rebuild statistics UI and client runtime
New Blade layout, copy, and visual system for the monitoring tab; RRD hosts use data-statistics-chart hooks; tables use pxStatTableKit and data-statistics-data-row; network and comparison panels use new selectors; CSV helpers renamed (pxExport*); modals and telemetry refreshed.

Made-with: Cursor
2026-03-28 11:38:56 -04:00

180 lines
10 KiB
PHP

<x-proxmox::layout :server="$server" view="statistics" :status="$status" :status-polling="true" :status-polling-interval="'2s'">
@once
<script src="https://cdn.jsdelivr.net/npm/apexcharts" onload="window.apexChartsLoaded = true"></script>
@endonce
<script data-navigate-track>
if (typeof window.copyToClipboard === 'undefined') {
window.copyToClipboard = function(text, el) {
navigator.clipboard.writeText(text).then(() => {
const icon = el.querySelector('svg');
if (icon) {
icon.classList.add('text-success');
setTimeout(() => icon.classList.remove('text-success'), 1500);
}
window.dispatchEvent(new CustomEvent('toast', {
detail: { message: 'Value copied to clipboard', type: 'success' }
}));
}).catch(() => {});
};
}
</script>
<div class="flex min-w-0 max-w-full flex-col gap-8" data-server-id="{{ $server->id }}" wire:key="proxmox-statistics-v2-root">
<header class="space-y-2">
<div class="flex items-center gap-3 flex-wrap">
<div class="flex items-center gap-2 min-w-0">
<h2 class="text-2xl font-semibold tracking-tight break-words">Insight console</h2>
<div class="relative" x-data="{ open: false }" x-cloak>
<button type="button" @click="open = true" class="text-base/50 hover:text-base/70 transition-colors" aria-label="Explain this workspace">
<x-ri-question-line class="size-4" />
</button>
<div x-show="open"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
@click="open = false"
@keydown.escape.window="open = false"
class="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm"
style="display: none;">
</div>
<div x-show="open"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
@click.self="open = false"
class="fixed inset-0 z-50 flex items-center justify-center p-4"
style="display: none;">
<div class="relative w-full max-w-2xl max-h-[90vh] overflow-y-auto bg-background-secondary rounded-xl border border-neutral/20 shadow-2xl p-6">
<button type="button" @click="open = false" class="absolute top-4 right-4 text-base/50 hover:text-base transition-colors" aria-label="Close explainer">
<x-ri-close-line class="size-5" />
</button>
<div class="flex items-center gap-3 mb-4 pr-8">
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-primary/12">
<x-ri-dashboard-line class="size-5 text-primary" />
</div>
<h6 class="font-sans text-lg font-semibold leading-tight">What lives on this screen</h6>
</div>
<p class="block font-sans text-sm font-normal leading-relaxed text-base/90 mb-4">
A single pane that stitches together live polls, Proxmox RRD graphs, archived samples, job consoles, lifecycle highlights, period math, and—when configured—Core Router session intelligence.
</p>
<ul class="list-disc space-y-2 pl-5 text-xs text-base/75">
<li><span class="font-semibold text-base/90">Pulse tiles</span> — instant CPU/RAM from the same status feed as Overview, plus archive and signal counts.</li>
<li><span class="font-semibold text-base/90">RRD studio</span> — interactive charts with horizon, blend mode, and CSV extracts.</li>
<li><span class="font-semibold text-base/90">Evidence lockers</span> — tabular archives for samples, UPIDs, and distilled lifecycle beats.</li>
<li><span class="font-semibold text-base/90">Contrast &amp; fabric</span> — compare eras side-by-side, then inspect live sessions, peers, and ports.</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<p class="text-sm text-base/70 max-w-3xl leading-relaxed">
Align what the hypervisor reports right now with what it recorded yesterday—without hopping tabs.
</p>
</header>
@php
$statCpuCores = max(1, (int) ($status?->cpus ?? 1));
$statLiveCpuPct = round(max(0, ($status?->cpu ?? 0) * 100), 1);
$statMemMax = (int) ($status?->maxmem ?? 0);
$statMemUsed = (int) ($status?->mem ?? 0);
$statLiveMemPct = $statMemMax > 0 ? round(($statMemUsed / $statMemMax) * 100, 1) : 0;
$statSnapshotCount = $this->statistics->total();
$statTaskBufferCount = count($this->taskHistory ?? []);
$statLifecyclePreview = count($this->chartEvents ?? []);
$statGlanceCards = [
[
'label' => 'CPU pulse',
'value' => $statLiveCpuPct . '%',
'hint' => $statCpuCores . ' vCPU budget',
'icon' => 'x-ri-cpu-line',
'tone' => 'sky',
],
[
'label' => 'RAM pulse',
'value' => $statLiveMemPct . '%',
'hint' => $statMemMax > 0
? \Illuminate\Support\Number::fileSize(bytes: $statMemUsed) . ' of ' . \Illuminate\Support\Number::fileSize(bytes: $statMemMax)
: 'RAM cap unknown',
'icon' => 'x-ri-database-2-line',
'tone' => 'emerald',
],
[
'label' => 'Stored samples',
'value' => number_format($statSnapshotCount),
'hint' => 'Hourly automation + five manual pins',
'icon' => 'x-ri-stack-line',
'tone' => 'violet',
],
[
'label' => 'Signals loaded',
'value' => number_format($statTaskBufferCount) . ' jobs',
'hint' => $statLifecyclePreview . ' lifecycle beats mapped',
'icon' => 'x-ri-pulse-line',
'tone' => 'amber',
],
];
$toneRing = [
'sky' => 'ring-sky-500/30 bg-sky-500/[0.04]',
'emerald' => 'ring-emerald-500/30 bg-emerald-500/[0.04]',
'violet' => 'ring-violet-500/30 bg-violet-500/[0.04]',
'amber' => 'ring-amber-500/30 bg-amber-500/[0.04]',
];
@endphp
<section class="rounded-2xl ring-1 ring-neutral/40 bg-background-secondary/80 p-6 shadow-sm" aria-labelledby="px-stat-pulse-heading">
<div class="flex flex-col gap-2 md:flex-row md:items-center md:justify-between mb-6">
<div>
<h3 id="px-stat-pulse-heading" class="text-lg font-semibold tracking-tight">Live pulse tiles</h3>
<p class="text-sm text-base/70">Numbers mirror the Overview status ticker; charts underneath carry the longer story.</p>
</div>
<p class="text-xs text-base/50">Point-in-time only—scroll for RRD and archives.</p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4">
@foreach ($statGlanceCards as $card)
<div class="rounded-2xl p-4 ring-1 {{ $toneRing[$card['tone']] ?? 'ring-neutral/30' }} flex flex-col gap-3 transition hover:shadow-md">
<div class="flex items-center justify-between gap-2">
<p class="text-xs font-semibold tracking-wide uppercase text-base/60">{{ $card['label'] }}</p>
<div class="flex size-9 items-center justify-center rounded-xl bg-background/90 ring-1 ring-neutral/30">
<x-dynamic-component :component="$card['icon']" class="size-4 text-primary" />
</div>
</div>
<p class="text-2xl font-semibold leading-tight">{{ $card['value'] }}</p>
<p class="text-xs text-base/60 leading-relaxed">{{ $card['hint'] }}</p>
</div>
@endforeach
</div>
</section>
@include('proxmox::partials.statistics.telemetry-inner')
@include('proxmox::partials.statistics.history-inner')
<div class="grid grid-cols-1 md:grid-cols-2 gap-10 md:items-stretch">
<div class="grid gap-10 items-start min-w-0">
@include('proxmox::partials.statistics.tasks-inner')
</div>
<div class="grid gap-10 items-start min-w-0">
@include('proxmox::partials.statistics.lifecycle-inner')
</div>
</div>
@include('proxmox::partials.statistics.comparison-inner')
@include('proxmox::partials.statistics.network-inner')
@include('proxmox::partials.statistics.modals-inner')
@include('proxmox::partials.statistics.scripts-inner')
</div>
</x-proxmox::layout>