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();
- +

Login

- +

Willkommen! 👋

-
+ Cluster Topologie + VMs verwalten Server & API Task Scheduler @@ -84,23 +85,42 @@ $nodeCount = $stmt->fetchColumn();
-
+
-
- +
+ +
+
+
+

Gesamtübersicht

Cluster & Standalone Nodes

+
+
+

Server (Nodes)

0/0

+ +

Total VMs/LXC

0

+

Online

0

+

Offline

0

+
+
+

Cluster CPU Cores

Lade...

Globaler RAM

Lade...

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

- -