Files
UNITRONIX b73648651d feat(console): add Client Branding for desktop remotes
Admin page and Go /api/branding schema v1 with logo/contact fields,
RustDesk-safe heartbeat projections, i18n, and handler tests.

Thanks: INSOLVE (Honorary); Marco Jakobs (@jacotec); MyNameisStitch (@MyNameisStitch); Redspin (@playerumpknow)
2026-09-06 12:26:39 +02:00

463 lines
26 KiB
HTML

<% const sidebarServerTab = typeof currentTab !== 'undefined' && currentTab ? currentTab : 'overview'; %>
<% const sidebarCommercialTab = typeof currentTab !== 'undefined' && currentTab ? currentTab : 'overview'; %>
<!-- Sidebar Navigation — TeamViewer-style icon rail + flyout -->
<aside class="sidebar" id="sidebar" data-collapsed="true">
<!-- Icon rail (always visible) -->
<div class="sidebar-rail">
<!-- Logo -->
<div class="sidebar-rail-logo">
<a href="/" class="sidebar-logo" title="<%= appName %>">
<% if (branding.logoType === 'text' && branding.logoText) { %>
<span class="brand-text-logo"><%= branding.logoText %><% if (branding.logoTextAccent) { %><span class="brand-text-accent"><%= branding.logoTextAccent %></span><% } %></span>
<% } else if (branding.logoType === 'svg' && branding.logoSvg) { %>
<span class="sidebar-logo-svg"><%- branding.logoSvg %></span>
<% } else if (branding.logoType === 'image' && branding.logoUrl) { %>
<img src="<%= branding.logoUrl %>" alt="<%= appName %>" class="sidebar-logo-img">
<% } else { %>
<span class="material-icons"><%= branding.logoIcon || 'dns' %></span>
<% } %>
</a>
</div>
<!-- Category icons -->
<nav class="sidebar-rail-nav">
<button class="sidebar-rail-btn <%= ['dashboard','devices','registrations','client-branding'].includes(currentPage) ? 'active' : '' %>"
data-category="main" title="<%= _('nav.main') %>">
<span class="material-icons">home</span>
</button>
<button class="sidebar-rail-btn <%= ['inventory','tickets','help-requests','automation','network','activity','cdap','fleet','scaling','cross-platform','chat'].includes(currentPage) ? 'active' : '' %>"
data-category="management" title="<%= _('nav.management') || 'Management' %>">
<span class="material-icons">business_center</span>
</button>
<button class="sidebar-rail-btn <%= ['reports','keys','generator','remote','toolkit','cdap-studio'].includes(currentPage) ? 'active' : '' %>"
data-category="tools" title="<%= _('nav.tools') %>">
<span class="material-icons">build</span>
</button>
<% if (isSuperAdmin || hasPermission('server.config') || hasPermission('user.view') || hasPermission('enrollment.manage') || hasPermission('enrollment.approve') || hasPermission('audit.view')) { %>
<button class="sidebar-rail-btn <%= ['tokens','organizations','policies','attestation','dataguard','users','security-audit','permissions','languages','resource-control'].includes(currentPage) ? 'active' : '' %>"
data-category="system" title="<%= _('nav.system') %>">
<span class="material-icons">admin_panel_settings</span>
</button>
<% } %>
<% if (hasPermission('server.config')) { %>
<button class="sidebar-rail-btn <%= currentPage === 'server-management' ? 'active' : '' %>"
data-category="server-mgmt" title="<%= _('nav.server_management') || 'Server Management' %>">
<span class="material-icons">dns</span>
</button>
<% } %>
<% if (hasPermission('billing.view')) { %>
<button class="sidebar-rail-btn sidebar-rail-btn--alpha <%= currentPage === 'commercialization' ? 'active' : '' %>"
data-category="commercialization" title="<%= _('commercialization.alpha_tooltip') || _('nav.commercialization') || 'Commercialization' %>">
<span class="material-icons">payments</span>
<span class="rail-stage-badge" aria-hidden="true"><%= _('commercialization.alpha_badge') || 'ALPHA' %></span>
</button>
<% } %>
<% if (hasPermission('server.attestation')) { %>
<a href="/server-attestation" class="sidebar-rail-btn <%= currentPage === 'server-attestation' ? 'active' : '' %>"
title="<%= _('nav.server_attestation') || 'Server Attestation' %>">
<span class="material-icons">verified</span>
</a>
<% } %>
<a href="/settings" class="sidebar-rail-btn <%= currentPage === 'settings' ? 'active' : '' %>"
title="<%= _('nav.settings') %>">
<span class="material-icons">settings</span>
</a>
</nav>
<!-- Bottom rail icons -->
<div class="sidebar-rail-bottom">
<button class="sidebar-rail-btn" id="sidebar-help-btn" title="<%= _('nav.help') || 'Help' %>">
<span class="material-icons">help_outline</span>
</button>
<button class="sidebar-rail-btn" id="sidebar-expand-btn" title="<%= _('nav.expand_sidebar') || 'Expand' %>">
<span class="material-icons">chevron_right</span>
</button>
</div>
</div>
<!-- Flyout panel (shows on category click/hover) -->
<div class="sidebar-flyout" id="sidebar-flyout">
<div class="sidebar-flyout-header">
<span class="sidebar-flyout-title" id="sidebar-flyout-title"></span>
</div>
<!-- Commercialization category links -->
<div class="sidebar-flyout-panel" data-panel="commercialization">
<% if (hasPermission('billing.view')) { %>
<a href="/commercialization?tab=overview" class="sidebar-link <%= currentPage === 'commercialization' && sidebarCommercialTab === 'overview' ? 'active' : '' %>">
<span class="material-icons">dashboard</span>
<span class="sidebar-link-text"><%= _('commercialization.tabs.overview') %></span>
</a>
<a href="/commercialization?tab=packages" class="sidebar-link <%= currentPage === 'commercialization' && sidebarCommercialTab === 'packages' ? 'active' : '' %>">
<span class="material-icons">inventory_2</span>
<span class="sidebar-link-text"><%= _('commercialization.tabs.packages') %></span>
</a>
<a href="/commercialization?tab=sessions" class="sidebar-link <%= currentPage === 'commercialization' && sidebarCommercialTab === 'sessions' ? 'active' : '' %>">
<span class="material-icons">schedule</span>
<span class="sidebar-link-text"><%= _('commercialization.tabs.sessions') %></span>
</a>
<a href="/commercialization?tab=reports" class="sidebar-link <%= currentPage === 'commercialization' && sidebarCommercialTab === 'reports' ? 'active' : '' %>">
<span class="material-icons">description</span>
<span class="sidebar-link-text"><%= _('commercialization.tabs.reports') %></span>
</a>
<a href="/commercialization?tab=settings" class="sidebar-link <%= currentPage === 'commercialization' && sidebarCommercialTab === 'settings' ? 'active' : '' %>">
<span class="material-icons">tune</span>
<span class="sidebar-link-text"><%= _('commercialization.tabs.settings') %></span>
</a>
<% } %>
</div>
<!-- Main category links -->
<div class="sidebar-flyout-panel" data-panel="main">
<a href="/" class="sidebar-link <%= currentPage === 'dashboard' ? 'active' : '' %>">
<span class="material-icons">dashboard</span>
<span class="sidebar-link-text"><%= _('nav.dashboard') %></span>
</a>
<% if (hasPermission('device.view')) { %>
<a href="/devices" class="sidebar-link <%= currentPage === 'devices' ? 'active' : '' %>">
<span class="material-icons">devices</span>
<span class="sidebar-link-text"><%= _('nav.devices') %></span>
</a>
<% } %>
<% if (hasPermission('enrollment.approve')) { %>
<a href="/registrations" class="sidebar-link <%= currentPage === 'registrations' ? 'active' : '' %>">
<span class="material-icons">how_to_reg</span>
<span class="sidebar-link-text"><%= _('nav.registrations') %></span>
<span class="badge-sidebar" id="reg-sidebar-badge" style="display: none;">0</span>
</a>
<% } %>
<% if (hasPermission('branding.edit')) { %>
<a href="/client-branding" class="sidebar-link <%= currentPage === 'client-branding' ? 'active' : '' %>">
<span class="material-icons">branding_watermark</span>
<span class="sidebar-link-text"><%= _('nav.client_branding') %></span>
</a>
<% } %>
</div>
<!-- Management category links -->
<div class="sidebar-flyout-panel" data-panel="management">
<% if (hasPermission('device.view')) { %>
<a href="/inventory" class="sidebar-link <%= currentPage === 'inventory' ? 'active' : '' %>">
<span class="material-icons">inventory_2</span>
<span class="sidebar-link-text"><%= _('nav.inventory') %></span>
</a>
<a href="/tickets" class="sidebar-link <%= currentPage === 'tickets' ? 'active' : '' %>">
<span class="material-icons">confirmation_number</span>
<span class="sidebar-link-text"><%= _('nav.tickets') %></span>
</a>
<a href="/help-requests" class="sidebar-link <%= currentPage === 'help-requests' ? 'active' : '' %>">
<span class="material-icons">support_agent</span>
<span class="sidebar-link-text"><%= _('nav.help_requests') %></span>
</a>
<% } %>
<% if (hasPermission('server.config')) { %>
<a href="/automation" class="sidebar-link <%= currentPage === 'automation' ? 'active' : '' %>">
<span class="material-icons">smart_toy</span>
<span class="sidebar-link-text"><%= _('nav.automation') %></span>
</a>
<% } %>
<% if (hasPermission('device.view')) { %>
<a href="/network" class="sidebar-link <%= currentPage === 'network' ? 'active' : '' %>">
<span class="material-icons">lan</span>
<span class="sidebar-link-text"><%= _('nav.network') %></span>
</a>
<% } %>
<% if (hasPermission('audit.view')) { %>
<a href="/activity" class="sidebar-link <%= currentPage === 'activity' ? 'active' : '' %>">
<span class="material-icons">timeline</span>
<span class="sidebar-link-text"><%= _('nav.activity') %></span>
</a>
<% } %>
<% if (hasPermission('cdap.view')) { %>
<a href="/cdap" class="sidebar-link <%= currentPage === 'cdap' ? 'active' : '' %>">
<span class="material-icons">developer_board</span>
<span class="sidebar-link-text"><%= _('nav.cdap') %></span>
</a>
<% } %>
<% if (hasPermission('org.manage_devices')) { %>
<a href="/fleet" class="sidebar-link <%= currentPage === 'fleet' ? 'active' : '' %>">
<span class="material-icons">hub</span>
<span class="sidebar-link-text"><%= _('nav.fleet') || 'Fleet' %></span>
</a>
<% } %>
<% if (hasPermission('server.config')) { %>
<a href="/scaling" class="sidebar-link <%= currentPage === 'scaling' ? 'active' : '' %>">
<span class="material-icons">cell_tower</span>
<span class="sidebar-link-text"><%= _('nav.scaling') || 'Scaling' %></span>
</a>
<% } %>
<% if (hasPermission('device.view')) { %>
<a href="/cross-platform" class="sidebar-link <%= currentPage === 'cross-platform' ? 'active' : '' %>">
<span class="material-icons">devices</span>
<span class="sidebar-link-text"><%= _('nav.cross_platform') || 'Cross-Platform' %></span>
</a>
<% } %>
<a href="/chat" class="sidebar-link <%= currentPage === 'chat' ? 'active' : '' %>">
<span class="material-icons">chat</span>
<span class="sidebar-link-text"><%= _('nav.chat') %></span>
</a>
</div>
<!-- Tools category links -->
<div class="sidebar-flyout-panel" data-panel="tools">
<% if (hasPermission('audit.view')) { %>
<a href="/reports" class="sidebar-link <%= currentPage === 'reports' ? 'active' : '' %>">
<span class="material-icons">assessment</span>
<span class="sidebar-link-text"><%= _('nav.reports') %></span>
</a>
<% } %>
<% if (hasPermission('server.config')) { %>
<a href="/keys" class="sidebar-link <%= currentPage === 'keys' ? 'active' : '' %>">
<span class="material-icons">vpn_key</span>
<span class="sidebar-link-text"><%= _('nav.keys') %></span>
</a>
<a href="/generator" class="sidebar-link <%= currentPage === 'generator' ? 'active' : '' %>" title="<%= _('generator.alpha_tooltip') || _('nav.generator') %>">
<span class="material-icons">build</span>
<span class="sidebar-link-text"><%= _('nav.generator') %></span>
<span class="badge-sidebar badge-sidebar--alpha"><%= _('generator.alpha_badge') || 'ALPHA' %></span>
</a>
<% } %>
<% if (hasPermission('device.connect')) { %>
<a href="/remote" class="sidebar-link <%= currentPage === 'remote' ? 'active' : '' %>" target="_blank" rel="noopener noreferrer" title="<%= _('nav.remote_open_tab') || _('nav.remote') %>">
<span class="material-icons">connected_tv</span>
<span class="sidebar-link-text"><%= _('nav.remote') || 'Remote Desktop' %></span>
<span class="material-icons sidebar-link-external" aria-hidden="true">open_in_new</span>
</a>
<a href="/toolkit" class="sidebar-link <%= currentPage === 'toolkit' ? 'active' : '' %>">
<span class="material-icons">handyman</span>
<span class="sidebar-link-text"><%= _('nav.toolkit') || 'Toolkit' %></span>
</a>
<% } %>
<% if (hasPermission('cdap.view')) { %>
<a href="/cdap-studio" class="sidebar-link <%= currentPage === 'cdap-studio' ? 'active' : '' %>">
<span class="material-icons">account_tree</span>
<span class="sidebar-link-text"><%= _('nav.sdk_studio') || 'SDK Studio' %></span>
</a>
<% } %>
</div>
<!-- System category links -->
<div class="sidebar-flyout-panel" data-panel="system">
<% if (hasPermission('enrollment.manage')) { %>
<a href="/tokens" class="sidebar-link <%= currentPage === 'tokens' ? 'active' : '' %>">
<span class="material-icons">token</span>
<span class="sidebar-link-text"><%= _('nav.tokens') %></span>
</a>
<% } %>
<% if (hasPermission('org.create') || hasPermission('org.edit') || hasPermission('org.manage_users')) { %>
<a href="/organizations" class="sidebar-link <%= currentPage === 'organizations' ? 'active' : '' %>">
<span class="material-icons">corporate_fare</span>
<span class="sidebar-link-text"><%= _('nav.organizations') %></span>
</a>
<% } %>
<% if (hasPermission('server.config')) { %>
<a href="/policies" class="sidebar-link <%= currentPage === 'policies' ? 'active' : '' %>">
<span class="material-icons">policy</span>
<span class="sidebar-link-text"><%= _('nav.policies') || 'Policies' %></span>
</a>
<a href="/attestation" class="sidebar-link <%= currentPage === 'attestation' ? 'active' : '' %>">
<span class="material-icons">fingerprint</span>
<span class="sidebar-link-text"><%= _('nav.attestation') || 'Attestation' %></span>
</a>
<a href="/dataguard" class="sidebar-link <%= currentPage === 'dataguard' ? 'active' : '' %>">
<span class="material-icons">shield</span>
<span class="sidebar-link-text"><%= _('nav.dataguard') %></span>
</a>
<% } %>
<% if (hasPermission('user.view')) { %>
<a href="/users" class="sidebar-link <%= currentPage === 'users' ? 'active' : '' %>">
<span class="material-icons">group</span>
<span class="sidebar-link-text"><%= _('nav.users') %></span>
</a>
<% } %>
<% if (hasPermission('audit.view')) { %>
<a href="/security-audit" class="sidebar-link <%= currentPage === 'security-audit' ? 'active' : '' %>">
<span class="material-icons">security</span>
<span class="sidebar-link-text"><%= _('nav.security_audit') %></span>
</a>
<% } %>
<% if (isSuperAdmin) { %>
<a href="/permissions" class="sidebar-link <%= currentPage === 'permissions' ? 'active' : '' %>">
<span class="material-icons">admin_panel_settings</span>
<span class="sidebar-link-text"><%= _('nav.permissions') %></span>
</a>
<% } %>
<% if (hasPermission('server.config')) { %>
<a href="/languages" class="sidebar-link <%= currentPage === 'languages' ? 'active' : '' %>">
<span class="material-icons">translate</span>
<span class="sidebar-link-text"><%= _('nav.languages') %></span>
</a>
<a href="/resource-control" class="sidebar-link <%= currentPage === 'resource-control' ? 'active' : '' %>">
<span class="material-icons">tune</span>
<span class="sidebar-link-text"><%= _('nav.resource_control') %></span>
</a>
<% } %>
</div>
<!-- Server Management category links (BETA) -->
<% if (hasPermission('server.config')) { %>
<div class="sidebar-flyout-panel" data-panel="server-mgmt">
<a href="/server-management?tab=overview" class="sidebar-link <%= currentPage === 'server-management' && sidebarServerTab === 'overview' ? 'active' : '' %>">
<span class="material-icons">monitoring</span>
<span class="sidebar-link-text"><%= _('server_mgmt.tab_overview') || 'Overview' %></span>
</a>
<a href="/server-management?tab=terminal" class="sidebar-link <%= currentPage === 'server-management' && sidebarServerTab === 'terminal' ? 'active' : '' %>">
<span class="material-icons">terminal</span>
<span class="sidebar-link-text"><%= _('server_mgmt.tab_terminal') || 'Terminal' %></span>
</a>
<a href="/server-management?tab=files" class="sidebar-link <%= currentPage === 'server-management' && sidebarServerTab === 'files' ? 'active' : '' %>">
<span class="material-icons">folder_open</span>
<span class="sidebar-link-text"><%= _('server_mgmt.tab_files') || 'Files' %></span>
</a>
<a href="/server-management?tab=services" class="sidebar-link <%= currentPage === 'server-management' && sidebarServerTab === 'services' ? 'active' : '' %>">
<span class="material-icons">miscellaneous_services</span>
<span class="sidebar-link-text"><%= _('server_mgmt.tab_services') || 'Services' %></span>
<span class="badge-sidebar" style="background:linear-gradient(135deg,#ff7b72,#d29922);color:#fff;flex-shrink:0;margin-left:auto;">BETA</span>
</a>
</div>
<% } %>
<!-- Sidebar footer - User -->
<div class="sidebar-footer">
<div class="sidebar-user" id="user-menu-trigger">
<div class="sidebar-user-avatar">
<%= user && user.username ? user.username.charAt(0).toUpperCase() : 'A' %>
</div>
<div class="sidebar-user-info">
<div class="sidebar-user-name"><%= user ? user.username : 'Admin' %></div>
<%
var _roleKey = user && user.role ? ('users.role_' + user.role) : 'users.role_admin';
var _roleLabel = _(_roleKey);
if (!_roleLabel || _roleLabel === _roleKey) {
_roleLabel = user && user.role
? user.role.replace(/_/g, ' ').replace(/\b\w/g, function(c){ return c.toUpperCase(); })
: 'Administrator';
}
%>
<div class="sidebar-user-role"><%= _roleLabel %></div>
</div>
</div>
<% if ((branding && branding.footerText) || (branding && branding.showPoweredBy !== 'false')) { %>
<div class="sidebar-attribution">
<% if (branding.footerText) { %><span class="sidebar-attribution-text"><%= branding.footerText %></span><% } %>
<% if (branding.showPoweredBy !== 'false') { %><span class="sidebar-attribution-powered"><%= _('branding.powered_by') %> BetterDesk</span><% } %>
</div>
<% } %>
</div>
</div>
</aside>
<script nonce="<%= cspNonce %>">
// Sidebar rail navigation + flyout logic
(function() {
var sidebar = document.getElementById('sidebar');
var flyout = document.getElementById('sidebar-flyout');
var flyoutTitle = document.getElementById('sidebar-flyout-title');
var expandBtn = document.getElementById('sidebar-expand-btn');
var nav = flyout;
var STORAGE_COLLAPSED = 'bd_sidebar_collapsed';
var STORAGE_SCROLL = 'bd_sidebar_scroll';
var activeCategory = null;
// Determine initial state
var savedCollapsed = localStorage.getItem(STORAGE_COLLAPSED);
var isCollapsed = savedCollapsed === null ? true : savedCollapsed === 'true';
sidebar.dataset.collapsed = String(isCollapsed);
document.documentElement.style.setProperty('--sidebar-current-width',
isCollapsed ? 'var(--sidebar-width-collapsed)' : 'var(--sidebar-width)');
// Auto-show the panel for the current page's category
var activeRail = sidebar.querySelector('.sidebar-rail-btn.active[data-category]');
if (activeRail) {
activeCategory = activeRail.dataset.category;
showPanel(activeCategory);
} else if (['settings', 'server-attestation'].includes('<%= currentPage %>')) {
// Direct rail links — no flyout sub-menu, keep sidebar collapsed
isCollapsed = true;
sidebar.dataset.collapsed = 'true';
document.documentElement.style.setProperty('--sidebar-current-width', 'var(--sidebar-width-collapsed)');
if (flyout) delete flyout.dataset.activeCategory;
}
function showPanel(category) {
// Hide all panels
flyout.querySelectorAll('.sidebar-flyout-panel').forEach(function(p) {
p.classList.remove('visible');
});
// Show target
var panel = flyout.querySelector('[data-panel="' + category + '"]');
if (panel) panel.classList.add('visible');
// Update rail active state
sidebar.querySelectorAll('.sidebar-rail-btn[data-category]').forEach(function(b) {
b.classList.toggle('selected', b.dataset.category === category);
});
// Title mapping
var titles = {
commercialization: '<%= _("nav.commercialization") || "Commercialization" %>',
main: '<%= _("nav.main") %>',
management: '<%= _("nav.management") || "Management" %>',
tools: '<%= _("nav.tools") %>',
system: '<%= _("nav.system") %>',
'server-mgmt': '<%= _("nav.server_management") || "Server Management" %>'
};
if (flyoutTitle) flyoutTitle.textContent = titles[category] || '';
if (flyout) flyout.dataset.activeCategory = category;
activeCategory = category;
}
// Rail button clicks
sidebar.querySelectorAll('.sidebar-rail-btn[data-category]').forEach(function(btn) {
btn.addEventListener('click', function() {
var cat = btn.dataset.category;
if (!isCollapsed && activeCategory === cat) {
toggleCollapse();
} else {
showPanel(cat);
if (isCollapsed) toggleCollapse();
}
});
});
// Expand/collapse toggle
function toggleCollapse() {
isCollapsed = !isCollapsed;
sidebar.dataset.collapsed = String(isCollapsed);
document.documentElement.style.setProperty('--sidebar-current-width',
isCollapsed ? 'var(--sidebar-width-collapsed)' : 'var(--sidebar-width)');
localStorage.setItem(STORAGE_COLLAPSED, String(isCollapsed));
if (expandBtn) {
expandBtn.querySelector('.material-icons').textContent =
isCollapsed ? 'chevron_right' : 'chevron_left';
}
}
if (expandBtn) {
expandBtn.addEventListener('click', toggleCollapse);
expandBtn.querySelector('.material-icons').textContent =
isCollapsed ? 'chevron_right' : 'chevron_left';
}
// Scroll preservation
if (nav) {
try {
var saved = sessionStorage.getItem(STORAGE_SCROLL);
if (saved) nav.scrollTop = parseInt(saved, 10) || 0;
} catch(e) {}
var activeLink = nav.querySelector('.sidebar-link.active');
if (activeLink) {
var rect = activeLink.getBoundingClientRect();
var navRect = nav.getBoundingClientRect();
if (rect.top < navRect.top || rect.bottom > navRect.bottom) {
activeLink.scrollIntoView({ block: 'center' });
}
}
window.addEventListener('beforeunload', function() {
try { sessionStorage.setItem(STORAGE_SCROLL, String(nav.scrollTop)); } catch(e) {}
});
}
// Theme follows branding / data-theme-mode (do not force dark)
})();
</script>