diff --git a/src/api_pve.php b/src/api_pve.php index 302ba27..4720b8e 100644 --- a/src/api_pve.php +++ b/src/api_pve.php @@ -356,31 +356,27 @@ if ($action === 'get_node_status' || $action === 'get_vm_status') { } } -// === NEU: RRD Historie (24h / 7 Tage) für alle Node Typen === +// === RRD Historie (24h / 7 Tage) für alle Node Typen === if ($action === 'get_historical_rrd') { if (!checkVmPermission($pdo, $_GET['vmid'] ?? 0)) exit; $stmt = $pdo->prepare("SELECT * FROM nodes WHERE id = ?"); $stmt->execute([$_GET['node_id']]); $node = $stmt->fetch(PDO::FETCH_ASSOC); $timeframe = $_GET['timeframe'] === 'week' ? 'week' : 'day'; $type = $node['type'] ?? 'pve'; - if ($type === 'pbs') { - $endpoint = "/api2/json/nodes/localhost/rrddata?timeframe={$timeframe}&cf=AVERAGE"; - } elseif ($type === 'pmg') { - $internalName = $_GET['host'] ?? 'localhost'; + 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"; } else { - if ($_GET['target_mode'] === 'node') { - $endpoint = "/api2/json/nodes/{$_GET['host']}/rrddata?timeframe={$timeframe}&cf=AVERAGE"; - } else { - $endpoint = "/api2/json/nodes/{$_GET['host']}/{$_GET['target_mode']}/{$_GET['vmid']}/rrddata?timeframe={$timeframe}&cf=AVERAGE"; - } + $endpoint = "/api2/json/nodes/{$_GET['host']}/{$_GET['target_mode']}/{$_GET['vmid']}/rrddata?timeframe={$timeframe}&cf=AVERAGE"; } $res = getProxmoxData($node['ip_address'], $node['token_id'], $node['token_secret'], $endpoint, $type); echo json_encode(['success' => true, 'data' => $res['data'] ?? []]); exit; } -// === NEU: APT Update Manager === +// === APT Update Manager === if ($action === 'get_update_details') { if (!$isAdmin) exit; $stmt = $pdo->query("SELECT * FROM nodes WHERE type != 'pmg'"); $updateList = []; diff --git a/src/app.js b/src/app.js index ff86cb4..84765dc 100644 --- a/src/app.js +++ b/src/app.js @@ -301,14 +301,29 @@ if (window.APP.isLoggedIn && window.APP.nodeCount > 0) { window.checkCronAction = function() { const action = document.getElementById('cronAction').value; const vmBlock = document.getElementById('cronVmBlock'); if (action.includes('_vm')) vmBlock.classList.remove('hidden'); else vmBlock.classList.add('hidden'); } async function loadCronJobs() { const tbody = document.getElementById('cronTableBody'); tbody.innerHTML = 'Lade Jobs...'; try { const res = await (await fetch('api.php?action=get_cron_jobs')).json(); if (res.success) { tbody.innerHTML = ''; if(res.data.length === 0) { tbody.innerHTML = 'Keine geplanten Jobs.'; return; } res.data.forEach(job => { const lastRun = formatDate(job.last_run); const isActive = parseInt(job.is_active) === 1; const statusDot = isActive ? ' Aktiv' : ' Pausiert'; const targetStr = job.action_type.includes('_vm') ? `VM ${job.target_vmid}` : 'Gesamter Host'; let actionStr = job.action_type; if(actionStr === 'reboot_node') actionStr = 'Host Reboot'; if(actionStr === 'start_vm') actionStr = 'VM Start'; if(actionStr === 'stop_vm') actionStr = 'VM Stop'; if(actionStr === 'reboot_vm') actionStr = 'VM Reboot'; tbody.innerHTML += `
${statusDot}

${job.name}

${actionStr}

${job.node_name}

Ziel: ${targetStr}

${job.cron_schedule}${lastRun}`; }); } } catch(e) {} } + window.toggleCronDate = function() { const d = document.getElementById('cronDays').value; const i = document.getElementById('cronSpecificDate'); if (d === 'specific') { i.classList.remove('hidden'); i.required = true; } else { i.classList.add('hidden'); i.required = false; } } + const addCronForm = document.getElementById('addCronJobForm'); if(addCronForm) { addCronForm.addEventListener('submit', async function(e) { e.preventDefault(); const btn = this.querySelector('button[type="submit"]'); const oTxt = btn.innerText; btn.innerText = 'Speichere...'; - const timeVal = document.getElementById('cronTime').value; const daysVal = document.getElementById('cronDays').value; - const [hour, minute] = timeVal.split(':'); const generatedCronStr = `${parseInt(minute, 10)} ${parseInt(hour, 10)} * * ${daysVal}`; + + const timeVal = document.getElementById('cronTime').value; + const daysVal = document.getElementById('cronDays').value; + const [hour, minute] = timeVal.split(':'); + + let generatedCronStr = ''; + if (daysVal === 'specific') { + const dateVal = document.getElementById('cronSpecificDate').value; + if (!dateVal) { btn.innerText = oTxt; return alert('Bitte Datum wählen!'); } + const dParts = dateVal.split('-'); // YYYY-MM-DD + generatedCronStr = `${parseInt(minute, 10)} ${parseInt(hour, 10)} ${parseInt(dParts[2], 10)} ${parseInt(dParts[1], 10)} *`; + } else { + generatedCronStr = `${parseInt(minute, 10)} ${parseInt(hour, 10)} * * ${daysVal}`; + } + const fd = new FormData(); fd.append('name', document.getElementById('cronName').value); fd.append('node_id', document.getElementById('cronNodeId').value); fd.append('action_type', document.getElementById('cronAction').value); fd.append('target_vmid', document.getElementById('cronTargetVmid').value); fd.append('cron_schedule', generatedCronStr); - try { const res = await (await fetch('api.php?action=add_cron_job', {method: 'POST', body: fd})).json(); if(res.success) { addCronForm.reset(); document.getElementById('cronTime').value = '03:00'; checkCronAction(); loadCronJobs(); } else alert('Fehler.'); } catch(e) {} finally { btn.innerText = oTxt; } + try { const res = await (await fetch('api.php?action=add_cron_job', {method: 'POST', body: fd})).json(); if(res.success) { addCronForm.reset(); document.getElementById('cronTime').value = '03:00'; toggleCronDate(); checkCronAction(); loadCronJobs(); } else alert('Fehler.'); } catch(e) {} finally { btn.innerText = oTxt; } }); } diff --git a/src/modals.php b/src/modals.php index 6486637..e8f9b89 100644 --- a/src/modals.php +++ b/src/modals.php @@ -88,13 +88,15 @@
- - + +