From b84effa66b74f6621fc263c9946f7af74cd9b8a1 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 27 Aug 2026 09:27:35 +0200 Subject: [PATCH] Release v1.1: Hotfix RRD Graphen. --- src/api_pve.php | 14 ++++++-------- src/app.js | 2 -- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/api_pve.php b/src/api_pve.php index 4720b8e..e67051b 100644 --- a/src/api_pve.php +++ b/src/api_pve.php @@ -18,7 +18,6 @@ if ($action === 'get_stats') { $nData = getProxmoxData($node['ip_address'], $node['token_id'], $node['token_secret'], "/api2/json/nodes"); if (isset($nData['data'])) { foreach ($nData['data'] as $nInfo) { - // Duplikate (z.B. in Clustern) vermeiden if (!isset($seenNodes[$nInfo['node']])) { $seenNodes[$nInfo['node']] = true; @@ -48,7 +47,6 @@ if ($action === 'get_stats') { } } - // VMs von allen angebundenen Hosts ziehen und Duplikate filtern $vms = getProxmoxData($node['ip_address'], $node['token_id'], $node['token_secret'], "/api2/json/cluster/resources?type=vm"); if (isset($vms['data'])) { foreach($vms['data'] as $vm) { @@ -324,13 +322,14 @@ if ($action === 'restore_backup') { if ($action === 'get_node_status' || $action === 'get_vm_status') { $stmt = $pdo->prepare("SELECT * FROM nodes WHERE id = ?"); $stmt->execute([$_GET['node_id']]); $node = $stmt->fetch(PDO::FETCH_ASSOC); + $type = $node['type'] ?? 'pve'; if ($action === 'get_node_status') { - $nData = getProxmoxData($node['ip_address'], $node['token_id'], $node['token_secret'], "/api2/json/nodes"); - $internalName = $nData['data'][0]['node'] ?? 'pve'; + $nData = getProxmoxData($node['ip_address'], $node['token_id'], $node['token_secret'], "/api2/json/nodes", $type); + $internalName = $nData['data'][0]['node'] ?? ($type === 'pbs' ? 'localhost' : 'pve'); - $res = getProxmoxData($node['ip_address'], $node['token_id'], $node['token_secret'], "/api2/json/nodes/{$internalName}/status"); - $rrd = getProxmoxData($node['ip_address'], $node['token_id'], $node['token_secret'], "/api2/json/nodes/{$internalName}/rrddata?timeframe=hour&cf=AVERAGE"); + $res = getProxmoxData($node['ip_address'], $node['token_id'], $node['token_secret'], "/api2/json/nodes/{$internalName}/status", $type); + $rrd = getProxmoxData($node['ip_address'], $node['token_id'], $node['token_secret'], "/api2/json/nodes/{$internalName}/rrddata?timeframe=hour&cf=AVERAGE", $type); $netin = 0; $netout = 0; if (isset($rrd['data']) && is_array($rrd['data'])) { @@ -351,7 +350,7 @@ if ($action === 'get_node_status' || $action === 'get_vm_status') { echo json_encode(['success' => true, 'data' => $data]); exit; } else { $endpoint = "/api2/json/nodes/{$_GET['host']}/{$_GET['type']}/{$_GET['vmid']}/status/current"; - $res = getProxmoxData($node['ip_address'], $node['token_id'], $node['token_secret'], $endpoint); + $res = getProxmoxData($node['ip_address'], $node['token_id'], $node['token_secret'], $endpoint, $type); echo json_encode(['success' => true, 'data' => $res['data'] ?? []]); exit; } } @@ -364,7 +363,6 @@ if ($action === 'get_historical_rrd') { $type = $node['type'] ?? 'pve'; if ($_GET['target_mode'] === 'node') { - // Internen Node-Namen von Proxmox auflösen, da der Datenbank-Name sonst zu einem 404 Fehler führt $nData = getProxmoxData($node['ip_address'], $node['token_id'], $node['token_secret'], "/api2/json/nodes", $type); $internalName = $nData['data'][0]['node'] ?? 'localhost'; $endpoint = "/api2/json/nodes/{$internalName}/rrddata?timeframe={$timeframe}&cf=AVERAGE"; diff --git a/src/app.js b/src/app.js index 84765dc..c9e6de7 100644 --- a/src/app.js +++ b/src/app.js @@ -43,7 +43,6 @@ if (window.APP.isLoggedIn && window.APP.nodeCount > 0) { if(tab === 'pbs') fetchPbsStats(); if(tab === 'pmg') fetchPmgStats(); } - // HAUPT-GRAPHEN WIEDER HERGESTELLT const ctx = document.getElementById('liveChart')?.getContext('2d'); let liveChart; if(ctx) { liveChart = new Chart(ctx, { type: 'line', data: { labels: [], datasets: [{ label: 'CPU (%)', borderColor: '#3b82f6', backgroundColor: 'rgba(59, 130, 246, 0.1)', borderWidth: 2, tension: 0.4, fill: true, data: [] }, { label: 'RAM (%)', borderColor: '#E57000', backgroundColor: 'rgba(229, 112, 0, 0.1)', borderWidth: 2, tension: 0.4, fill: true, data: [] }] }, options: { responsive: true, maintainAspectRatio: false, animation: { duration: 500 }, scales: { x: { ticks: { color: '#9ca3af' }, grid: { color: '#33334d' } }, y: { min: 0, max: 100, ticks: { color: '#9ca3af', callback: v => v + '%' }, grid: { color: '#33334d' } } }, plugins: { legend: { labels: { color: '#e2e8f0', usePointStyle: true } } } } }); } @@ -103,7 +102,6 @@ if (window.APP.isLoggedIn && window.APP.nodeCount > 0) { async function fetchTopVms() { if(!document.getElementById('top-vms-container')) return; try { const res = await (await fetch('api.php?action=get_top_vms')).json(); if(res.success && res.data) { const container = document.getElementById('top-vms-container'); container.innerHTML = ''; if(res.data.length === 0) { container.innerHTML = '

Keine aktiven VMs.

'; return; } res.data.forEach((vm, i) => { const cpuPercent = ((vm.cpu || 0) * 100).toFixed(1); const ramUsed = formatBytes(vm.mem || 0); const icon = vm.type === 'lxc' ? '📦' : '🖥️'; const numberColor = i === 0 ? 'text-red-500' : (i === 1 ? 'text-orange-400' : (i === 2 ? 'text-yellow-400' : 'text-gray-400')); container.innerHTML += `
#${i + 1}

${icon} ${vm.name}

Host: ${vm.host}

${cpuPercent}% CPU

${ramUsed} RAM

`; }); } } catch (err) {} } async function fetchRecentJobs() { if(!document.getElementById('recent-jobs-container')) return; try { const res = await (await fetch('api.php?action=get_recent_jobs')).json(); if(res.success && res.data) { const container = document.getElementById('recent-jobs-container'); container.innerHTML = ''; if(res.data.length === 0) { container.innerHTML = '

Keine aktuellen Jobs.

'; return; } res.data.forEach(job => { const jobTypeStr = job.type || job.worker_type || 'unknown'; let statusColor = 'text-gray-400', statusIcon = '⏳', statusText = job.status || 'running...'; if(statusText.toLowerCase() === 'ok') { statusColor = 'text-green-500'; statusIcon = '✅'; } else if(statusText !== 'running...') { statusColor = 'text-red-500'; statusIcon = '❌'; } else { statusColor = 'text-blue-400'; statusIcon = '🔄'; } const date = new Date(job.starttime * 1000); const timeStr = date.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }), dateStr = date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' }); const isBackup = jobTypeStr.includes('sync') || jobTypeStr.includes('prune') || jobTypeStr.includes('garbage_collection') || jobTypeStr.includes('vzdump') || jobTypeStr.includes('verify'); const jobTypeColor = isBackup ? 'text-purple-400' : 'text-white'; container.innerHTML += `
${statusIcon}

${jobTypeStr}

Host: ${job.node_name}

${statusText}

${dateStr} - ${timeStr}

`; }); } } catch (err) {} } - // APT UPDATES window.fetchUpdates = async function() { if(!document.getElementById('stat-updates-text')) return; try { const res = await (await fetch('api.php?action=get_updates')).json(); if(res.success) { const el = document.getElementById('stat-updates-text'), subEl = document.getElementById('stat-updates-sub'); if(res.total === 0) { el.innerText = '0 Updates'; el.className = 'text-2xl font-bold text-green-500 mt-1'; subEl.innerText = 'Alle Systeme sind aktuell.'; } else { el.innerText = res.total + ' Updates'; el.className = 'text-2xl font-bold text-red-500 mt-1'; subEl.innerText = 'Auf: ' + res.details; } } } catch (err) {} } window.openUpdateManager = async function() { document.getElementById('updateManagerModal').classList.remove('hidden'); const content = document.getElementById('updateManagerContent'); content.innerHTML = '
Prüfe Updates...
'; try { const res = await (await fetch('api.php?action=get_update_details')).json(); if (res.success) { content.innerHTML = ''; if(res.data.length === 0) { content.innerHTML = '
🎉 Alle Systeme sind auf dem neuesten Stand!
'; return; } res.data.forEach(node => { let pkgsHtml = ''; node.packages.forEach(p => { pkgsHtml += `
${p.Title}${p.OldVersion} ➔ ${p.Version}
`; }); content.innerHTML += `

${node.display_name} ${node.packages.length} Updates

${pkgsHtml}
`; }); } } catch(e) {} } window.closeUpdateManager = function() { document.getElementById('updateManagerModal').classList.add('hidden'); fetchUpdates(); }