mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-10 09:35:39 +00:00
Gitleaks fix
This commit is contained in:
@@ -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$''',
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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; }
|
||||
}
|
||||
@@ -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 = `
|
||||
<div class="b31-topbar-logo">
|
||||
<span class="material-icons">dns</span>
|
||||
<span>${escHtml(appName)}</span>
|
||||
<span class="b31-topbar-badge">BETA 3.1</span>
|
||||
</div>
|
||||
<div class="b31-topbar-spacer"></div>
|
||||
<div class="b31-topbar-actions">
|
||||
<button class="b31-topbar-btn" id="b31-refresh-btn" title="Refresh">
|
||||
<span class="material-icons">refresh</span>
|
||||
</button>
|
||||
<button class="b31-topbar-btn" id="b31-theme-btn" title="Toggle theme">
|
||||
<span class="material-icons">${isDark ? 'light_mode' : 'dark_mode'}</span>
|
||||
</button>
|
||||
<button class="b31-topbar-btn" id="b31-exit-btn" title="Exit Beta 3.1">
|
||||
<span class="material-icons">close</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 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 = `<div class="b31-sidebar-heading">${escHtml(sec.heading)}</div>`;
|
||||
sec.items.forEach(item => {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'b31-sidebar-item';
|
||||
btn.dataset.panel = item.id;
|
||||
btn.innerHTML = `<span class="material-icons">${item.icon}</span><span>${escHtml(item.label)}</span>`;
|
||||
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 = '<div class="b31-loading"><span class="material-icons">sync</span> Loading…</div>';
|
||||
|
||||
// Use cache if available
|
||||
if (panelCache[panelId]) {
|
||||
renderPanel(panelId, panelCache[panelId]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch panel content — we load the actual page and extract main-content
|
||||
fetch('/' + (panelId === 'dashboard' ? '' : panelId), {
|
||||
headers: { 'X-Beta31': '1', 'X-Requested-With': 'XMLHttpRequest' },
|
||||
credentials: 'same-origin'
|
||||
})
|
||||
.then(resp => {
|
||||
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
||||
return resp.text();
|
||||
})
|
||||
.then(html => {
|
||||
// Extract inner content from the page
|
||||
const extracted = extractMainContent(html);
|
||||
panelCache[panelId] = extracted;
|
||||
// Only render if still on same panel
|
||||
if (currentPanel === panelId) renderPanel(panelId, extracted);
|
||||
})
|
||||
.catch(err => {
|
||||
if (currentPanel === panelId) {
|
||||
contentArea.innerHTML = `<div class="b31-card"><div class="b31-card-body">
|
||||
<p style="color:var(--b31-muted)">Failed to load panel: ${escHtml(err.message)}</p>
|
||||
<button class="b31-btn" onclick="document.getElementById('b31-refresh-btn').click()">
|
||||
<span class="material-icons">refresh</span> Retry
|
||||
</button>
|
||||
</div></div>`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function extractMainContent(html) {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, 'text/html');
|
||||
const main = doc.querySelector('.main-content');
|
||||
if (main) return main.innerHTML;
|
||||
// Fallback: body content
|
||||
return doc.body ? doc.body.innerHTML : html;
|
||||
}
|
||||
|
||||
function renderPanel(panelId, html) {
|
||||
contentArea.innerHTML = html;
|
||||
|
||||
// Execute inline scripts
|
||||
contentArea.querySelectorAll('script').forEach(oldScript => {
|
||||
const newScript = document.createElement('script');
|
||||
if (oldScript.src) {
|
||||
newScript.src = oldScript.src;
|
||||
} else {
|
||||
newScript.textContent = oldScript.textContent;
|
||||
}
|
||||
oldScript.parentNode.replaceChild(newScript, oldScript);
|
||||
});
|
||||
|
||||
// Trigger page-specific init
|
||||
window.dispatchEvent(new CustomEvent('app:refresh'));
|
||||
}
|
||||
|
||||
/* ──────────────────────────────────────────────
|
||||
Activate / Deactivate
|
||||
────────────────────────────────────────────── */
|
||||
function activate() {
|
||||
buildShell();
|
||||
shell.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
localStorage.setItem(STORAGE_KEY, '1');
|
||||
|
||||
// Navigate to saved or default panel
|
||||
const saved = localStorage.getItem(STORAGE_PANEL) || 'dashboard';
|
||||
navigateTo(saved);
|
||||
}
|
||||
|
||||
function deactivate() {
|
||||
if (shell) shell.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
localStorage.setItem(STORAGE_KEY, '0');
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
if (!shell) return;
|
||||
shell.classList.toggle('b31-dark');
|
||||
const icon = document.querySelector('#b31-theme-btn .material-icons');
|
||||
if (icon) icon.textContent = shell.classList.contains('b31-dark') ? 'light_mode' : 'dark_mode';
|
||||
}
|
||||
|
||||
/* ──────────────────────────────────────────────
|
||||
Helpers
|
||||
────────────────────────────────────────────── */
|
||||
function escHtml(s) {
|
||||
const d = document.createElement('div');
|
||||
d.textContent = s || '';
|
||||
return d.innerHTML;
|
||||
}
|
||||
|
||||
/* ──────────────────────────────────────────────
|
||||
Public API
|
||||
────────────────────────────────────────────── */
|
||||
window.Beta31 = {
|
||||
activate: activate,
|
||||
deactivate: deactivate,
|
||||
navigateTo: navigateTo,
|
||||
isActive: () => shell && shell.classList.contains('active'),
|
||||
init: function () {
|
||||
// Wire up the toggle button in the navbar
|
||||
const btn = document.getElementById('beta31-toggle-btn');
|
||||
if (btn) btn.addEventListener('click', () => {
|
||||
if (window.Beta31.isActive()) deactivate(); else activate();
|
||||
});
|
||||
|
||||
// Auto-restore if was active
|
||||
if (localStorage.getItem(STORAGE_KEY) === '1') {
|
||||
activate();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Auto-init on DOM ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => window.Beta31.init());
|
||||
} else {
|
||||
window.Beta31.init();
|
||||
}
|
||||
})();
|
||||
@@ -19,6 +19,7 @@
|
||||
<link rel="stylesheet" href="/css/desktop-mode.css?v=<%= cacheVersion %>">
|
||||
<link rel="stylesheet" href="/css/desktop-widgets.css?v=<%= cacheVersion %>">
|
||||
<link rel="stylesheet" href="/css/tutorial.css?v=<%= cacheVersion %>">
|
||||
<link rel="stylesheet" href="/css/beta31.css?v=<%= cacheVersion %>">
|
||||
<% } else { %>
|
||||
<link rel="stylesheet" href="/css/desktop-widget-overrides.css?v=<%= cacheVersion %>">
|
||||
<% } %>
|
||||
@@ -149,6 +150,7 @@
|
||||
<script src="/js/desktop-widgets.js?v=<%= cacheVersion %>"></script>
|
||||
<script src="/js/desktop-mode.js?v=<%= cacheVersion %>"></script>
|
||||
<script src="/js/tutorial.js?v=<%= cacheVersion %>"></script>
|
||||
<script src="/js/beta31.js?v=<%= cacheVersion %>"></script>
|
||||
<script nonce="<%= cspNonce %>">if (typeof Tutorial !== 'undefined') Tutorial.autoStart('console');</script>
|
||||
<% } %>
|
||||
<% if (typeof pageScripts !== 'undefined' && pageScripts.length) { %>
|
||||
|
||||
@@ -50,6 +50,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Beta 3.1 Panel toggle -->
|
||||
<button class="navbar-btn beta31-toggle-btn" id="beta31-toggle-btn" title="Beta 3.1 Console">
|
||||
<span class="material-icons">view_sidebar</span>
|
||||
<span class="beta-badge">3.1</span>
|
||||
</button>
|
||||
|
||||
<!-- Desktop mode toggle (Beta) - visible only on large screens -->
|
||||
<button class="navbar-btn desktop-toggle-btn" id="desktop-toggle-btn" title="<%= _('desktop.switch_mode') %> (Beta)">
|
||||
<span class="material-icons">desktop_windows</span>
|
||||
|
||||
Reference in New Issue
Block a user