mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-10 01:27:11 +00:00
cbf72ca1e3
- Added UX 3.5 as the default console shell, featuring a full-list sidebar, topbar, and customizable themes (Dark/Light/Custom). - Updated documentation to reflect the new UX 3.5 shell. - Removed legacy Desktop Mode and Beta 3.1 styles, ensuring a streamlined user experience. - Enhanced language support for new theme options across multiple languages. - Fixed OIDC client login issues by proxying callback URLs to the Go API. This update improves the overall user interface and experience, aligning with modern design standards.
166 lines
5.1 KiB
JavaScript
166 lines
5.1 KiB
JavaScript
/**
|
|
* BetterDesk Console - Main Application
|
|
*/
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
// Initialize when DOM is ready
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
|
|
function init() {
|
|
initNavbar();
|
|
initLanguageSelector();
|
|
initUserMenu();
|
|
initRefreshButton();
|
|
initRegistrationBadge();
|
|
}
|
|
|
|
/**
|
|
* Navbar functionality
|
|
*/
|
|
function initNavbar() {
|
|
// Dropdowns
|
|
document.querySelectorAll('.lang-selector').forEach(selector => {
|
|
const btn = selector.querySelector('button');
|
|
const dropdown = selector.querySelector('.lang-dropdown');
|
|
|
|
if (!btn || !dropdown) return;
|
|
|
|
btn.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
// Close other dropdowns
|
|
document.querySelectorAll('.lang-dropdown.open').forEach(d => {
|
|
if (d !== dropdown) d.classList.remove('open');
|
|
});
|
|
dropdown.classList.toggle('open');
|
|
});
|
|
});
|
|
|
|
// Close dropdowns on outside click
|
|
document.addEventListener('click', () => {
|
|
document.querySelectorAll('.lang-dropdown.open').forEach(d => {
|
|
d.classList.remove('open');
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Language selector
|
|
*/
|
|
function initLanguageSelector() {
|
|
const langDropdown = document.getElementById('lang-dropdown');
|
|
if (!langDropdown) return;
|
|
|
|
langDropdown.querySelectorAll('.lang-option').forEach(option => {
|
|
option.addEventListener('click', async (e) => {
|
|
const lang = option.dataset.lang;
|
|
if (lang) {
|
|
await window.changeLanguage(lang);
|
|
}
|
|
});
|
|
option.addEventListener('keydown', async (e) => {
|
|
if (e.key !== 'Enter' && e.key !== ' ') return;
|
|
e.preventDefault();
|
|
const lang = option.dataset.lang;
|
|
if (lang) {
|
|
await window.changeLanguage(lang);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* User menu
|
|
*/
|
|
function initUserMenu() {
|
|
const changePasswordBtn = document.getElementById('change-password-btn');
|
|
const logoutBtn = document.getElementById('logout-btn');
|
|
|
|
changePasswordBtn?.addEventListener('click', () => {
|
|
window.location.href = '/settings';
|
|
});
|
|
|
|
logoutBtn?.addEventListener('click', async () => {
|
|
const confirmed = await Modal.confirm({
|
|
title: _('auth.logout'),
|
|
message: _('auth.logout_confirm'),
|
|
confirmLabel: _('auth.logout'),
|
|
danger: true
|
|
});
|
|
|
|
if (confirmed) {
|
|
try {
|
|
await Utils.api('/api/auth/logout', { method: 'POST' });
|
|
window.location.href = '/login';
|
|
} catch (error) {
|
|
Notifications.error(_('errors.logout_failed'));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Refresh button
|
|
*/
|
|
function initRefreshButton() {
|
|
const refreshBtn = document.getElementById('refresh-btn');
|
|
if (!refreshBtn) return;
|
|
|
|
refreshBtn.addEventListener('click', () => {
|
|
// Dispatch custom event for page-specific refresh handlers
|
|
window.dispatchEvent(new CustomEvent('app:refresh'));
|
|
|
|
// Visual feedback
|
|
const icon = refreshBtn.querySelector('.material-icons');
|
|
if (icon) {
|
|
icon.style.animation = 'spin 0.5s linear';
|
|
setTimeout(() => {
|
|
icon.style.animation = '';
|
|
}, 500);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Add spin animation
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
@keyframes spin {
|
|
from { transform: rotate(0deg); }
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
|
|
/**
|
|
* Periodically fetch pending registration count for sidebar badge.
|
|
*/
|
|
function initRegistrationBadge() {
|
|
const badge = document.getElementById('reg-sidebar-badge');
|
|
if (!badge) return;
|
|
|
|
let badgeInterval = null;
|
|
|
|
async function updateBadge() {
|
|
try {
|
|
const resp = await fetch('/api/registrations/count');
|
|
if (!resp.ok) {
|
|
if (resp.status === 401 && badgeInterval) {
|
|
clearInterval(badgeInterval);
|
|
badgeInterval = null;
|
|
}
|
|
return;
|
|
}
|
|
const data = await resp.json();
|
|
const count = data.count || 0;
|
|
badge.textContent = count;
|
|
badge.style.display = count > 0 ? '' : 'none';
|
|
} catch (_) { /* silent */ }
|
|
}
|
|
|
|
updateBadge();
|
|
badgeInterval = setInterval(updateBadge, 30000); // every 30s
|
|
}
|
|
|
|
})();
|