window.folders = []; window.connections = []; window.protocols = []; window.templates = []; window.users = []; window.appSettings = {}; window.securityDashboardUrl = "https://threatmap.fortiguard.com/"; window.currentUser = { role: 'viewer', username: '' }; window.categories = []; window.scripts = []; window.auditLogs = []; window.networkInstance = null; window.mapLabelMode = window.mapLabelMode || 'name'; window.isMapPage = document.getElementById('mynetwork') !== null; // --- UTILS & UI --- window.initTheme = function() { const t = localStorage.getItem('rdm_theme') || 'light'; document.documentElement.setAttribute('data-theme', t); if(document.getElementById('theme-icon')) { document.getElementById('theme-icon').className = t === 'dark' ? 'bi bi-sun' : 'bi bi-moon'; } return t; }; window.toggleTheme = function() { const t = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark'; document.documentElement.setAttribute('data-theme', t); localStorage.setItem('rdm_theme', t); if(document.getElementById('theme-icon')) { document.getElementById('theme-icon').className = t === 'dark' ? 'bi bi-sun' : 'bi bi-moon'; } if (window.isMapPage) window.drawMap(); }; window.showToast = function(msg) { const toast = document.getElementById("toast"); if(!toast) return; toast.innerText = msg; toast.className = "show"; setTimeout(() => { toast.className = toast.className.replace("show", ""); }, 3000); }; window.copyToClipboard = function(text) { if(!text) return; navigator.clipboard.writeText(text).then(() => window.showToast("In die Zwischenablage kopiert!")); }; window.togglePwd = function(id, realPwd) { const el = document.getElementById(id); const icon = document.getElementById('icon-' + id); if(el.innerText === '••••••••') { el.innerText = realPwd; icon.classList.replace('bi-eye', 'bi-eye-slash'); } else { el.innerText = '••••••••'; icon.classList.replace('bi-eye-slash', 'bi-eye'); } }; window.switchTab = function(tabId) { document.querySelectorAll('.tab-view').forEach(el => el.style.display = 'none'); document.querySelectorAll('.dev-tab').forEach(el => el.classList.remove('active')); document.getElementById('view-' + tabId).style.display = 'block'; document.getElementById('tab-btn-' + tabId).classList.add('active'); }; window.getProtoInfo = function(type) { let p = window.protocols.find(x => x.proto_key === type); return p ? p : {icon: 'bi-link', name: type}; }; // NEU: Erweiterter Generator mit Filter window.generatePassword = function() { let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+~|}{[]:;?><,./-="; const exclude = window.appSettings.pwd_exclude_chars || ""; if(exclude) { for(let i=0; i `${h.changed_at}••••••••`).join(''); const modalHtml = ``; const oldModal = document.getElementById('historyModal'); if(oldModal) oldModal.remove(); document.body.insertAdjacentHTML('beforeend', modalHtml); new bootstrap.Modal(document.getElementById('historyModal')).show(); }; // --- AUTHENTICATION --- window.checkSession = async function() { try { const res = await (await fetch('api.php?action=session')).json(); if(res.status === 'active') { window.currentUser.role = res.role; window.currentUser.username = res.username; if(document.getElementById('user-badge')) document.getElementById('user-badge').innerText = res.username; document.getElementById('login-overlay').style.display = 'none'; window.loadData(); } } catch(e) { console.error("Session Check Error:", e); } }; window.doLogin = async function() { const u = document.getElementById('l_user').value; const p = document.getElementById('l_pass').value; try { const res = await fetch('api.php?action=login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({username: u, password: p}) }); if(res.ok) { const data = await res.json(); if(data.status === '2fa_required') { document.getElementById('step-login').classList.add('d-none'); document.getElementById('step-2fa').classList.remove('d-none'); } else { window.currentUser.role = data.role; window.currentUser.username = data.username; if(document.getElementById('user-badge')) document.getElementById('user-badge').innerText = data.username; document.getElementById('login-overlay').style.display = 'none'; window.loadData(); } } else { document.getElementById('login-error').innerText = "Zugangsdaten falsch."; document.getElementById('login-error').classList.remove('d-none'); } } catch (e) { document.getElementById('login-error').innerText = "Verbindungsfehler zur API."; document.getElementById('login-error').classList.remove('d-none'); } }; window.verify2FA = async function() { const code = document.getElementById('l_2fa').value; const res = await fetch('api.php?action=verify_2fa', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({code: code}) }); if(res.ok) { const data = await res.json(); window.currentUser.role = data.role; window.currentUser.username = data.username; if(document.getElementById('user-badge')) document.getElementById('user-badge').innerText = data.username; document.getElementById('login-overlay').style.display = 'none'; window.loadData(); } else { document.getElementById('login-error').innerText = "Code falsch."; document.getElementById('login-error').classList.remove('d-none'); } }; window.doLogout = async function() { await fetch('api.php?action=logout'); location.reload(); }; // --- DATA LOADING & ROUTING --- window.loadData = async function() { try { window.folders = await (await fetch('api.php?action=folders')).json(); window.connections = await (await fetch('api.php?action=connections')).json(); window.protocols = await (await fetch('api.php?action=protocols')).json(); window.appSettings = await (await fetch('api.php?action=settings')).json(); window.categories = await (await fetch('api.php?action=categories')).json(); window.scripts = await (await fetch('api.php?action=scripts')).json(); if(window.currentUser.role === 'admin') { window.users = await (await fetch('api.php?action=users')).json(); window.auditLogs = await (await fetch('api.php?action=audit')).json(); } window.securityDashboardUrl = window.appSettings.dashboard_url || "https://threatmap.fortiguard.com/"; if(window.isMapPage) { window.drawMap(); } else { window.renderSidebar(); window.showHomeDashboard(); } } catch(e) { console.error("Load Data Error", e); } }; window.showHomeDashboard = function() { if(window.isMapPage) return; document.querySelectorAll('.connection-item').forEach(el => el.classList.remove('active')); let totalAssets = window.connections ? window.connections.length : 0; let totalScripts = window.scripts ? window.scripts.length : 0; let foldersCount = window.folders ? window.folders.length : 0; let assetCounts = {}; if (window.connections) { window.connections.forEach(c => { if(c.asset_type) assetCounts[c.asset_type] = (assetCounts[c.asset_type] || 0) + 1; }); } let breakdownHtml = Object.entries(assetCounts).map(([k,v]) => ` ${k}: ${v}`).join(''); let kumaItems = window.connections ? window.connections.filter(c => c.kuma_url) : []; let kumaHtml = ''; if(kumaItems.length > 0) { kumaHtml = `

Wall of Health

`; kumaItems.forEach(c => { let iframeOrImg = c.kuma_url.includes('/badge/') ? `` : ``; kumaHtml += `
${c.name}
${iframeOrImg}
`; }); kumaHtml += `
`; } document.getElementById('main-area').innerHTML = `

System Dashboard

${totalAssets}
Verbindungen / Assets
${totalScripts}
Skripte / Snippets
${foldersCount}
Ordnerstrukturen
${breakdownHtml ? `
Hardware-Inventar
${breakdownHtml}
` : ''}${kumaHtml}
`; }; window.renderSidebar = function() { if(window.isMapPage) return; const sidebar = document.getElementById('sidebar-content'); sidebar.innerHTML = ''; if(window.currentUser.role === 'admin') { sidebar.innerHTML += `
`; } const tree = {}; window.folders.forEach(f => { const parts = f.split('/'); let current = tree; let path = ""; parts.forEach(p => { path = path ? path + "/" + p : p; if(!current[p]) current[p] = { fullPath: path, children: {} }; current = current[p].children; }); }); for(let rootName in tree) { window.renderFolderNode(rootName, tree[rootName], sidebar, 0); } }; window.renderFolderNode = function(name, node, container, depth) { const wrapper = document.createElement('div'); const folderDiv = document.createElement('div'); folderDiv.className = `folder-header px-2 search-item depth-${depth > 2 ? 2 : depth}`; folderDiv.innerHTML = ` ${name}`; wrapper.appendChild(folderDiv); const childrenContainer = document.createElement('div'); childrenContainer.className = "folder-children"; folderDiv.querySelector('.toggle-icon').onclick = (e) => { e.stopPropagation(); childrenContainer.classList.toggle('d-none'); e.target.classList.toggle('bi-chevron-down'); e.target.classList.toggle('bi-chevron-right'); }; for(let childName in node.children) { window.renderFolderNode(childName, node.children[childName], childrenContainer, depth + 1); } window.connections.filter(c => c.folder_name === node.fullPath && !c.parent_id).forEach(item => { window.renderConnectionItem(item, childrenContainer, depth + 1); }); window.scripts.filter(s => s.folder_name === node.fullPath).forEach(script => { window.renderScriptItem(script, childrenContainer, depth + 1); }); wrapper.appendChild(childrenContainer); container.appendChild(wrapper); }; window.renderConnectionItem = function(item, container, depth) { const itemDiv = document.createElement('div'); itemDiv.setAttribute('data-search', `${item.name} ${item.host} ${item.tags} ${item.asset_type} ${item.hardware}`.toLowerCase()); itemDiv.className = `connection-item depth-${depth > 2 ? 2 : depth} search-item`; itemDiv.innerHTML = ` ${item.name}`; itemDiv.onclick = () => { document.querySelectorAll('.connection-item').forEach(el => el.classList.remove('active')); itemDiv.classList.add('active'); window.showConnectionDetails(item); }; container.appendChild(itemDiv); window.connections.filter(c => c.parent_id == item.id).forEach(child => window.renderConnectionItem(child, container, depth + 1)); }; window.renderScriptItem = function(script, container, depth) { const itemDiv = document.createElement('div'); itemDiv.setAttribute('data-search', `${script.name} ${script.language} script`.toLowerCase()); itemDiv.className = `connection-item depth-${depth > 2 ? 2 : depth} search-item`; let icon = 'bi-file-earmark-text'; if(script.language === 'bash') icon = 'bi-terminal'; if(script.language === 'powershell') icon = 'bi-windows'; if(script.language === 'python') icon = 'bi-filetype-py'; if(script.language === 'sql') icon = 'bi-filetype-sql'; itemDiv.innerHTML = ` ${script.name}`; itemDiv.onclick = () => { document.querySelectorAll('.connection-item').forEach(el => el.classList.remove('active')); itemDiv.classList.add('active'); window.showScriptDetails(script); }; container.appendChild(itemDiv); }; window.filterSidebar = function() { const term = document.getElementById('searchInput').value.toLowerCase(); document.querySelectorAll('.search-item').forEach(item => { if(item.classList.contains('folder-header')) { item.style.display = item.innerText.toLowerCase().includes(term) ? '' : 'none'; } else { item.style.display = item.getAttribute('data-search').includes(term) ? '' : 'none'; } }); }; window.openNewFolderModal = function() { if(window.currentUser.role !== 'admin') return; let folderOptions = ''; window.folders.forEach(f => folderOptions += ``); const modalHtml = ``; const oldModal = document.getElementById('folderModal'); if(oldModal) oldModal.remove(); document.body.insertAdjacentHTML('beforeend', modalHtml); new bootstrap.Modal(document.getElementById('folderModal')).show(); }; window.saveNewFolder = async function() { const parent = document.getElementById('new_folder_parent').value; const name = document.getElementById('new_folder_name').value.trim(); if(!name) return; const fullName = parent ? `${parent}/${name}` : name; await fetch('api.php?action=folders', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({name: fullName}) }); bootstrap.Modal.getInstance(document.getElementById('folderModal')).hide(); await window.loadData(); }; window.showScriptForm = function(editItem = null) { if(window.currentUser.role !== 'admin' || window.isMapPage) return; const isEdit = editItem !== null; let folderOptions = ''; window.folders.forEach(f => folderOptions += ``); const langs = ['bash', 'powershell', 'python', 'sql', 'text']; let langOptions = langs.map(l => ``).join(''); document.getElementById('main-area').innerHTML = `

${isEdit ? 'Skript bearbeiten' : 'Neues Skript'}

${isEdit ? `` : ''}
`; }; window.saveScript = async function() { if(window.currentUser.role !== 'admin') return; const data = { id: document.getElementById('f_script_id').value || null, folder_name: document.getElementById('f_script_folder').value || null, name: document.getElementById('f_script_name').value, language: document.getElementById('f_script_lang').value, script_body: document.getElementById('f_script_body').value }; if(!data.name) return alert("Bitte einen Namen vergeben!"); await fetch('api.php?action=scripts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); await window.loadData(); window.showHomeDashboard(); }; window.showScriptDetails = function(item) { if(window.isMapPage) return; let adminActions = ''; if (window.currentUser.role === 'admin') { adminActions = `
Bearbeiten
Editieren
Löschen
`; } const safeContent = String(item.script_body).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); document.getElementById('main-area').innerHTML = `
Aktionen
Code Kopieren
${adminActions}

${item.name}

Snippet: ${item.language.toUpperCase()}
${safeContent}
`; }; window.deleteScript = async function(id) { if(window.currentUser.role !== 'admin') return; if(!confirm("Skript wirklich löschen?")) return; await fetch('api.php?action=delete_script', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({id: id}) }); await window.loadData(); window.showHomeDashboard(); }; window.showConnectionForm = function(editItem = null) { if(window.currentUser.role !== 'admin' || window.isMapPage) return; if(window.folders.length === 0 && !editItem) { alert("Bitte erstelle zuerst einen Ordner!"); return; } const isEdit = editItem !== null; let protoOptions = window.protocols.map(p => ``).join(''); let folderOptions = window.folders.map(f => ``).join(''); let connOptions = window.connections.filter(c => !isEdit || c.id !== editItem.id).map(c => ``).join(''); let uplinkOptions = ``; uplinkOptions += window.connections.filter(c => !isEdit || c.id !== editItem.id).map(c => ``).join(''); let catOptions = ``; window.categories.forEach(cat => { catOptions += ``; }); document.getElementById('main-area').innerHTML = `

${isEdit ? 'Eigenschaften bearbeiten' : 'Neuer Eintrag'}

Verbindung & System
Topologie & Port-Mapping
Asset Details
${isEdit ? `` : ''}
`; }; window.showConnectionDetails = function(item) { if(window.isMapPage) return; const protoInfo = window.getProtoInfo(item.type); const children = window.connections.filter(c => c.parent_id == item.id); let tagsHtml = ''; if (item.tags) tagsHtml = item.tags.split(/[\s,]+/).filter(t=>t).map(t => `${t.startsWith('#')?t:'#'+t}`).join(''); let uplinkName = '-'; if(item.uplink_id) { const up = window.connections.find(c => c.id == item.uplink_id); let portInfo = ''; if(item.local_port && item.uplink_port) portInfo = `(${item.local_port} ➔ ${item.uplink_port})`; else if(item.local_port) portInfo = `(Lokaler Port: ${item.local_port})`; else if(item.uplink_port) portInfo = `(Zielport: ${item.uplink_port})`; if(up) uplinkName = `${up.name} ${portInfo}`; } let childRows = ''; if (children.length > 0) { childRows = `
Unterverbindungen (${children.length})
`; children.forEach(c => { childRows += `
${c.name}
${c.username || '-'}
`; }); childRows += `
`; } let adminActions = ''; if (window.currentUser.role === 'admin') { const isLocked = item.map_locked == 1; const lockIcon = isLocked ? 'bi-pin-angle-fill text-danger' : 'bi-pin-angle text-secondary'; const lockText = isLocked ? 'Fixierung lösen' : 'Auf Map fixieren'; adminActions = `
Bearbeiten
Neu
Edit
${lockText}
Löschen
`; } document.getElementById('main-area').innerHTML = `
Aktionen
Sitzung öffnen
${item.type === 'web' ? `
Neuer Tab
` : ''}${adminActions}

${item.name}

${protoInfo.name}
Übersicht
Asset Info
Unterverbindungen (${children.length})
${item.type === 'web' ? `
` : ''}
Uplink (hängt an)
${uplinkName}
Host
${item.host}${item.port ? ':'+item.port : ''}
Benutzername
${item.username || '-'}
Passwort
••••••••
${tagsHtml ? `
Tags
${tagsHtml}
` : ''}
${item.kuma_url ? `
Systemstatus (Uptime Kuma)
${item.kuma_url.includes('/badge/') ? `` : `
`}
` : ''}
`; }; window.saveConnection = async function() { if(window.currentUser.role !== 'admin') return; const t = document.getElementById('f_target').value; const data = { id: document.getElementById('f_conn_id').value || null, folder_name: t.startsWith('folder:') ? t.substring(7) : null, parent_id: t.startsWith('conn:') ? t.substring(5) : null, name: document.getElementById('f_conn_name').value, type: document.getElementById('f_type').value, host: document.getElementById('f_host').value, port: document.getElementById('f_port').value, username: document.getElementById('f_user').value, password: document.getElementById('f_pass').value, kuma_url: document.getElementById('f_kuma').value, tags: document.getElementById('f_tags').value, asset_type: document.getElementById('f_asset_type').value, os: document.getElementById('f_os').value, hardware: document.getElementById('f_hardware').value, serial_number: document.getElementById('f_serial').value, mac_address: document.getElementById('f_mac').value, location: document.getElementById('f_location').value, warranty: document.getElementById('f_warranty').value, uplink_id: document.getElementById('f_uplink_id').value || null, uplink_port: document.getElementById('f_uplink_port').value, local_port: document.getElementById('f_local_port').value }; await fetch('api.php?action=connections', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); await window.loadData(); window.showHomeDashboard(); }; window.executeConnection = function(type, host, port, user, pass) { if(type === 'web') return; const p = port || "-"; const u = user || "-"; const pwd = pass || "-"; window.location.href = `my${type}://${host};${p};${u};${pwd}`; }; window.deleteConnection = async function(id) { if(window.currentUser.role !== 'admin') return; if(!confirm("Wirklich löschen?")) return; await fetch('api.php?action=delete_connection', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({id: id}) }); await window.loadData(); window.showHomeDashboard(); }; window.toggleMapLock = async function(id, lockState) { if(window.currentUser.role !== 'admin') return; await fetch('api.php?action=toggle_map_lock', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({id: id, locked: lockState}) }); await window.loadData(); const item = window.connections.find(c => c.id == id); if (item && !window.isMapPage) window.showConnectionDetails(item); }; window.loadMy2FA = async function() { const data = await (await fetch('api.php?action=my_2fa')).json(); const container = document.getElementById('2fa-container'); if (data.enabled) { container.innerHTML = `
2FA ist aktiv.
`; } else { container.innerHTML = `

Scanne diesen Code mit dem Google Authenticator.
Secret (Manuell): ${data.secret}

`; } }; window.enable2FA = async function(secret) { const code = document.getElementById('setup_2fa_code').value; const res = await fetch('api.php?action=my_2fa', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({secret: secret, code: code}) }); if(res.ok) { window.showToast("2FA aktiviert!"); window.loadMy2FA(); } else { alert("Code falsch!"); } }; window.disable2FA = async function() { if(!confirm("2FA wirklich deaktivieren?")) return; await fetch('api.php?action=my_2fa', { method: 'DELETE' }); window.showToast("2FA deaktiviert!"); window.loadMy2FA(); }; window.showSettings = function() { if(window.isMapPage) return; document.querySelectorAll('.connection-item').forEach(el => el.classList.remove('active')); const mainArea = document.getElementById('main-area'); let adminTabs = ''; let adminContent = ''; if (window.currentUser.role === 'admin') { let userRows = window.users.map(u => `${u.username}${u.role}${u.totp_enabled ? '' : '-'}`).join(''); let protoRows = window.protocols.map(p => ` ${p.name}${p.proto_key}${p.exec_path}${p.proto_key !== 'web' ? `` : ''}`).join(''); let auditRows = ''; if(window.auditLogs && window.auditLogs.length > 0) { auditRows = window.auditLogs.map(l => `${l.timestamp}${l.user}${l.action}${l.details}`).join(''); } else { auditRows = `Keine Einträge vorhanden.`; } adminTabs = ``; // NEU: Eingabefeld für ausgeschlossene Passwort-Zeichen im Tab "System & Daten" adminContent = `
Startseite / Dashboard
Passwort-Generator
Daten & Import
|
${userRows}
BenutzernameRolle2FA
Benutzer anlegen/bearbeiten
${protoRows}
DienstKürzelPfad
Audit-Log
Zeigt die letzten 200 Aktionen
${auditRows}
ZeitstempelBenutzerAktionDetails
`; } else { adminContent = `
Nur Administratoren können globale Einstellungen ändern. Lade dein lokales PC Setup-Skript hier herunter.
`; } mainArea.innerHTML = `

Profil & Einstellungen

Zwei-Faktor-Authentifizierung (2FA)

Schütze deinen Account zusätzlich mit einem TOTP-Code.

Lade 2FA Daten...
${adminContent}
`; window.loadMy2FA(); }; window.saveUser = async function() { const data = { id: document.getElementById('u_id').value, username: document.getElementById('u_name').value, password: document.getElementById('u_pass').value, role: document.getElementById('u_role').value }; if(!data.username) return alert("Benutzername fehlt"); await fetch('api.php?action=users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); await window.loadData(); window.showSettings(); }; window.editUser = function(id, name, role) { document.getElementById('u_id').value = id; document.getElementById('u_name').value = name; document.getElementById('u_role').value = role; }; window.deleteUser = async function(id) { if(!confirm("User löschen?")) return; await fetch('api.php?action=users', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({id: id}) }); await window.loadData(); window.showSettings(); }; window.saveGeneralSettings = async function() { const data = { dashboard_url: document.getElementById('s_dashboard_url').value, pwd_exclude_chars: document.getElementById('s_pwd_exclude').value }; await fetch('api.php?action=settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); await window.loadData(); window.showToast("Einstellungen gespeichert!"); }; window.addProtocol = async function() { const data = { proto_key: document.getElementById('p_key').value, name: document.getElementById('p_name').value, exec_path: document.getElementById('p_path').value, args: '', icon: 'bi-terminal' }; await fetch('api.php?action=protocols', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); await window.loadData(); window.showSettings(); }; window.deleteProtocol = async function(id) { await fetch('api.php?action=protocols', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({id: id}) }); await window.loadData(); window.showSettings(); }; window.exportCSV = function() { const esc = (s) => '"' + String(s || '').replace(/"/g, '""') + '"'; let csv = "folder,name,type,host,port,username,password,kuma_url,tags,asset_type,os,hardware,serial_number,mac_address,location,warranty,uplink_id,uplink_port,local_port\n"; window.connections.forEach(c => { csv += [esc(c.folder_name), esc(c.name), esc(c.type), esc(c.host), esc(c.port), esc(c.username), esc(c.password), esc(c.kuma_url), esc(c.tags), esc(c.asset_type), esc(c.os), esc(c.hardware), esc(c.serial_number), esc(c.mac_address), esc(c.location), esc(c.warranty), esc(c.uplink_id), esc(c.uplink_port), esc(c.local_port)].join(',') + "\n"; }); const a = document.createElement('a'); a.href = window.URL.createObjectURL(new Blob([csv], { type: 'text/csv;charset=utf-8;' })); a.download = 'rdm_export.csv'; a.click(); }; window.importCSV = async function() { const f = document.getElementById('csvFileInput').files[0]; if (!f) return alert("Datei wählen."); const reader = new FileReader(); reader.onload = async (e) => { const lines = e.target.result.split(/\r?\n/).filter(line => line.trim() !== ''); const delimiter = lines[0].includes(';') ? ';' : ','; const headers = lines[0].split(delimiter).map(h => h.trim().toLowerCase().replace(/^"|"$/g, '')); const data = []; for (let i = 1; i < lines.length; i++) { const regex = new RegExp(`(\\${delimiter}|\\r?\\n|\\r|^)(?:"([^"]*(?:""[^"]*)*)"|([^"\\${delimiter}\\r\\n]*))`, 'gi'); let values = []; let match; while (match = regex.exec(lines[i])) { values.push(match[2] ? match[2].replace(/""/g, '"') : match[3]); } let rowObj = {}; headers.forEach((h, idx) => rowObj[h] = values[idx] || ''); if(rowObj['name'] && rowObj['host']) data.push(rowObj); } if(data.length === 0) return alert("Keine gültigen Zeilen."); const res = await (await fetch('api.php?action=import_connections', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) })).json(); if (res.status === "success") { window.showToast("Import erfolgreich!"); await window.loadData(); window.showSettings(); } }; reader.readAsText(f); }; window.exportMapPNG = function() { const canvas = document.querySelector('#mynetwork canvas'); if(!canvas) { alert("Fehler: Karte konnte nicht im Browser gefunden werden."); return; } const dataUrl = canvas.toDataURL("image/png"); const a = document.createElement('a'); a.href = dataUrl; a.download = 'Netzwerk-Topologie.png'; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.showToast("Export erfolgreich!"); }; window.toggleFullScreen = function() { const elem = document.getElementById('mynetwork').parentElement; if (!document.fullscreenElement) { elem.requestFullscreen().catch(err => { alert("Fullscreen API wird nicht unterstützt."); }); } else { document.exitFullscreen(); } }; window.filterMap = function(category) { if (!window.networkInstance) return; const allNodes = window.networkInstance.body.data.nodes.get(); const updates = []; allNodes.forEach(n => { if (n.id.startsWith('folder_')) return; const connId = n.id.replace('conn_', ''); const conn = window.connections.find(c => c.id == connId); let isMatch = !category || (conn && conn.asset_type === category); updates.push({ id: n.id, opacity: isMatch ? 1 : 0.15 }); }); window.networkInstance.body.data.nodes.update(updates); }; window.resetMapPhysics = async function() { if(window.currentUser.role !== 'admin') return; if(!confirm("⚠️ ACHTUNG: Möchtest du wirklich alle fixierten Koordinaten löschen und die Schwerkraft neu berechnen lassen?")) return; await fetch('api.php?action=reset_map', { method: 'POST' }); window.showToast("Schwerkraft reaktiviert!"); await window.loadData(); }; window.drawMap = function() { if (!window.isMapPage) return; let containerWrapper = document.getElementById('mynetwork').parentElement; if(!document.getElementById('map-controls') && containerWrapper) { containerWrapper.style.position = 'relative'; const controls = document.createElement('div'); controls.id = 'map-controls'; controls.style.cssText = "position:absolute; top:20px; right:20px; z-index:1000; background:var(--bg-card); padding:10px; border-radius:8px; border:1px solid var(--border-color); box-shadow:0 4px 10px rgba(0,0,0,0.3); display:flex; gap:10px; align-items:center;"; let filterOptions = ``; window.categories.forEach(c => filterOptions += ``); controls.innerHTML = `
`; containerWrapper.appendChild(controls); } const currentTheme = document.documentElement.getAttribute('data-theme') || 'light'; const isDark = currentTheme === 'dark'; const textColor = isDark ? '#f1f1f1' : '#333333'; const folderColor = isDark ? '#333333' : '#e2e8f0'; const highlightEdgeColor = isDark ? '#3f98e7' : '#0078d7'; const nodes = new vis.DataSet(); const edges = new vis.DataSet(); const folderSet = new Set(); const groupColors = { 'rdp': { background: '#0078d7', border: '#005a9e' }, 'ssh': { background: '#2b2b2b', border: '#000000' }, 'web': { background: '#107c41', border: '#0b592f' }, 'default': { background: '#6c757d', border: '#495057' } }; window.connections.forEach(c => { if (c.folder_name && !c.uplink_id && !folderSet.has(c.folder_name)) { folderSet.add(c.folder_name); const shortName = c.folder_name.split('/').pop(); nodes.add({ id: 'folder_' + c.folder_name, label: shortName, shape: 'box', color: { background: folderColor, border: '#aaaaaa' }, font: { color: textColor, size: 16, bold: true }, margin: 10 }); } let tooltip = `${c.name}
${c.host}${c.port ? ':'+c.port : ''}
`; if(c.asset_type) tooltip += `Typ: ${c.asset_type}
`; if(c.hardware) tooltip += `Hardware: ${c.hardware}
`; if(c.local_port && c.uplink_port) tooltip += `Ports: ${c.local_port} ➔ ${c.uplink_port}`; let nodeShape = c.uplink_id ? 'box' : 'dot'; let nodeImage = undefined; let nodeFont = { color: c.uplink_id ? '#fff' : textColor, size: c.parent_id ? 12 : 14 }; let labelText = ''; let typeBrackets = c.asset_type ? '\n[' + c.asset_type + ']' : ''; if (window.mapLabelMode === 'name') { labelText = c.name + typeBrackets; } else if (window.mapLabelMode === 'ip') { labelText = (c.host || 'Keine IP') + typeBrackets; } else if (window.mapLabelMode === 'both') { labelText = c.name + '\n' + (c.host || 'Keine IP') + typeBrackets; } if (c.asset_type) { const catInfo = window.categories.find(cat => cat.name === c.asset_type); if (catInfo) { nodeShape = 'image'; nodeImage = catInfo.url; nodeFont.color = textColor; nodeFont.background = isDark ? 'rgba(30,30,30,0.7)' : 'rgba(255,255,255,0.7)'; } } let nodeOptions = { id: 'conn_' + c.id, label: c.map_locked == 1 ? "📌 " + labelText : labelText, shape: nodeShape, image: nodeImage, size: nodeImage ? 25 : (c.parent_id ? 10 : 18), color: groupColors[c.type] || groupColors['default'], font: nodeFont, title: tooltip }; if (c.map_x !== null && c.map_y !== null) { nodeOptions.x = parseFloat(c.map_x); nodeOptions.y = parseFloat(c.map_y); nodeOptions.physics = false; } if (c.map_locked == 1) nodeOptions.fixed = true; const filterVal = document.getElementById('map-filter-select') ? document.getElementById('map-filter-select').value : ''; if(filterVal && c.asset_type !== filterVal) { nodeOptions.opacity = 0.15; } nodes.add(nodeOptions); if (c.uplink_id) { let edgeLabel = ''; if (c.local_port && c.uplink_port) edgeLabel = `${c.local_port} ➔ ${c.uplink_port}`; else if (c.local_port) edgeLabel = `${c.local_port} ➔`; else if (c.uplink_port) edgeLabel = `➔ ${c.uplink_port}`; edges.add({ from: 'conn_' + c.uplink_id, to: 'conn_' + c.id, label: edgeLabel, arrows: { to: { enabled: true, scaleFactor: 0.6 } }, font: { align: 'middle', size: 11, color: textColor, background: isDark ? '#1e1e1e' : '#ffffff', strokeWidth: 0 }, color: { color: highlightEdgeColor, highlight: highlightEdgeColor }, width: 2, length: 180 }); } else if (c.parent_id) { edges.add({ from: 'conn_' + c.parent_id, to: 'conn_' + c.id, dashes: true, color: { color: '#aaaaaa' }, length: 80 }); } else if (c.folder_name) { edges.add({ from: 'folder_' + c.folder_name, to: 'conn_' + c.id, color: { color: '#aaaaaa', opacity: 0.4 }, length: 250 }); } }); const container = document.getElementById('mynetwork'); const data = { nodes: nodes, edges: edges }; const options = { physics: { stabilization: true, barnesHut: { gravitationalConstant: -2500, springLength: 180, springConstant: 0.04 } }, edges: { smooth: false }, interaction: { hover: true, zoomView: true, dragView: true } }; window.networkInstance = new vis.Network(container, data, options); window.networkInstance.on("dragEnd", async function (params) { if (window.currentUser.role !== 'admin') return; if (params.nodes.length > 0) { const nodeId = params.nodes[0]; if(nodeId.startsWith('conn_')) { const id = nodeId.replace('conn_', ''); const pos = window.networkInstance.getPositions([nodeId])[nodeId]; await fetch('api.php?action=save_map_pos', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({id: id, x: pos.x, y: pos.y}) }); const conn = window.connections.find(c => c.id == id); if(conn) { conn.map_x = pos.x; conn.map_y = pos.y; } } } }); }; window.spawnAsteroidsShip = function() { if(!window.isMapPage || document.getElementById('asteroids-ship')) return; const mapContainer = document.getElementById('mynetwork'); if(!mapContainer) return; const ship = document.createElement('div'); ship.id = 'asteroids-ship'; ship.innerHTML = ``; const startY = Math.random() * mapContainer.offsetHeight; ship.style.cssText = `position: absolute; top: ${startY}px; left: -50px; z-index: 10; pointer-events: none; transition: transform 10s linear; transform: rotate(90deg);`; mapContainer.appendChild(ship); setTimeout(() => { const endY = Math.random() * mapContainer.offsetHeight; const endX = mapContainer.offsetWidth + 50; const angle = Math.atan2(endY - startY, endX - (-50)) * 180 / Math.PI; ship.style.transform = `translate(${endX}px, ${endY - startY}px) rotate(${angle + 90}deg)`; }, 100); setTimeout(() => { if(ship.parentNode) ship.parentNode.removeChild(ship); }, 10500); }; setInterval(() => { if(Math.random() > 0.8) window.spawnAsteroidsShip(); }, 30000); window.initTheme(); window.checkSession();