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 @@
-
-