Release v1.1: Hotfix RRD Graphen.
This commit is contained in:
+7
-11
@@ -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 ($action === 'get_historical_rrd') {
|
||||||
if (!checkVmPermission($pdo, $_GET['vmid'] ?? 0)) exit;
|
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);
|
$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';
|
$timeframe = $_GET['timeframe'] === 'week' ? 'week' : 'day';
|
||||||
$type = $node['type'] ?? 'pve';
|
$type = $node['type'] ?? 'pve';
|
||||||
|
|
||||||
if ($type === 'pbs') {
|
if ($_GET['target_mode'] === 'node') {
|
||||||
$endpoint = "/api2/json/nodes/localhost/rrddata?timeframe={$timeframe}&cf=AVERAGE";
|
// Internen Node-Namen von Proxmox auflösen, da der Datenbank-Name sonst zu einem 404 Fehler führt
|
||||||
} elseif ($type === 'pmg') {
|
$nData = getProxmoxData($node['ip_address'], $node['token_id'], $node['token_secret'], "/api2/json/nodes", $type);
|
||||||
$internalName = $_GET['host'] ?? 'localhost';
|
$internalName = $nData['data'][0]['node'] ?? 'localhost';
|
||||||
$endpoint = "/api2/json/nodes/{$internalName}/rrddata?timeframe={$timeframe}&cf=AVERAGE";
|
$endpoint = "/api2/json/nodes/{$internalName}/rrddata?timeframe={$timeframe}&cf=AVERAGE";
|
||||||
} else {
|
} else {
|
||||||
if ($_GET['target_mode'] === 'node') {
|
$endpoint = "/api2/json/nodes/{$_GET['host']}/{$_GET['target_mode']}/{$_GET['vmid']}/rrddata?timeframe={$timeframe}&cf=AVERAGE";
|
||||||
$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";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$res = getProxmoxData($node['ip_address'], $node['token_id'], $node['token_secret'], $endpoint, $type);
|
$res = getProxmoxData($node['ip_address'], $node['token_id'], $node['token_secret'], $endpoint, $type);
|
||||||
echo json_encode(['success' => true, 'data' => $res['data'] ?? []]); exit;
|
echo json_encode(['success' => true, 'data' => $res['data'] ?? []]); exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// === NEU: APT Update Manager ===
|
// === APT Update Manager ===
|
||||||
if ($action === 'get_update_details') {
|
if ($action === 'get_update_details') {
|
||||||
if (!$isAdmin) exit;
|
if (!$isAdmin) exit;
|
||||||
$stmt = $pdo->query("SELECT * FROM nodes WHERE type != 'pmg'"); $updateList = [];
|
$stmt = $pdo->query("SELECT * FROM nodes WHERE type != 'pmg'"); $updateList = [];
|
||||||
|
|||||||
+18
-3
@@ -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'); }
|
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 = '<tr><td colspan="6" class="text-center text-gray-500 py-4 animate-pulse">Lade Jobs...</td></tr>'; 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 = '<tr><td colspan="6" class="text-center text-gray-500 py-4">Keine geplanten Jobs.</td></tr>'; return; } res.data.forEach(job => { const lastRun = formatDate(job.last_run); const isActive = parseInt(job.is_active) === 1; const statusDot = isActive ? '<span class="w-2 h-2 rounded-full bg-green-500 shadow-[0_0_5px_#22c55e]"></span> Aktiv' : '<span class="w-2 h-2 rounded-full bg-gray-600"></span> 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 += `<tr class="hover:bg-darkcard/50 transition-colors border-b border-darkborder/50"><td class="px-4 py-3 whitespace-nowrap cursor-pointer" onclick="toggleCronJob(${job.id}, ${isActive ? 0 : 1})"><div class="flex items-center gap-2 text-xs font-bold text-gray-300 hover:text-white">${statusDot}</div></td><td class="px-4 py-3"><p class="text-white font-bold">${job.name}</p><p class="text-xs text-proxmox font-bold">${actionStr}</p></td><td class="px-4 py-3"><p class="text-gray-300">${job.node_name}</p><p class="text-xs text-gray-500">Ziel: ${targetStr}</p></td><td class="px-4 py-3 font-mono text-blue-400 font-bold tracking-widest">${job.cron_schedule}</td><td class="px-4 py-3 text-gray-400 whitespace-nowrap">${lastRun}</td><td class="px-4 py-3 text-right"><button onclick="deleteCronJob(${job.id})" class="text-red-500 hover:text-white px-2 py-1 bg-red-500/10 hover:bg-red-500 rounded text-xs font-bold transition-colors">Löschen</button></td></tr>`; }); } } catch(e) {} }
|
async function loadCronJobs() { const tbody = document.getElementById('cronTableBody'); tbody.innerHTML = '<tr><td colspan="6" class="text-center text-gray-500 py-4 animate-pulse">Lade Jobs...</td></tr>'; 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 = '<tr><td colspan="6" class="text-center text-gray-500 py-4">Keine geplanten Jobs.</td></tr>'; return; } res.data.forEach(job => { const lastRun = formatDate(job.last_run); const isActive = parseInt(job.is_active) === 1; const statusDot = isActive ? '<span class="w-2 h-2 rounded-full bg-green-500 shadow-[0_0_5px_#22c55e]"></span> Aktiv' : '<span class="w-2 h-2 rounded-full bg-gray-600"></span> 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 += `<tr class="hover:bg-darkcard/50 transition-colors border-b border-darkborder/50"><td class="px-4 py-3 whitespace-nowrap cursor-pointer" onclick="toggleCronJob(${job.id}, ${isActive ? 0 : 1})"><div class="flex items-center gap-2 text-xs font-bold text-gray-300 hover:text-white">${statusDot}</div></td><td class="px-4 py-3"><p class="text-white font-bold">${job.name}</p><p class="text-xs text-proxmox font-bold">${actionStr}</p></td><td class="px-4 py-3"><p class="text-gray-300">${job.node_name}</p><p class="text-xs text-gray-500">Ziel: ${targetStr}</p></td><td class="px-4 py-3 font-mono text-blue-400 font-bold tracking-widest">${job.cron_schedule}</td><td class="px-4 py-3 text-gray-400 whitespace-nowrap">${lastRun}</td><td class="px-4 py-3 text-right"><button onclick="deleteCronJob(${job.id})" class="text-red-500 hover:text-white px-2 py-1 bg-red-500/10 hover:bg-red-500 rounded text-xs font-bold transition-colors">Löschen</button></td></tr>`; }); } } 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');
|
const addCronForm = document.getElementById('addCronJobForm');
|
||||||
if(addCronForm) {
|
if(addCronForm) {
|
||||||
addCronForm.addEventListener('submit', async function(e) {
|
addCronForm.addEventListener('submit', async function(e) {
|
||||||
e.preventDefault(); const btn = this.querySelector('button[type="submit"]'); const oTxt = btn.innerText; btn.innerText = 'Speichere...';
|
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);
|
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; }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -88,13 +88,15 @@
|
|||||||
<input type="time" id="cronTime" value="03:00" class="w-full bg-darkbg border border-darkborder rounded p-2 text-white text-sm" required>
|
<input type="time" id="cronTime" value="03:00" class="w-full bg-darkbg border border-darkborder rounded p-2 text-white text-sm" required>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-xs font-medium text-gray-500 mb-1">Tage</label>
|
<label class="block text-xs font-medium text-gray-500 mb-1">Tage / Datum</label>
|
||||||
<select id="cronDays" class="w-full bg-darkbg border border-darkborder rounded p-2 text-white text-sm" required>
|
<select id="cronDays" onchange="toggleCronDate()" class="w-full bg-darkbg border border-darkborder rounded p-2 text-white text-sm" required>
|
||||||
<option value="*">Jeden Tag</option>
|
<option value="*">Jeden Tag</option>
|
||||||
<option value="1-5">Montag - Freitag</option>
|
<option value="1-5">Montag - Freitag</option>
|
||||||
<option value="6,0">Wochenende</option>
|
<option value="6,0">Wochenende</option>
|
||||||
<option value="1">Jeden Montag</option>
|
<option value="1">Jeden Montag</option>
|
||||||
|
<option value="specific">📅 Einmalig (Bestimmtes Datum)</option>
|
||||||
</select>
|
</select>
|
||||||
|
<input type="date" id="cronSpecificDate" class="w-full bg-darkbg border border-darkborder rounded p-2 text-white text-sm mt-2 hidden">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="bg-proxmox hover:bg-orange-600 text-white font-bold py-2 px-6 rounded transition-colors text-sm w-full md:w-auto">Job Speichern</button>
|
<button type="submit" class="bg-proxmox hover:bg-orange-600 text-white font-bold py-2 px-6 rounded transition-colors text-sm w-full md:w-auto">Job Speichern</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user