diff --git a/extensions/Servers/Proxmox/routes.php b/extensions/Servers/Proxmox/routes.php index fb371f72f..ed026088c 100644 --- a/extensions/Servers/Proxmox/routes.php +++ b/extensions/Servers/Proxmox/routes.php @@ -17,6 +17,8 @@ use Paymenter\Extensions\Servers\Proxmox\Livewire\VNC; use Paymenter\Extensions\Servers\Proxmox\Livewire\XTerm; use Paymenter\Extensions\Servers\Proxmox\Livewire\Help; use Paymenter\Extensions\Servers\Proxmox\Livewire\Statistics; +use Paymenter\Extensions\Servers\Proxmox\Models\Server; +use Paymenter\Extensions\Servers\Proxmox\Proxmox; Route::group([ 'prefix' => 'servers', @@ -39,4 +41,27 @@ Route::group([ Route::get('{server}/vnc', VNC::class)->name('server.vnc'); Route::get('{server}/xterm', XTerm::class)->name('server.xterm'); Route::get('{server}/help', Help::class)->name('server.help'); + + // Popup console routes (minimal layout for popup windows) + Route::get('{server}/vnc-popup', function (Server $server) { + if (!$server->node->vnc_enabled) { + abort(403, 'VNC is not enabled on this server node.'); + } + $proxmox = new Proxmox; + return view('proxmox::vnc-popup', [ + 'server' => $server, + 'vnc' => $proxmox->vnc($server), + ]); + })->name('server.vnc-popup'); + + Route::get('{server}/xterm-popup', function (Server $server) { + if (!$server->node->vnc_enabled) { + abort(403, 'Console access is not enabled on this server node.'); + } + $proxmox = new Proxmox; + return view('proxmox::xterm-popup', [ + 'server' => $server, + 'xterm' => $proxmox->xterm($server), + ]); + })->name('server.xterm-popup'); }); diff --git a/extensions/Servers/Proxmox/views/components/layout.blade.php b/extensions/Servers/Proxmox/views/components/layout.blade.php index 06ad385f7..42506b312 100644 --- a/extensions/Servers/Proxmox/views/components/layout.blade.php +++ b/extensions/Servers/Proxmox/views/components/layout.blade.php @@ -1588,8 +1588,8 @@ 'label' => 'Console', 'dropdown' => true, 'items' => [ - ['label' => 'NoVNC', 'view' => 'vnc', 'route' => route('proxmox.server.vnc', ['server' => $server->id])], - ['label' => 'Xterm.js', 'view' => 'xterm', 'route' => route('proxmox.server.xterm', ['server' => $server->id])], + ['label' => 'NoVNC', 'view' => 'vnc', 'route' => route('proxmox.server.vnc-popup', ['server' => $server->id]), 'popup' => true], + ['label' => 'Xterm.js', 'view' => 'xterm', 'route' => route('proxmox.server.xterm-popup', ['server' => $server->id]), 'popup' => true], ] ]; } @@ -1974,15 +1974,26 @@ @foreach($tab['items'] as $itemIndex => $item) @php $itemAdvanced = $item['advanced'] ?? false; + $isPopup = $item['popup'] ?? false; @endphp
- - {{ $item['label'] }} - + @if($isPopup) + + {{ $item['label'] }} + + @else + + {{ $item['label'] }} + + @endif @if($itemIndex < count($tab['items']) - 1 && $itemAdvanced && !($tab['items'][$itemIndex + 1]['advanced'] ?? false)) @endif diff --git a/extensions/Servers/Proxmox/views/overview.blade.php b/extensions/Servers/Proxmox/views/overview.blade.php index b77374def..12c86147a 100644 --- a/extensions/Servers/Proxmox/views/overview.blade.php +++ b/extensions/Servers/Proxmox/views/overview.blade.php @@ -1408,6 +1408,63 @@ let charts = {}; let peakRates = {}; + // Handle chart toggle clicks via event delegation (works for dynamically created elements) + const handleChartToggle = (interfaceId) => { + const chartContainer = document.querySelector(`[data-chart-container="${interfaceId}"]`); + + if (chartContainer) { + chartContainer.classList.toggle('open'); + + // Create chart on first open + if (chartContainer.classList.contains('open') && !charts[interfaceId]) { + const attemptCreateChart = (retries = 5, delay = 200) => { + const interfaceData = networkData?.interfaces?.find(i => + i.id.replace(/[^a-z0-9]/gi, '-') === interfaceId + ); + + const canvas = document.getElementById(`chart-${interfaceId}`); + + if (canvas && interfaceData && interfaceData.history) { + const historyLength = Math.max( + interfaceData.history.download?.length || 0, + interfaceData.history.upload?.length || 0 + ); + + if (historyLength > 0) { + console.log('Creating chart for interface:', interfaceId); + requestAnimationFrame(() => { + setTimeout(() => { + if (!charts[interfaceId]) { + createChart(interfaceId, interfaceData); + } + }, 50); + }); + return; + } + } + + if (retries > 0) { + console.log(`Chart data not ready for ${interfaceId}, retrying... (${retries} attempts left)`); + setTimeout(() => attemptCreateChart(retries - 1, delay), delay); + } + }; + + attemptCreateChart(); + } + } + }; + + // Event delegation for chart toggle buttons - attach once to document + document.addEventListener('click', (e) => { + const toggleButton = e.target.closest('.chart-toggle'); + if (toggleButton) { + const interfaceId = toggleButton.dataset.interface; + if (interfaceId) { + handleChartToggle(interfaceId); + } + } + }); + const formatBytes = (bytes, decimals = 2) => { if (bytes === 0) return '0 B'; const k = 1024; @@ -1669,96 +1726,7 @@ .map((iface) => createInterfaceCard(iface)) .join(''); - // Set up chart toggles - use event delegation or ensure fresh listeners - document.querySelectorAll('.chart-toggle').forEach(button => { - button.addEventListener('click', (e) => { - const interfaceId = e.currentTarget.dataset.interface; - const container = document.querySelector(`[data-chart-container="${interfaceId}"]`); - - if (container) { - container.classList.toggle('open'); - - // Create chart on first open - if (container.classList.contains('open') && !charts[interfaceId]) { - const attemptCreateChart = (retries = 5, delay = 200) => { - const interfaceData = networkData?.interfaces?.find(i => - i.id.replace(/[^a-z0-9]/gi, '-') === interfaceId - ); - - const canvas = document.getElementById(`chart-${interfaceId}`); - - if (canvas && interfaceData && interfaceData.history) { - const historyLength = Math.max( - interfaceData.history.download?.length || 0, - interfaceData.history.upload?.length || 0 - ); - - if (historyLength > 0) { - console.log('Creating chart for interface:', interfaceId, 'with data:', { - id: interfaceData.id, - name: interfaceData.name, - historyLength: historyLength - }); - - // Use requestAnimationFrame to ensure DOM is ready - requestAnimationFrame(() => { - setTimeout(() => { - if (!charts[interfaceId]) { - createChart(interfaceId, interfaceData); - } - }, 50); - }); - return; - } - } - - // Retry if data not ready yet - if (retries > 0) { - console.log(`Chart data not ready for ${interfaceId}, retrying... (${retries} attempts left)`); - setTimeout(() => attemptCreateChart(retries - 1, delay), delay); - } else { - console.warn('Failed to create chart for', interfaceId, 'after multiple attempts. Available interfaces:', networkData?.interfaces?.map(i => i.id)); - - // Try one more time with latest data from next poll - const checkOnNextUpdate = () => { - const latestData = networkData?.interfaces?.find(i => - i.id.replace(/[^a-z0-9]/gi, '-') === interfaceId - ); - if (latestData && latestData.history && container.classList.contains('open')) { - const historyLength = Math.max( - latestData.history.download?.length || 0, - latestData.history.upload?.length || 0 - ); - if (historyLength > 0 && !charts[interfaceId]) { - console.log('Creating chart for', interfaceId, 'with data from next update'); - createChart(interfaceId, latestData); - } - } - }; - - // Check on next data update - const originalUpdate = updateInterfaceData; - const checkOnce = (interfaceData) => { - if (interfaceData.id.replace(/[^a-z0-9]/gi, '-') === interfaceId && container.classList.contains('open')) { - checkOnNextUpdate(); - } - }; - - // Set up a one-time check - setTimeout(() => { - if (container.classList.contains('open') && !charts[interfaceId]) { - checkOnNextUpdate(); - } - }, 500); - } - }; - - // Start attempting to create chart - attemptCreateChart(); - } - } - }); - }); + // Chart toggles are handled by event delegation at document level }; const createChart = (interfaceId, interfaceData) => { diff --git a/extensions/Servers/Proxmox/views/vnc-popup.blade.php b/extensions/Servers/Proxmox/views/vnc-popup.blade.php new file mode 100644 index 000000000..a8126bb6b --- /dev/null +++ b/extensions/Servers/Proxmox/views/vnc-popup.blade.php @@ -0,0 +1,31 @@ + + + + + + + NoVNC Console - {{ $server->name }} + + + + + + diff --git a/extensions/Servers/Proxmox/views/xterm-popup.blade.php b/extensions/Servers/Proxmox/views/xterm-popup.blade.php new file mode 100644 index 000000000..4f6f0a8d3 --- /dev/null +++ b/extensions/Servers/Proxmox/views/xterm-popup.blade.php @@ -0,0 +1,31 @@ + + + + + + + Xterm.js Console - {{ $server->name }} + + + + + +