diff --git a/src/app.js b/src/app.js
index 9326736..1ce8e84 100644
--- a/src/app.js
+++ b/src/app.js
@@ -37,7 +37,6 @@ if (window.APP.isLoggedIn && window.APP.nodeCount > 0) {
});
}
- // NEU: Dynamische Sidebar für PVE Nodes
async function loadSidebarNodes() {
const container = document.getElementById('sidebar-pve-nodes'); if(!container) return;
try {
@@ -64,31 +63,39 @@ if (window.APP.isLoggedIn && window.APP.nodeCount > 0) {
if(nav) nav.classList.remove('tab-active', 'tab-active-pbs', 'tab-active-pmg');
}
});
- // Reset Sidebar Highlights for nodes
document.querySelectorAll('[id^="nav-node-"]').forEach(el => el.classList.remove('text-white', 'font-bold'));
-
if(tab === 'pbs') fetchPbsStats(); if(tab === 'pmg') fetchPmgStats();
}
- // NEU: Node View (Spezifischer Host)
window.openNodeView = async function(nodeId, host) {
switchTab('node-view');
document.querySelectorAll('[id^="nav-node-"]').forEach(el => el.classList.remove('text-white', 'font-bold'));
- document.getElementById(`nav-node-${host}`).classList.add('text-white', 'font-bold');
+ const activeNav = document.getElementById(`nav-node-${host}`);
+ if(activeNav) activeNav.classList.add('text-white', 'font-bold');
+
document.getElementById('nodeViewTitle').innerText = 'Host: ' + host;
- document.getElementById('nodeViewHeaderBtn').onclick = () => openLiveGraph('node', 0, host, nodeId, host);
- document.getElementById('nodeViewBackupBtn').onclick = () => openNodeBackups(nodeId, host);
+
+ // REPARIERT: EventListener sauber binden, nicht überschreiben
+ const headerBtn = document.getElementById('nodeViewHeaderBtn');
+ const backupBtn = document.getElementById('nodeViewBackupBtn');
+
+ // Remove old listeners by cloning
+ const newHeaderBtn = headerBtn.cloneNode(true);
+ headerBtn.parentNode.replaceChild(newHeaderBtn, headerBtn);
+ newHeaderBtn.addEventListener('click', () => openLiveGraph('node', 0, host, nodeId, host));
+
+ const newBackupBtn = backupBtn.cloneNode(true);
+ backupBtn.parentNode.replaceChild(newBackupBtn, backupBtn);
+ newBackupBtn.addEventListener('click', () => openNodeBackups(nodeId, host));
- // Vorbelegung für "Neue VM" Modal
window.currentSelectedNodeId = nodeId; window.currentSelectedHost = host;
- const storContainer = document.getElementById('nodeStoragesContainer'); storContainer.innerHTML = 'Lade...';
- const vmsBody = document.getElementById('nodeVmsTableBody'); vmsBody.innerHTML = '
| Lade VMs... |
';
+ const storContainer = document.getElementById('nodeStoragesContainer'); storContainer.innerHTML = 'Lade Storages...
';
+ const vmsBody = document.getElementById('nodeVmsTableBody'); vmsBody.innerHTML = '| Lade VMs... |
';
try {
const res = await (await fetch(`api.php?action=get_node_dashboard&node_id=${nodeId}&host=${host}`)).json();
if(res.success) {
- // Storages rendern
storContainer.innerHTML = '';
res.storages.forEach(st => {
const pct = st.total > 0 ? ((st.used / st.total) * 100).toFixed(1) : 0;
@@ -98,15 +105,14 @@ if (window.APP.isLoggedIn && window.APP.nodeCount > 0) {
${st.content}
`;
});
- // VMs rendern
vmsBody.innerHTML = '';
if(res.vms.length === 0) vmsBody.innerHTML = '| Keine VMs auf diesem Host. |
';
res.vms.forEach(vm => {
const isRunning = vm.status === 'running'; const typeStr = vm.type === 'lxc' ? '📦 LXC' : '🖥️ QEMU';
const statusBadge = isRunning ? 'Online' : 'Offline';
- let actionButtons = isRunning ? `` : ``;
- actionButtons += ``;
- vmsBody.innerHTML += `| ${vm.vmid} | ${vm.name} | ${typeStr} | ${statusBadge} | ${actionButtons} |
`;
+ let actionButtons = isRunning ? `` : ``;
+ actionButtons += ``;
+ vmsBody.innerHTML += `| ${vm.vmid} | ${vm.name} | ${typeStr} | ${statusBadge} | ${actionButtons} |
`;
});
}
} catch(e) {}
@@ -120,7 +126,7 @@ if (window.APP.isLoggedIn && window.APP.nodeCount > 0) {
let prevGlobalTime = null;
async function fetchGlobalStats() {
- if(document.getElementById('tab-pve').classList.contains('hidden')) return;
+ if(document.getElementById('tab-pve') && document.getElementById('tab-pve').classList.contains('hidden')) return;
try {
const res = await (await fetch('api.php?action=get_stats')).json();
if(res.success && res.data) {
@@ -176,12 +182,12 @@ if (window.APP.isLoggedIn && window.APP.nodeCount > 0) {
window.closeNodeManager = function() { nodeModal.classList.add('hidden'); document.getElementById('addNodeFormContainer').classList.add('hidden'); }
window.toggleAddNodeForm = function() { document.getElementById('addNodeFormContainer').classList.toggle('hidden'); }
async function loadNodesIntoTable() { try { const res = await (await fetch('api.php?action=get_nodes')).json(); if(res.success) { const tbody = document.getElementById('nodeTableBody'); tbody.innerHTML = ''; if(res.data.length === 0) { tbody.innerHTML = '| Keine Server. |
'; return; } res.data.forEach(node => { let typeBadge = node.type === 'pbs' ? 'PBS Backup' : (node.type === 'pmg' ? 'PMG Mail' : 'PVE Node'); tbody.innerHTML += `| ${node.name} | ${typeBadge} | ${node.ip_address} | |
`; }); } } catch (err) {} }
- window.deleteNode = async function(id, name) { if(!confirm(`Server '${name}' löschen?`)) return; const fd = new FormData(); fd.append('id', id); await fetch('api.php?action=delete_node', { method: 'POST', body: fd }); loadNodesIntoTable(); }
+ window.deleteNode = async function(id, name) { if(!confirm(`Server '${name}' löschen?`)) return; const fd = new FormData(); fd.append('id', id); await fetch('api.php?action=delete_node', { method: 'POST', body: fd }); loadNodesIntoTable(); loadSidebarNodes(); }
const addNewNodeForm = document.getElementById('addNewNodeForm');
if(addNewNodeForm) { addNewNodeForm.addEventListener('submit', async function(e) { e.preventDefault(); const btn = this.querySelector('button[type="submit"]'); const oTxt = btn.innerText; btn.innerText = 'Verbinde...'; const fd = new FormData(); fd.append('name', document.getElementById('newNodeName').value); fd.append('ip', document.getElementById('newNodeIp').value); fd.append('user', document.getElementById('newNodeUser').value); fd.append('pass', document.getElementById('newNodePass').value); fd.append('type', document.getElementById('newNodeType').value); try { const res = await (await fetch('api.php?action=add_node', { method: 'POST', body: fd })).json(); if(res.success) { addNewNodeForm.reset(); toggleAddNodeForm(); loadNodesIntoTable(); loadSidebarNodes(); alert('Erfolgreich angebunden!'); } else alert(res.error); } catch(err) {} btn.innerText = oTxt; }); }
- // VM Erstellung (jetzt mit automatischer Node-Vorauswahl aus dem Sidebar-View)
+ // VM Erstellung
const createVmModal = document.getElementById('createVmModal');
window.openCreateVm = async function() {
createVmModal.classList.remove('hidden');
@@ -201,7 +207,7 @@ if (window.APP.isLoggedIn && window.APP.nodeCount > 0) {
const createVmForm = document.getElementById('createVmForm');
if(createVmForm) { createVmForm.addEventListener('submit', async function(e) { e.preventDefault(); const hostDataStr = document.getElementById('createVmHost').value; if(!hostDataStr) return; const hostData = JSON.parse(hostDataStr); const btn = this.querySelector('button[type="submit"]'); const oTxt = btn.innerText; btn.innerText = 'Richte VM ein...'; const fd = new FormData(); fd.append('node_id', hostData.id); fd.append('host', hostData.host); fd.append('name', document.getElementById('createVmName').value); fd.append('memory', document.getElementById('createVmRam').value); fd.append('cores', document.getElementById('createVmCores').value); try { const res = await (await fetch('api.php?action=create_vm', { method: 'POST', body: fd })).json(); if(res.success) { alert(`Erfolgreich! VM-ID: ${res.vmid}`); createVmForm.reset(); closeCreateVm(); if(window.currentSelectedHost === hostData.host) openNodeView(hostData.id, hostData.host); } else alert(res.error); } catch(e) {} finally { btn.innerText = oTxt; } }); }
- // VM EDIT MODAL (Erweitert um ISO & OnBoot)
+ // VM EDIT MODAL
const settingsModal = document.getElementById('vmSettingsModal');
window.openVmSettings = async function(vmid, host, type, nodeId, name) {
settingsModal.classList.remove('hidden'); document.getElementById('settingsModalTitle').innerText = 'Einstellungen: ' + name;
@@ -244,6 +250,8 @@ if (window.APP.isLoggedIn && window.APP.nodeCount > 0) {
const res = await (await fetch('api.php?action=update_vm_config', { method: 'POST', body: fd })).json();
if(res.success) { alert('Gespeichert!'); if(window.currentSelectedHost) openNodeView(fd.get('node_id'), fd.get('host')); } else alert(res.error);
}
+ window.saveNetwork = async function(disconnect) { let net = document.getElementById('setRawNet0').value; if (!net) return; if (disconnect) { if (!net.includes('link_down=1')) net += ',link_down=1'; } else { net = net.replace(',link_down=1', ''); } const fd = new FormData(); fd.append('vmid', document.getElementById('setVmid').value); fd.append('host', document.getElementById('setHost').value); fd.append('type', document.getElementById('setType').value); fd.append('node_id', document.getElementById('setNodeId').value); fd.append('net0', net); const res = await (await fetch('api.php?action=update_vm_config', { method: 'POST', body: fd })).json(); if(res.success) { alert('Netzwerkstatus geändert!'); openVmSettings(fd.get('vmid'), fd.get('host'), fd.get('type'), fd.get('node_id'), document.getElementById('settingsModalTitle').innerText.replace('Einstellungen: ', '')); } }
+ window.addNic = async function() { const bridge = document.getElementById('addNicBridge').value.trim(); if (!bridge) return; const fd = new FormData(); fd.append('vmid', document.getElementById('setVmid').value); fd.append('host', document.getElementById('setHost').value); fd.append('type', document.getElementById('setType').value); fd.append('node_id', document.getElementById('setNodeId').value); fd.append('bridge', bridge); try { const res = await (await fetch('api.php?action=add_vm_nic', { method: 'POST', body: fd })).json(); if(res.success) { alert(`Erfolgreich in Slot: ${res.slot}`); openVmSettings(fd.get('vmid'), fd.get('host'), fd.get('type'), fd.get('node_id'), document.getElementById('settingsModalTitle').innerText.replace('Einstellungen: ', '')); } else alert(res.error); } catch(e) {} }
// Node Backups Manager
window.openNodeBackups = async function(nodeId, host) {
@@ -261,7 +269,7 @@ if (window.APP.isLoggedIn && window.APP.nodeCount > 0) {
${j.schedule || 'Manuell'} |
${j.storage} |
${j.vmid || 'Alle VMs'} |
- Read-Only via API |
+ Read-Only via API |
`;
});
}
@@ -335,7 +343,9 @@ if (window.APP.isLoggedIn && window.APP.nodeCount > 0) {
window.sendVmCommand = async function(vmid, host, type, cmd, nodeId) { if(!confirm(`Maschine '${vmid}' wirklich ${cmd}?`)) return; const fd = new FormData(); fd.append('vmid', vmid); fd.append('host', host); fd.append('type', type); fd.append('cmd', cmd); fd.append('node_id', nodeId); try { const res = await (await fetch('api.php?action=vm_action', { method: 'POST', body: fd })).json(); if(res.success) { setTimeout(() => { if(window.currentSelectedHost === host) openNodeView(nodeId, host); }, 2000); } else alert(res.error); } catch (err) {} }
- // Bestehende Dummy-Funktionen für andere Module der Übersichtlichkeit halber:
- window.openPmgManager = function() {} // Siehe original code
- window.openPbsDatastore = function() {} // Siehe original code
+ // Platzhalter für Modals, die im Header aufgerufen werden
+ window.openUserManager = function() {}
+ window.openPmgManager = function() {}
+ window.openPbsDatastore = function() {}
+ window.openNodeTopology = function() {}
}
\ No newline at end of file
diff --git a/src/index.php b/src/index.php
index 9ed0935..648908c 100644
--- a/src/index.php
+++ b/src/index.php
@@ -47,24 +47,23 @@ $nodeCount = $stmt->fetchColumn();
-
+
-
+
-
+
Cluster Topologie
+
VMs verwalten
Server & API
Task Scheduler
@@ -84,23 +85,42 @@ $nodeCount = $stmt->fetchColumn();
-
+
-
-
+
+
+
+
+
+
Gesamtübersicht
Cluster & Standalone Nodes
+
+
+
+
Datacenter Storage
Lade...
-
- +Neue VM anlegen
-
+
+Neue VM anlegen
+
+
+
Live Cluster Auslastung
CPU / RAM
+
Live Netzwerk Traffic
MB/s Total pro Node
+
+
+
🔥 Top 5 Ressourcen-Fresser
Lädt Live-Daten von Proxmox API...
-
-
+
+
Host: ...
@@ -111,20 +131,17 @@ $nodeCount = $stmt->fetchColumn();
-
Laufende & Gestoppte Maschinen
-
+
| ID | Name | Typ | Status | Aktionen |
-
-
💾 Storages
@@ -132,16 +149,25 @@ $nodeCount = $stmt->fetchColumn();
-
-
+
+
-
-
Mail Gateway Status
-
+
+
+
+
Mail Gateway Status
+
+
+
+
+