// BenzCloud Frontend Application Engine // Author: Jeremy Benz (@benzjeremy) • GNU GPL-3.0 let currentLang = localStorage.getItem("benzcloud_lang") || "de"; let sessionToken = localStorage.getItem("benzcloud_token") || ""; let systemConfig = null; let currentPath = ""; const i18n = { de: { setup_badge: "✨ One-Click Ersteinrichtung", setup_title: "Willkommen bei BenzCloud", setup_desc: "Deine datenschutzfreundliche, lokale Enterprise-Suite. Trage deine Wunsch-Domain ein – Mesh-VPN, DNS-Server und Routing konfigurieren sich vollautomatisch ohne Router-Portfreigaben.", lbl_domain: "Wunsch-Domain (Basis für System & Dienste):", hint_domain: "Subdomains wie vpn, drive und mail werden automatisch erzeugt.", lbl_admin_user: "Administrator-Benutzername:", lbl_admin_pass: "Administrator-Passwort (mind. 8 Zeichen):", sec_title: "Zero-Dummy-Security ab Werk:", sec_desc: "PBKDF2 Schlüsselableitung (100.000 Runden), AES-256-GCM Verschlüsselung at Rest und gegenseitig authentifiziertes Slack Nebula P2P-Mesh.", btn_start_setup: "🚀 BenzCloud jetzt automatisch einrichten", login_title: "BenzCloud Anmeldung", login_desc: "Melde dich mit deinem Konto an, um auf das Cockpit und deine Dienste zuzugreifen.", lbl_username: "Benutzername:", lbl_password: "Passwort:", btn_login: "Anmelden", logout: "Abmelden", m_domain: "Basis-Domain", m_vpn: "Mesh-VPN (Nebula)", m_dns: "Custom DNS Server", m_peers: "Verbundene Peers", m_peers_sub: "P2P Mesh Nodes", tab_overview: "🧭 Schnellstart", tab_drive: "📁 Drive (Cloud-Dateien)", tab_vpn: "🛡️ Mesh-VPN & Peers", tab_dns: "🌐 DNS & Routing", tab_plugins: "🧩 Plugins", tab_users: "👥 Benutzerverwaltung", services_title: "Verfügbare Enterprise-Dienste", srv_drive: "BenzCloud Drive", srv_drive_desc: "Verschlüsselter Dateispeicher, Dokumentenablage & Dateifreigabe (Nextcloud-Alternative).", srv_mail: "BenzCloud Mail", srv_mail_desc: "Geschlossenes internes E-Mail-System (SMTP/IMAP) mit integrierter Webmail-Oberfläche.", srv_chat: "BenzCloud Chat", srv_chat_desc: "Echtzeit-Team-Chat (Slack/Teams-Alternative) für verschlüsselte Kommunikation.", srv_web: "Web-Hosting Engine", srv_web_desc: "Eigene Webseiten unter frei wählbaren Subdomains (HTML, PHP, Astro).", pair_title: "Client-Kopplung (Android & PC)", pair_desc: "Verbinde die Client-App einmalig über die lokale Server-IP. Danach läuft die gesamte Kommunikation automatisch verschlüsselt über das Mesh-VPN und den Custom-DNS.", pair_server_endpoint: "Server LAN-Kopplungs-Endpunkt:", pair_cli_cmd: "Verbindungsbefehl (BenzCloud-Client CLI):", drive_vault_title: "Verschlüsselter Dateispeicher (AES-256-GCM)", btn_upload: "⬆️ Datei hochladen", btn_new_folder: "📁 Neuer Ordner", th_name: "Name", th_size: "Größe", th_date: "Änderungsdatum", th_actions: "Aktionen", loading_files: "Lade Dateien...", vpn_title: "Slack Nebula Mesh-VPN & Peer-Topologie", vpn_desc: "Das P2P Mesh-Overlay verbindet alle autorisierten Endgeräte verschlüsselt ohne Portweiterleitungen am Router.", th_peer_name: "Node / Peer Name", th_overlay_ip: "Overlay IP", th_role: "Rolle", th_status: "Status", th_valid_until: "Zertifikat gültig bis", dns_title: "Custom DNS Server & Subdomain-Routing", dns_records_title: "Autoritative System-Subdomains", dns_logs_title: "Live DNS-Abfragen (RFC 1035)", plugins_title: "Modulare Enterprise-Plugins", plugins_desc: "Der Server orchestriert Module in getrennten Prozessen über token-gesicherte Schnittstellen.", users_title: "Benutzer- und Rechteverwaltung", btn_create_user: "➕ Neuer Benutzer", th_user: "Benutzer", th_perms: "Berechtigungen", th_base_perms: "Basis-Privilegien (Fixiert)", modal_create_user_title: "Neuen Benutzer anlegen", immutable_perms_text: "VPN & DNS sind unveränderlich aktiv und können nicht entzogen werden.", btn_cancel: "Abbrechen", btn_save: "Erstellen", pre_release_pill: "Pre-Release / In aktiver Entwicklung", btn_toggle_on: "Aktiviert", btn_toggle_off: "Deaktiviert", btn_download: "⬇️ Herunterladen", btn_delete: "🗑️ Löschen", prompt_folder_name: "Name des neuen Ordners:", confirm_delete: "Möchtest du diese Datei wirklich löschen?" }, en: { setup_badge: "✨ One-Click Initial Setup", setup_title: "Welcome to BenzCloud", setup_desc: "Your privacy-first, local enterprise suite. Enter your desired domain – Mesh-VPN, DNS server, and routing configure automatically without router port forwards.", lbl_domain: "Target Domain (Base for system & services):", hint_domain: "Subdomains like vpn, drive, and mail will be generated automatically.", lbl_admin_user: "Administrator Username:", lbl_admin_pass: "Administrator Password (min. 8 characters):", sec_title: "Zero-Dummy-Security by Design:", sec_desc: "PBKDF2 key derivation (100,000 rounds), AES-256-GCM encryption at rest, and mutually authenticated Slack Nebula P2P mesh.", btn_start_setup: "🚀 Automatically configure BenzCloud now", login_title: "BenzCloud Authentication", login_desc: "Sign in with your account to access your cockpit and services.", lbl_username: "Username:", lbl_password: "Password:", btn_login: "Sign In", logout: "Log Out", m_domain: "Base Domain", m_vpn: "Mesh-VPN (Nebula)", m_dns: "Custom DNS Server", m_peers: "Connected Peers", m_peers_sub: "P2P Mesh Nodes", tab_overview: "🧭 Quickstart", tab_drive: "📁 Drive (Cloud Files)", tab_vpn: "🛡️ Mesh-VPN & Peers", tab_dns: "🌐 DNS & Routing", tab_plugins: "🧩 Plugins", tab_users: "👥 User Management", services_title: "Available Enterprise Services", srv_drive: "BenzCloud Drive", srv_drive_desc: "Encrypted file vault, document store & file sharing (Nextcloud alternative).", srv_mail: "BenzCloud Mail", srv_mail_desc: "Closed internal email system (SMTP/IMAP) with integrated webmail client.", srv_chat: "BenzCloud Chat", srv_chat_desc: "Real-time team chat (Slack/Teams alternative) for direct communication.", srv_web: "Web-Hosting Engine", srv_web_desc: "Deploy custom websites under arbitrary subdomains (HTML, PHP, Astro).", pair_title: "Client Pairing (Android & PC)", pair_desc: "Connect your client app once via local server IP. Afterward, all traffic automatically flows securely through Mesh-VPN and custom DNS.", pair_server_endpoint: "Server LAN Pairing Endpoint:", pair_cli_cmd: "Connect Command (BenzCloud-Client CLI):", drive_vault_title: "Encrypted File Storage (AES-256-GCM)", btn_upload: "⬆️ Upload File", btn_new_folder: "📁 New Folder", th_name: "Name", th_size: "Size", th_date: "Modified Date", th_actions: "Actions", loading_files: "Loading files...", vpn_title: "Slack Nebula Mesh-VPN & Peer Topology", vpn_desc: "The P2P mesh overlay connects all authorized endpoints securely without router port forwarding.", th_peer_name: "Node / Peer Name", th_overlay_ip: "Overlay IP", th_role: "Role", th_status: "Status", th_valid_until: "Certificate Valid Until", dns_title: "Custom DNS Server & Subdomain Routing", dns_records_title: "Authoritative System Subdomains", dns_logs_title: "Live DNS Queries (RFC 1035)", plugins_title: "Modular Enterprise Plugins", plugins_desc: "The server orchestrates modules in separate processes across token-authenticated interfaces.", users_title: "User & Rights Management", btn_create_user: "➕ New User", th_user: "User", th_perms: "Permissions", th_base_perms: "Base Privileges (Locked)", modal_create_user_title: "Create New User", immutable_perms_text: "VPN & DNS are permanently active and cannot be revoked.", btn_cancel: "Cancel", btn_save: "Create", pre_release_pill: "Pre-Release / In Active Development", btn_toggle_on: "Enabled", btn_toggle_off: "Disabled", btn_download: "⬇️ Download", btn_delete: "🗑️ Delete", prompt_folder_name: "Name of new folder:", confirm_delete: "Are you sure you want to delete this file?" } }; document.addEventListener("DOMContentLoaded", () => { applyLanguage(currentLang); checkSystemStatus(); setInterval(refreshActiveTabData, 3000); }); function setLanguage(lang) { currentLang = lang; localStorage.setItem("benzcloud_lang", lang); applyLanguage(lang); } function applyLanguage(lang) { document.documentElement.lang = lang; document.querySelectorAll("[data-i18n]").forEach(el => { const key = el.getAttribute("data-i18n"); if (i18n[lang] && i18n[lang][key]) { el.textContent = i18n[lang][key]; } }); document.getElementById("langDE").classList.toggle("active", lang === "de"); document.getElementById("langEN").classList.toggle("active", lang === "en"); } async function checkSystemStatus() { try { const res = await fetch("/api/status"); const data = await res.json(); systemConfig = data; document.getElementById("headerDomainPill").textContent = data.base_domain || "intern"; document.getElementById("mDomain").textContent = data.base_domain || "-"; document.getElementById("mLocalIP").textContent = "LAN: " + (data.server_local_ip || "127.0.0.1"); document.getElementById("mVPNIP").textContent = "Overlay IP: " + (data.server_vpn_ip || "10.42.0.1"); document.getElementById("mDNSPort").textContent = `Port ${data.dns_port} (UDP & TCP)`; document.getElementById("mPeers").textContent = data.mesh_peers || "0"; // Update pairing links const pairUrl = `http://${data.server_local_ip}:${data.http_port}/api/pair`; document.getElementById("pairEndpoint").textContent = pairUrl; document.getElementById("pairCliCmd").textContent = `benzcloud-client pair -server http://${data.server_local_ip}:${data.http_port} -user admin`; // Service URLs document.getElementById("urlDrive").textContent = `http://drive.${data.base_domain}`; document.getElementById("linkDrive").href = `http://drive.${data.base_domain}`; document.getElementById("urlMail").textContent = `http://mail.${data.base_domain}`; document.getElementById("linkMail").href = `http://mail.${data.base_domain}`; document.getElementById("urlChat").textContent = `http://chat.${data.base_domain}`; document.getElementById("linkChat").href = `http://chat.${data.base_domain}`; document.getElementById("urlWeb").textContent = `http://*.${data.base_domain}`; document.getElementById("linkWeb").href = `http://${data.base_domain}`; if (!data.setup_completed) { showView("setupWizard"); return; } if (!sessionToken) { showView("loginView"); return; } showView("dashboardView"); document.getElementById("btnLogout").style.display = "block"; loadDashboardData(); } catch (err) { console.error("Failed to fetch system status:", err); } } function showView(viewId) { document.getElementById("setupWizard").style.display = viewId === "setupWizard" ? "block" : "none"; document.getElementById("loginView").style.display = viewId === "loginView" ? "block" : "none"; document.getElementById("dashboardView").style.display = viewId === "dashboardView" ? "block" : "none"; } async function submitSetup(e) { e.preventDefault(); const baseDomain = document.getElementById("setupDomain").value.trim(); const adminUser = document.getElementById("setupAdminUser").value.trim(); const adminPass = document.getElementById("setupAdminPass").value; const btn = document.getElementById("btnSubmitSetup"); btn.disabled = true; btn.textContent = currentLang === "de" ? "⚙️ Initialisiere Mesh-VPN, DNS und Zertifikate..." : "⚙️ Initializing Mesh-VPN, DNS and certificates..."; try { const res = await fetch("/api/setup", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ base_domain: baseDomain, admin_username: adminUser, admin_password: adminPass }) }); const data = await res.json(); if (!res.ok) { alert("Fehler: " + (data.error || "Setup fehlgeschlagen")); btn.disabled = false; btn.textContent = i18n[currentLang].btn_start_setup; return; } sessionToken = data.session_token; localStorage.setItem("benzcloud_token", sessionToken); checkSystemStatus(); } catch (err) { alert("Netzwerkfehler: " + err); btn.disabled = false; } } async function submitLogin(e) { e.preventDefault(); const username = document.getElementById("loginUser").value.trim(); const password = document.getElementById("loginPass").value; try { const res = await fetch("/api/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password }) }); const data = await res.json(); if (!res.ok) { alert(currentLang === "de" ? "Ungültige Anmeldedaten" : "Invalid credentials"); return; } sessionToken = data.token; localStorage.setItem("benzcloud_token", sessionToken); checkSystemStatus(); } catch (err) { alert("Netzwerkfehler: " + err); } } function logout() { sessionToken = ""; localStorage.removeItem("benzcloud_token"); location.reload(); } function switchTab(tabId) { document.querySelectorAll(".tab-btn").forEach(btn => btn.classList.remove("active")); document.querySelectorAll(".tab-pane").forEach(pane => pane.classList.remove("active")); event.target.classList.add("active"); const targetPane = document.getElementById(`tabContent_${tabId}`); if (targetPane) targetPane.classList.add("active"); refreshActiveTabData(); } function refreshActiveTabData() { if (!sessionToken || !systemConfig || !systemConfig.setup_completed) return; const activePane = document.querySelector(".tab-pane.active"); if (!activePane) return; const id = activePane.id; if (id === "tabContent_drive") loadDriveFiles(); if (id === "tabContent_vpn") loadVPNPeers(); if (id === "tabContent_dns") loadDNSData(); if (id === "tabContent_plugins") loadPlugins(); if (id === "tabContent_users") loadUsers(); } function loadDashboardData() { loadDriveFiles(); loadVPNPeers(); loadDNSData(); loadPlugins(); loadUsers(); } /* DRIVE */ async function loadDriveFiles() { try { const res = await fetch(`/api/drive/files?path=${encodeURIComponent(currentPath)}`, { headers: { Authorization: `Bearer ${sessionToken}` } }); if (!res.ok) return; const data = await res.json(); const tbody = document.getElementById("fileTableBody"); tbody.innerHTML = ""; if (!data.files || data.files.length === 0) { tbody.innerHTML = `${currentLang === "de" ? "Keine Dateien vorhanden." : "No files available."}`; return; } data.files.forEach(f => { const tr = document.createElement("tr"); const icon = f.is_dir ? "📁" : "📄"; const sizeStr = f.is_dir ? "-" : formatBytes(f.size); const dateStr = new Date(f.mod_time).toLocaleString(); tr.innerHTML = ` ${icon} ${escapeHtml(f.name)} ${sizeStr} ${dateStr} ${!f.is_dir ? `${i18n[currentLang].btn_download}` : ""} `; tbody.appendChild(tr); }); } catch (err) { console.error("Drive load failed:", err); } } async function uploadSelectedFile() { const input = document.getElementById("fileUploadInput"); if (!input.files || input.files.length === 0) return; const file = input.files[0]; const targetPath = currentPath ? `${currentPath}/${file.name}` : file.name; try { const res = await fetch(`/api/drive/upload?path=${encodeURIComponent(targetPath)}`, { method: "POST", headers: { Authorization: `Bearer ${sessionToken}` }, body: file }); if (res.ok) { loadDriveFiles(); } else { alert("Upload failed"); } } catch (err) { alert("Upload error: " + err); } input.value = ""; } async function promptNewFolder() { const name = prompt(i18n[currentLang].prompt_folder_name); if (!name) return; const folderPath = currentPath ? `${currentPath}/${name}` : name; try { await fetch("/api/drive/folder", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${sessionToken}` }, body: JSON.stringify({ path: folderPath }) }); loadDriveFiles(); } catch (err) { alert("Folder creation failed"); } } async function deleteFile(path) { if (!confirm(i18n[currentLang].confirm_delete)) return; try { await fetch(`/api/drive/delete?path=${encodeURIComponent(path)}`, { method: "DELETE", headers: { Authorization: `Bearer ${sessionToken}` } }); loadDriveFiles(); } catch (err) { alert("Delete failed"); } } /* VPN PEERS */ async function loadVPNPeers() { try { const res = await fetch("/api/status"); const data = await res.json(); const tbody = document.getElementById("peerTableBody"); tbody.innerHTML = ""; // Server Node const sTr = document.createElement("tr"); sTr.innerHTML = ` ${escapeHtml(data.system)} (Lighthouse) ${data.server_vpn_ip} Lighthouse Online Permanenter Host `; tbody.appendChild(sTr); } catch (err) { console.error("VPN peers load failed:", err); } } /* DNS & ROUTING */ async function loadDNSData() { if (!systemConfig) return; const domain = systemConfig.base_domain; const ip = systemConfig.server_vpn_ip; const recordsList = document.getElementById("dnsRecordsList"); recordsList.innerHTML = `
vpn.${domain}${ip}
drive.${domain}${ip}
mail.${domain}${ip}
chat.${domain}${ip}
*.${domain} (Web Engine)${ip}
`; try { const res = await fetch("/api/dns/logs", { headers: { Authorization: `Bearer ${sessionToken}` } }); if (!res.ok) return; const data = await res.json(); const logsList = document.getElementById("dnsLogsList"); logsList.innerHTML = ""; if (!data.logs || data.logs.length === 0) { logsList.innerHTML = `
${currentLang === "de" ? "Noch keine externen DNS-Anfragen eingetroffen." : "No DNS queries logged yet."}
`; return; } data.logs.slice(-8).reverse().forEach(log => { const row = document.createElement("div"); row.className = "log-row"; row.innerHTML = ` ${escapeHtml(log.domain)} (${escapeHtml(log.type)}) ${escapeHtml(log.answer)} `; logsList.appendChild(row); }); } catch (err) { console.error("DNS logs load failed:", err); } } /* PLUGINS */ async function loadPlugins() { try { const res = await fetch("/api/plugins", { headers: { Authorization: `Bearer ${sessionToken}` } }); if (!res.ok) return; const data = await res.json(); const grid = document.getElementById("pluginCardsGrid"); grid.innerHTML = ""; data.plugins.forEach(p => { const card = document.createElement("div"); card.className = "plugin-card"; card.innerHTML = `
${escapeHtml(p.name)}
${p.enabled ? i18n[currentLang].btn_toggle_on : i18n[currentLang].btn_toggle_off}

Binary: ${escapeHtml(p.binary)} | Port: ${p.port}

Subdomains: ${p.subdomains.length ? p.subdomains.join(', ') : 'Frei wählbar'}

`; grid.appendChild(card); }); } catch (err) { console.error("Plugins load failed:", err); } } async function togglePlugin(id, enabled) { try { await fetch("/api/plugins/toggle", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${sessionToken}` }, body: JSON.stringify({ id, enabled }) }); loadPlugins(); } catch (err) { alert("Plugin toggle failed"); } } /* USERS */ async function loadUsers() { try { const res = await fetch("/api/users", { headers: { Authorization: `Bearer ${sessionToken}` } }); if (!res.ok) return; const data = await res.json(); const tbody = document.getElementById("usersTableBody"); tbody.innerHTML = ""; data.users.forEach(u => { const tr = document.createElement("tr"); let permsBadges = []; if (u.permissions.admin) permsBadges.push('Admin'); if (u.permissions.drive) permsBadges.push('Drive'); if (u.permissions.mail) permsBadges.push('Mail'); if (u.permissions.chat) permsBadges.push('Chat'); if (u.permissions.web) permsBadges.push('Web'); tr.innerHTML = ` ${escapeHtml(u.username)} ${u.overlay_ip} ${permsBadges.join(' ')} 🔒 VPN & DNS (Immer aktiv) `; tbody.appendChild(tr); }); } catch (err) { console.error("Users load failed:", err); } } function openCreateUserModal() { document.getElementById("createUserModal").style.display = "flex"; } function closeCreateUserModal() { document.getElementById("createUserModal").style.display = "none"; } async function submitCreateUser(e) { e.preventDefault(); const username = document.getElementById("newUsername").value.trim(); const password = document.getElementById("newPassword").value; const drive = document.getElementById("permDrive").checked; const mail = document.getElementById("permMail").checked; const chat = document.getElementById("permChat").checked; const web = document.getElementById("permWeb").checked; try { const res = await fetch("/api/users", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${sessionToken}` }, body: JSON.stringify({ username, password, permissions: { admin: false, drive, mail, chat, web, vpn: true, dns: true } }) }); if (!res.ok) { alert("Fehler beim Erstellen des Benutzers"); return; } closeCreateUserModal(); loadUsers(); } catch (err) { alert("Netzwerkfehler: " + err); } } function formatBytes(bytes) { if (bytes === 0) return "0 B"; const k = 1024; const sizes = ["B", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i]; } function escapeHtml(str) { if (!str) return ""; return String(str).replace(/[&<>"']/g, m => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[m])); }