Enhance Proxmox views with improved loading states and UI updates

- Updated serverplaceholder.blade.php to enhance loading skeletons for server details.
- Added navigation breadcrumbs to statistics, subdomain, and system views for better user experience.
- Improved loading indicators for CPU, network, memory, and disk charts in statistics.blade.php.
- Refined storage.blade.php layout with section dividers and improved drive display, including bus type indicators and quota usage.
- Enhanced VNC and Xterm popups with a new header design, loading animations, and reconnect functionality.
- Added error handling for VNC and Xterm connections to inform users when unable to connect.
This commit is contained in:
2026-02-24 19:51:46 -05:00
parent af62a16354
commit 16cd5f05de
40 changed files with 1423 additions and 475 deletions

View File

@@ -21,6 +21,11 @@
</style>
<!-- Header Section -->
<div class="flex flex-col gap-4">
<div class="flex items-center gap-1.5 text-xs text-base/50 mb-0">
<a href="{{ route('proxmox.server.overview', ['server' => $server->id]) }}" wire:navigate class="hover:text-primary transition-colors">Overview</a>
<x-ri-arrow-right-s-line class="size-3" />
<span class="text-base/70">Statistics</span>
</div>
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div class="flex flex-col gap-1">
<div class="flex items-center gap-2">
@@ -306,7 +311,20 @@
</button>
</div>
</div>
<div id="cpu-chart" wire:ignore></div>
<div class="relative" x-data="{ cpuChartReady: false }" x-init="
let cpuCheckAttempts = 0;
const checkCpu = setInterval(() => {
cpuCheckAttempts++;
if (document.querySelector('#cpu-chart .apexcharts-canvas')) { cpuChartReady = true; clearInterval(checkCpu); }
else if (cpuCheckAttempts >= 150) { clearInterval(checkCpu); }
}, 200);
">
<div class="absolute inset-0 flex items-center justify-center skeleton-loading rounded-lg z-[1]"
x-show="!cpuChartReady" x-transition:leave="transition ease-in duration-300" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0">
<span class="text-xs text-base/40">Loading chart...</span>
</div>
<div id="cpu-chart" wire:ignore></div>
</div>
</div>
</div>
</div>
@@ -403,7 +421,20 @@
</button>
</div>
</div>
<div id="network-chart" wire:ignore></div>
<div class="relative" x-data="{ networkChartReady: false }" x-init="
let netCheckAttempts = 0;
const checkNet = setInterval(() => {
netCheckAttempts++;
if (document.querySelector('#network-chart .apexcharts-canvas')) { networkChartReady = true; clearInterval(checkNet); }
else if (netCheckAttempts >= 150) { clearInterval(checkNet); }
}, 200);
">
<div class="absolute inset-0 flex items-center justify-center skeleton-loading rounded-lg z-[1]"
x-show="!networkChartReady" x-transition:leave="transition ease-in duration-300" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0">
<span class="text-xs text-base/40">Loading chart...</span>
</div>
<div id="network-chart" wire:ignore></div>
</div>
</div>
</div>
</div>
@@ -497,7 +528,20 @@
</button>
</div>
</div>
<div id="memory-chart" wire:ignore></div>
<div class="relative" x-data="{ memoryChartReady: false }" x-init="
let memCheckAttempts = 0;
const checkMem = setInterval(() => {
memCheckAttempts++;
if (document.querySelector('#memory-chart .apexcharts-canvas')) { memoryChartReady = true; clearInterval(checkMem); }
else if (memCheckAttempts >= 150) { clearInterval(checkMem); }
}, 200);
">
<div class="absolute inset-0 flex items-center justify-center skeleton-loading rounded-lg z-[1]"
x-show="!memoryChartReady" x-transition:leave="transition ease-in duration-300" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0">
<span class="text-xs text-base/40">Loading chart...</span>
</div>
<div id="memory-chart" wire:ignore></div>
</div>
</div>
</div>
</div>
@@ -591,7 +635,20 @@
</button>
</div>
</div>
<div id="disk-chart" wire:ignore></div>
<div class="relative" x-data="{ diskChartReady: false }" x-init="
let diskCheckAttempts = 0;
const checkDisk = setInterval(() => {
diskCheckAttempts++;
if (document.querySelector('#disk-chart .apexcharts-canvas')) { diskChartReady = true; clearInterval(checkDisk); }
else if (diskCheckAttempts >= 150) { clearInterval(checkDisk); }
}, 200);
">
<div class="absolute inset-0 flex items-center justify-center skeleton-loading rounded-lg z-[1]"
x-show="!diskChartReady" x-transition:leave="transition ease-in duration-300" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0">
<span class="text-xs text-base/40">Loading chart...</span>
</div>
<div id="disk-chart" wire:ignore></div>
</div>
</div>
</div>
</div>
@@ -3904,6 +3961,7 @@
</div>
</div>
</div>
</div>
<!-- Export Info Modal -->
<div x-data="{ showExportInfoModal: @entangle('showExportInfoModal') }">
@@ -4484,9 +4542,10 @@
window.formatBytes = function formatBytes(a, b) {
if (b === undefined) b = 2;
if (!+a) return "0 Bytes";
if (!+a || a < 0 || !isFinite(a)) return "0 Bytes";
var c = 0 > b ? 0 : b;
var d = Math.floor(Math.log(a) / Math.log(1024));
if (d < 0 || d > 8) return "0 Bytes";
return parseFloat((a/Math.pow(1024,d)).toFixed(c)) + " " + ["Bytes","KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"][d];
};
@@ -4893,7 +4952,7 @@
return component;
};
var maxMem = {{ $status->maxmem ?? 0 }};
var maxMem = {{ $status?->maxmem ?? 0 }};
var baseOptions = {
chart: {
@@ -5581,7 +5640,7 @@
});
// Also check periodically to catch cases where Livewire update event doesn't fire
setInterval(function() {
var _networkAnalysisCheckIntervalId = setInterval(function() {
if (window.networkAnalysisTimeoutId && checkNetworkAnalysisLoaded()) {
cancelNetworkAnalysisTimeout();
}
@@ -5658,13 +5717,32 @@
});
// Refresh stats periodically (every 2 minutes) using individual chart requests
setInterval(function() {
var _statsRefreshIntervalId = setInterval(function() {
Livewire.dispatch('proxmox:stats:cpu');
Livewire.dispatch('proxmox:stats:memory');
Livewire.dispatch('proxmox:stats:network');
Livewire.dispatch('proxmox:stats:disk');
}, 120000);
// Clean up intervals, listeners, and charts when component is destroyed
$cleanup(function() {
clearInterval(_networkAnalysisCheckIntervalId);
clearInterval(_statsRefreshIntervalId);
if (window.networkAnalysisTimeoutId) {
clearTimeout(window.networkAnalysisTimeoutId);
window.networkAnalysisTimeoutId = null;
}
eventCleanupFunctions.forEach(function(cleanup) {
if (typeof cleanup === 'function') cleanup();
});
eventCleanupFunctions = [];
// Destroy chart instances
[cpuChart, memoryChart, networkChart, diskChart].forEach(function(chart) {
if (chart) { try { chart.destroy(); } catch(e) {} }
});
cpuChart = null; memoryChart = null; networkChart = null; diskChart = null;
});
window.downloadCSV = window.downloadCSV || function(csvContent, filename) {
var blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
var link = document.createElement('a');
@@ -5675,6 +5753,7 @@
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
window.exportCpuChartToCSV = function() {