diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 00000000..7c953211 --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,14 @@ +# Gitleaks configuration for BetterDesk +# Suppresses false positives from auto-generated lock files + +title = "BetterDesk Gitleaks Config" + +[allowlist] + description = "Allowlisted files and patterns" + + # Cargo.lock files contain crate checksums and dependency names like + # "crypto_secretbox" that trigger generic-api-key false positives. + paths = [ + '''.*Cargo\.lock$''', + '''.*package-lock\.json$''', + ] diff --git a/web-nodejs/data/auth.db-shm b/web-nodejs/data/auth.db-shm new file mode 100644 index 00000000..a89befe0 Binary files /dev/null and b/web-nodejs/data/auth.db-shm differ diff --git a/web-nodejs/data/auth.db-wal b/web-nodejs/data/auth.db-wal new file mode 100644 index 00000000..15c419c9 Binary files /dev/null and b/web-nodejs/data/auth.db-wal differ diff --git a/web-nodejs/data/betterdesk.db-shm b/web-nodejs/data/betterdesk.db-shm new file mode 100644 index 00000000..600e9cbb Binary files /dev/null and b/web-nodejs/data/betterdesk.db-shm differ diff --git a/web-nodejs/data/betterdesk.db-wal b/web-nodejs/data/betterdesk.db-wal new file mode 100644 index 00000000..2bab5816 Binary files /dev/null and b/web-nodejs/data/betterdesk.db-wal differ diff --git a/web-nodejs/public/css/beta31.css b/web-nodejs/public/css/beta31.css new file mode 100644 index 00000000..210432b2 --- /dev/null +++ b/web-nodejs/public/css/beta31.css @@ -0,0 +1,510 @@ +/** + * BetterDesk Console — Beta 3.1 Panel + * ISPmanager-inspired layout with persistent sidebar + content area + */ + +/* ═══════════════════════════════════════════════════════════════════ + NAVBAR TOGGLE BUTTON + ═══════════════════════════════════════════════════════════════════ */ + +.beta31-toggle-btn { + position: relative; + display: flex !important; +} + +/* ═══════════════════════════════════════════════════════════════════ + BETA 3.1 SHELL — full-screen overlay + ═══════════════════════════════════════════════════════════════════ */ + +.beta31-shell { + position: fixed; + inset: 0; + z-index: 250; + display: none; + background: var(--b31-bg, #f0f2f5); + font-family: var(--font-family); +} + +.beta31-shell.active { + display: flex; + flex-direction: column; +} + +/* ── Top bar ── */ +.b31-topbar { + height: 48px; + min-height: 48px; + background: var(--b31-topbar-bg, #2b579a); + color: #fff; + display: flex; + align-items: center; + padding: 0 16px; + gap: 12px; + z-index: 2; + box-shadow: 0 1px 3px rgba(0,0,0,.18); +} + +.b31-topbar-logo { + display: flex; + align-items: center; + gap: 8px; + font-weight: 700; + font-size: 15px; + letter-spacing: .02em; + white-space: nowrap; +} + +.b31-topbar-logo .material-icons { font-size: 22px; } + +.b31-topbar-badge { + background: rgba(255,255,255,.2); + font-size: 10px; + padding: 2px 6px; + border-radius: 4px; + font-weight: 600; + letter-spacing: .05em; +} + +.b31-topbar-tabs { + display: flex; + align-items: center; + gap: 2px; + margin-left: 24px; + height: 100%; +} + +.b31-topbar-tab { + background: none; + border: none; + color: rgba(255,255,255,.7); + font-size: 13px; + padding: 0 14px; + height: 100%; + cursor: pointer; + transition: all .15s; + border-bottom: 2px solid transparent; + display: flex; + align-items: center; + gap: 6px; + white-space: nowrap; +} + +.b31-topbar-tab:hover { color: #fff; background: rgba(255,255,255,.08); } +.b31-topbar-tab.active { + color: #fff; + border-bottom-color: #fff; + background: rgba(255,255,255,.1); +} + +.b31-topbar-tab .material-icons { font-size: 18px; } + +.b31-topbar-spacer { flex: 1; } + +.b31-topbar-actions { + display: flex; + align-items: center; + gap: 4px; +} + +.b31-topbar-btn { + background: none; + border: none; + color: rgba(255,255,255,.75); + padding: 6px; + border-radius: 6px; + cursor: pointer; + display: flex; + align-items: center; + transition: all .15s; +} +.b31-topbar-btn:hover { background: rgba(255,255,255,.12); color: #fff; } +.b31-topbar-btn .material-icons { font-size: 20px; } + +/* ── Body: sidebar + content ── */ +.b31-body { + flex: 1; + display: flex; + overflow: hidden; +} + +/* ── Sidebar ── */ +.b31-sidebar { + width: 220px; + min-width: 220px; + background: var(--b31-sidebar-bg, #fff); + border-right: 1px solid var(--b31-border, #dde1e6); + display: flex; + flex-direction: column; + overflow-y: auto; + overflow-x: hidden; +} + +.b31-sidebar-section { + padding: 6px 0; + border-bottom: 1px solid var(--b31-border, #dde1e6); +} + +.b31-sidebar-section:last-child { border-bottom: none; } + +.b31-sidebar-heading { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: .06em; + color: var(--b31-muted, #8a8f9a); + padding: 10px 16px 4px; +} + +.b31-sidebar-item { + display: flex; + align-items: center; + gap: 10px; + padding: 7px 16px; + font-size: 13px; + color: var(--b31-text, #333); + cursor: pointer; + transition: all .12s; + border: none; + background: none; + width: 100%; + text-align: left; + border-left: 3px solid transparent; +} + +.b31-sidebar-item:hover { + background: var(--b31-hover, #f5f7fa); + color: var(--b31-primary, #2b579a); +} + +.b31-sidebar-item.active { + background: var(--b31-active-bg, #e8eef7); + color: var(--b31-primary, #2b579a); + font-weight: 600; + border-left-color: var(--b31-primary, #2b579a); +} + +.b31-sidebar-item .material-icons { + font-size: 20px; + opacity: .7; + flex-shrink: 0; +} + +.b31-sidebar-item.active .material-icons { opacity: 1; } + +.b31-sidebar-item-badge { + margin-left: auto; + background: var(--b31-primary, #2b579a); + color: #fff; + font-size: 10px; + padding: 1px 6px; + border-radius: 10px; + font-weight: 600; +} + +/* ── Content area ── */ +.b31-content { + flex: 1; + overflow-y: auto; + padding: 24px; + background: var(--b31-bg, #f0f2f5); +} + +/* ── Content cards (ISPmanager-style collapsible sections) ── */ +.b31-card { + background: var(--b31-card-bg, #fff); + border: 1px solid var(--b31-border, #dde1e6); + border-radius: 6px; + margin-bottom: 16px; + box-shadow: 0 1px 2px rgba(0,0,0,.04); +} + +.b31-card-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + cursor: pointer; + user-select: none; + border-bottom: 1px solid var(--b31-border, #dde1e6); + transition: background .12s; +} + +.b31-card-header:hover { background: var(--b31-hover, #f5f7fa); } + +.b31-card-header-left { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + font-weight: 600; + color: var(--b31-text, #333); +} + +.b31-card-header-left .material-icons { + font-size: 18px; + color: var(--b31-primary, #2b579a); +} + +.b31-card-header-actions { + display: flex; + align-items: center; + gap: 4px; +} + +.b31-card-action-btn { + background: none; + border: none; + color: var(--b31-muted, #8a8f9a); + padding: 4px; + border-radius: 4px; + cursor: pointer; + display: flex; + transition: all .12s; +} +.b31-card-action-btn:hover { + background: var(--b31-hover, #f5f7fa); + color: var(--b31-primary, #2b579a); +} +.b31-card-action-btn .material-icons { font-size: 18px; } + +.b31-card-body { padding: 16px; } + +.b31-card.collapsed .b31-card-body { display: none; } +.b31-card.collapsed .b31-card-header { border-bottom: none; } + +/* ── Info grid (label-value pairs like ISPmanager system info) ── */ +.b31-info-grid { + display: grid; + grid-template-columns: 180px 1fr; + gap: 0; +} + +.b31-info-label, +.b31-info-value { + padding: 8px 12px; + font-size: 13px; + border-bottom: 1px solid var(--b31-border-light, #eef0f3); +} + +.b31-info-label { + color: var(--b31-muted, #8a8f9a); + font-weight: 500; +} + +.b31-info-value { + color: var(--b31-text, #333); +} + +.b31-info-value a { + color: var(--b31-primary, #2b579a); + text-decoration: none; +} +.b31-info-value a:hover { text-decoration: underline; } + +/* ── Table styles ── */ +.b31-table { + width: 100%; + border-collapse: collapse; + font-size: 13px; +} + +.b31-table th { + text-align: left; + padding: 8px 12px; + font-weight: 600; + color: var(--b31-muted, #8a8f9a); + font-size: 12px; + text-transform: uppercase; + letter-spacing: .04em; + border-bottom: 2px solid var(--b31-border, #dde1e6); + white-space: nowrap; +} + +.b31-table td { + padding: 8px 12px; + border-bottom: 1px solid var(--b31-border-light, #eef0f3); + color: var(--b31-text, #333); +} + +.b31-table tbody tr:hover { + background: var(--b31-hover, #f5f7fa); +} + +/* ── Quick action bar (like ISPmanager taskbar) ── */ +.b31-taskbar { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + background: var(--b31-card-bg, #fff); + border: 1px solid var(--b31-border, #dde1e6); + border-radius: 6px; + margin-bottom: 16px; +} + +.b31-taskbar-item { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + padding: 8px 16px; + border-radius: 6px; + cursor: pointer; + transition: all .12s; + text-decoration: none; + color: var(--b31-text, #333); + font-size: 12px; + background: none; + border: none; +} +.b31-taskbar-item:hover { background: var(--b31-hover, #f5f7fa); color: var(--b31-primary, #2b579a); } +.b31-taskbar-item .material-icons { font-size: 28px; color: var(--b31-primary, #2b579a); } + +/* ── Status indicators ── */ +.b31-status { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 12px; + font-weight: 500; +} + +.b31-status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + flex-shrink: 0; +} +.b31-status.online .b31-status-dot { background: #2ea44f; } +.b31-status.offline .b31-status-dot { background: #8b949e; } +.b31-status.error .b31-status-dot { background: #f85149; } +.b31-status.warning .b31-status-dot { background: #d29922; } + +/* ── Buttons ── */ +.b31-btn { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 14px; + font-size: 13px; + border-radius: 4px; + border: 1px solid var(--b31-border, #dde1e6); + background: var(--b31-card-bg, #fff); + color: var(--b31-text, #333); + cursor: pointer; + transition: all .12s; + font-weight: 500; +} +.b31-btn:hover { background: var(--b31-hover, #f5f7fa); border-color: var(--b31-primary, #2b579a); } +.b31-btn .material-icons { font-size: 16px; } + +.b31-btn-primary { + background: var(--b31-primary, #2b579a); + color: #fff; + border-color: var(--b31-primary, #2b579a); +} +.b31-btn-primary:hover { background: #1e4280; } + +/* ── Loading state ── */ +.b31-loading { + display: flex; + align-items: center; + justify-content: center; + padding: 60px; + color: var(--b31-muted, #8a8f9a); + font-size: 14px; + gap: 10px; +} + +.b31-loading .material-icons { + animation: b31spin 1s linear infinite; +} + +@keyframes b31spin { + to { transform: rotate(360deg); } +} + +/* ── Page header inside content area ── */ +.b31-page-header { + margin-bottom: 20px; +} + +.b31-page-title { + font-size: 20px; + font-weight: 700; + color: var(--b31-text, #333); + margin-bottom: 4px; +} + +.b31-page-subtitle { + font-size: 13px; + color: var(--b31-muted, #8a8f9a); +} + +/* ── Stats row ── */ +.b31-stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 12px; + margin-bottom: 16px; +} + +.b31-stat-card { + background: var(--b31-card-bg, #fff); + border: 1px solid var(--b31-border, #dde1e6); + border-radius: 6px; + padding: 16px; + display: flex; + align-items: center; + gap: 12px; +} + +.b31-stat-icon { + width: 40px; + height: 40px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} +.b31-stat-icon.blue { background: rgba(43,87,154,.1); color: #2b579a; } +.b31-stat-icon.green { background: rgba(46,164,79,.1); color: #2ea44f; } +.b31-stat-icon.red { background: rgba(248,81,73,.1); color: #f85149; } +.b31-stat-icon.yellow { background: rgba(210,153,34,.1); color: #d29922; } +.b31-stat-icon .material-icons { font-size: 22px; } + +.b31-stat-value { font-size: 22px; font-weight: 700; color: var(--b31-text, #333); } +.b31-stat-label { font-size: 12px; color: var(--b31-muted, #8a8f9a); } + +/* ═══════════════════════════════════════════════════════════════════ + DARK MODE overrides + ═══════════════════════════════════════════════════════════════════ */ + +.beta31-shell.b31-dark { + --b31-bg: #0d1117; + --b31-topbar-bg: #161b22; + --b31-sidebar-bg: #161b22; + --b31-card-bg: #1c2128; + --b31-border: #30363d; + --b31-border-light: #262c36; + --b31-text: #e6edf3; + --b31-muted: #8b949e; + --b31-hover: rgba(255,255,255,.04); + --b31-active-bg: rgba(88,166,255,.12); + --b31-primary: #58a6ff; +} + +/* ═══════════════════════════════════════════════════════════════════ + RESPONSIVE + ═══════════════════════════════════════════════════════════════════ */ + +@media (max-width: 768px) { + .b31-sidebar { width: 56px; min-width: 56px; } + .b31-sidebar-heading, + .b31-sidebar-item span:not(.material-icons), + .b31-sidebar-item-badge { display: none; } + .b31-sidebar-item { justify-content: center; padding: 10px 0; } + .b31-content { padding: 12px; } + .b31-topbar-tabs { display: none; } +} diff --git a/web-nodejs/public/js/beta31.js b/web-nodejs/public/js/beta31.js new file mode 100644 index 00000000..6af4b72a --- /dev/null +++ b/web-nodejs/public/js/beta31.js @@ -0,0 +1,298 @@ +/** + * BetterDesk Console — Beta 3.1 Panel Controller + * Manages the ISPmanager-inspired shell: sidebar navigation, content loading + */ + +(function () { + 'use strict'; + + const STORAGE_KEY = 'bd_beta31_active'; + const STORAGE_PANEL = 'bd_beta31_panel'; + let shell = null; + let contentArea = null; + let currentPanel = null; + let panelCache = {}; + + /* ────────────────────────────────────────────── + Menu definition — mirrors sidebar.ejs entries + ────────────────────────────────────────────── */ + function getMenuSections() { + const t = (k, fb) => (typeof _ === 'function' ? _(k) : fb) || fb; + return [ + { + heading: t('nav.main', 'Main'), + items: [ + { id: 'dashboard', icon: 'dashboard', label: t('nav.dashboard', 'Dashboard') }, + { id: 'devices', icon: 'devices', label: t('nav.devices', 'Devices') }, + { id: 'registrations', icon: 'how_to_reg', label: t('nav.registrations', 'Registrations') }, + ] + }, + { + heading: t('nav.management', 'Management'), + items: [ + { id: 'inventory', icon: 'inventory_2', label: t('nav.inventory', 'Inventory') }, + { id: 'tickets', icon: 'confirmation_number', label: t('nav.tickets', 'Tickets') }, + { id: 'help-requests', icon: 'support_agent', label: t('nav.help_requests', 'Help Requests') }, + { id: 'automation', icon: 'smart_toy', label: t('nav.automation', 'Automation') }, + { id: 'network', icon: 'lan', label: t('nav.network', 'Network') }, + { id: 'activity', icon: 'timeline', label: t('nav.activity', 'Activity') }, + { id: 'cdap', icon: 'developer_board', label: t('nav.cdap', 'CDAP') }, + { id: 'fleet', icon: 'hub', label: t('nav.fleet', 'Fleet') }, + { id: 'scaling', icon: 'cell_tower', label: t('nav.scaling', 'Scaling') }, + { id: 'cross-platform', icon: 'devices', label: t('nav.cross_platform', 'Cross-Platform') }, + { id: 'chat', icon: 'chat', label: t('nav.chat', 'Chat') }, + ] + }, + { + heading: t('nav.tools', 'Tools'), + items: [ + { id: 'reports', icon: 'assessment', label: t('nav.reports', 'Reports') }, + { id: 'keys', icon: 'vpn_key', label: t('nav.keys', 'Keys') }, + { id: 'generator', icon: 'build', label: t('nav.generator', 'Generator') }, + { id: 'remote', icon: 'connected_tv', label: t('nav.remote', 'Remote Desktop') }, + { id: 'toolkit', icon: 'handyman', label: t('nav.toolkit', 'Toolkit') }, + { id: 'cdap-studio', icon: 'account_tree', label: t('nav.sdk_studio', 'SDK Studio') }, + ] + }, + { + heading: t('nav.system', 'System'), + items: [ + { id: 'tokens', icon: 'token', label: t('nav.tokens', 'Tokens') }, + { id: 'organizations', icon: 'corporate_fare', label: t('nav.organizations', 'Organizations') }, + { id: 'policies', icon: 'policy', label: t('nav.policies', 'Policies') }, + { id: 'attestation', icon: 'fingerprint', label: t('nav.attestation', 'Attestation') }, + { id: 'dataguard', icon: 'shield', label: t('nav.dataguard', 'DataGuard') }, + { id: 'users', icon: 'group', label: t('nav.users', 'Users') }, + { id: 'security-audit', icon: 'security', label: t('nav.security_audit', 'Security Audit') }, + { id: 'permissions', icon: 'admin_panel_settings', label: t('nav.permissions', 'Permissions') }, + { id: 'languages', icon: 'translate', label: t('nav.languages', 'Languages') }, + { id: 'resource-control', icon: 'tune', label: t('nav.resource_control', 'Resource Control') }, + { id: 'settings', icon: 'settings', label: t('nav.settings', 'Settings') }, + ] + } + ]; + } + + /* ────────────────────────────────────────────── + Build the shell DOM + ────────────────────────────────────────────── */ + function buildShell() { + if (document.getElementById('beta31-shell')) return; + + const appName = (window.BetterDesk && window.BetterDesk.branding && window.BetterDesk.branding.appName) || 'BetterDesk'; + const isDark = document.documentElement.getAttribute('data-theme') === 'dark'; + + shell = document.createElement('div'); + shell.id = 'beta31-shell'; + shell.className = 'beta31-shell' + (isDark ? ' b31-dark' : ''); + + // Build topbar + const topbar = document.createElement('div'); + topbar.className = 'b31-topbar'; + topbar.innerHTML = ` +
+ + + `; + + // Build body (sidebar + content) + const body = document.createElement('div'); + body.className = 'b31-body'; + + // Sidebar + const sidebar = document.createElement('nav'); + sidebar.className = 'b31-sidebar'; + sidebar.id = 'b31-sidebar'; + + const sections = getMenuSections(); + sections.forEach(sec => { + const div = document.createElement('div'); + div.className = 'b31-sidebar-section'; + div.innerHTML = ``; + sec.items.forEach(item => { + const btn = document.createElement('button'); + btn.className = 'b31-sidebar-item'; + btn.dataset.panel = item.id; + btn.innerHTML = `${escHtml(item.label)}`; + btn.addEventListener('click', () => navigateTo(item.id)); + div.appendChild(btn); + }); + sidebar.appendChild(div); + }); + + // Content + contentArea = document.createElement('div'); + contentArea.className = 'b31-content'; + contentArea.id = 'b31-content'; + + body.appendChild(sidebar); + body.appendChild(contentArea); + + shell.appendChild(topbar); + shell.appendChild(body); + document.body.appendChild(shell); + + // Event listeners + document.getElementById('b31-exit-btn').addEventListener('click', deactivate); + document.getElementById('b31-refresh-btn').addEventListener('click', () => { + if (currentPanel) { panelCache[currentPanel] = null; navigateTo(currentPanel); } + }); + document.getElementById('b31-theme-btn').addEventListener('click', toggleTheme); + } + + /* ────────────────────────────────────────────── + Navigation — load panel content via fetch + ────────────────────────────────────────────── */ + function navigateTo(panelId) { + if (!contentArea) return; + currentPanel = panelId; + localStorage.setItem(STORAGE_PANEL, panelId); + + // Update sidebar active state + shell.querySelectorAll('.b31-sidebar-item').forEach(el => { + el.classList.toggle('active', el.dataset.panel === panelId); + }); + + // Show loading + contentArea.innerHTML = 'Failed to load panel: ${escHtml(err.message)}
+ +