// Copyright 2026 Versity Software // This file is licensed under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. /** * VersityGW Admin - Application Utilities */ // ============================================ // Navigation & Auth Guards // ============================================ /** * Check if user is authenticated, redirect to login if not * Also loads user context (user type and accessible gateways) */ function requireAuth() { if (!api.loadCredentials()) { window.location.href = 'index.html'; return false; } api.loadUserContext(); return true; } /** * Require admin role, redirect non-admins to explorer * Call this on admin-only pages (dashboard, users, buckets, settings) * Also loads user context (user type and accessible gateways) */ function requireAdmin() { if (!api.loadCredentials()) { window.location.href = 'index.html'; return false; } api.loadUserContext(); if (!api.isAdmin()) { window.location.href = 'explorer.html'; return false; } return true; } /** * Redirect to appropriate page if already authenticated * Admin users go to dashboard, regular users go to explorer */ function redirectIfAuthenticated() { if (api.loadCredentials()) { if (api.isAdmin()) { window.location.href = 'dashboard.html'; } else { window.location.href = 'explorer.html'; } return true; } return false; } // ============================================ // Toast Notifications // ============================================ let toastContainer = null; function initToasts() { if (!toastContainer) { toastContainer = document.createElement('div'); toastContainer.id = 'toast-container'; toastContainer.className = 'fixed top-4 right-4 z-50 flex flex-col gap-2'; document.body.appendChild(toastContainer); } } function showToast(message, type = 'info') { initToasts(); const toast = document.createElement('div'); const bgColors = { success: 'bg-green-50 border-green-500 text-green-800', error: 'bg-red-50 border-red-500 text-red-800', warning: 'bg-yellow-50 border-yellow-500 text-yellow-800', info: 'bg-blue-50 border-blue-500 text-blue-800' }; const icons = { success: ``, error: ``, warning: ``, info: `` }; toast.className = `flex items-center gap-3 px-4 py-3 rounded-lg border-l-4 shadow-lg max-w-sm animate-slide-in ${bgColors[type]}`; toast.innerHTML = ` ${icons[type]}

${escapeHtml(message)}

`; toastContainer.appendChild(toast); // Auto-remove after 5 seconds setTimeout(() => { toast.classList.add('animate-fade-out'); setTimeout(() => toast.remove(), 300); }, 5000); } // ============================================ // Modal Utilities // ============================================ function openModal(modalId) { const modal = document.getElementById(modalId); if (modal) { modal.classList.remove('hidden'); // Focus first input const firstInput = modal.querySelector('input:not([readonly]), select'); if (firstInput) setTimeout(() => firstInput.focus(), 100); } } function closeModal(modalId) { const modal = document.getElementById(modalId); if (modal) { modal.classList.add('hidden'); } } function closeAllModals() { document.querySelectorAll('[id$="-modal"]').forEach(modal => { modal.classList.add('hidden'); }); } // Close modals on Escape key document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeAllModals(); }); // ============================================ // Loading States // ============================================ function setLoading(element, loading) { if (loading) { element.disabled = true; element.dataset.originalText = element.innerHTML; element.innerHTML = ` `; } else { element.disabled = false; if (element.dataset.originalText) { element.innerHTML = element.dataset.originalText; } } } function showTableLoading(tableBodyId, columns) { const tbody = document.getElementById(tableBodyId); if (!tbody) return; tbody.innerHTML = ''; for (let i = 0; i < 5; i++) { const row = document.createElement('tr'); row.className = 'border-b border-gray-50'; for (let j = 0; j < columns; j++) { row.innerHTML += `
`; } tbody.appendChild(row); } } function showEmptyState(tableBodyId, columns, message = 'No data found') { const tbody = document.getElementById(tableBodyId); if (!tbody) return; tbody.innerHTML = `

${escapeHtml(message)}

`; } // ============================================ // Utility Functions // ============================================ function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function formatRole(role) { const roleConfig = { admin: { label: 'Admin', class: 'bg-primary-50 text-primary' }, user: { label: 'User', class: 'bg-gray-100 text-charcoal' }, userplus: { label: 'User+', class: 'bg-accent-50 text-accent' } }; const config = roleConfig[role] || roleConfig.user; return `${config.label}`; } function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // ============================================ // Sidebar Active State // ============================================ function initSidebar() { const currentPage = window.location.pathname.split('/').pop() || 'index.html'; document.querySelectorAll('.nav-item').forEach(item => { const href = item.getAttribute('href'); if (href === currentPage) { item.classList.add('active'); item.classList.remove('text-white/70'); item.classList.add('text-white'); } else { item.classList.remove('active'); } }); } // ============================================ // Update User Info in Sidebar // ============================================ function updateUserInfo() { const info = api.getCredentialsInfo(); if (!info) return; const accessKeyShort = info.accessKey.length > 12 ? info.accessKey.substring(0, 12) + '...' : info.accessKey; const roleLabel = info.isAdmin ? 'Admin' : 'User'; const userInfoEl = document.getElementById('user-info'); if (userInfoEl) { userInfoEl.innerHTML = `

${escapeHtml(accessKeyShort)}

${roleLabel}

`; } } /** * Initialize sidebar with role-based navigation * Hides admin-only nav items for non-admin users */ function initSidebarWithRole() { initSidebar(); // Hide admin-only nav items for non-admin users if (!api.isAdmin()) { document.querySelectorAll('[data-admin-only]').forEach(item => { item.style.display = 'none'; }); } } // ============================================ // Confirm Dialog // ============================================ function confirm(message, onConfirm, onCancel) { const modal = document.createElement('div'); modal.className = 'fixed inset-0 z-50'; modal.innerHTML = `

Confirm Action

${escapeHtml(message)}

`; document.body.appendChild(modal); modal.querySelector('#confirm-cancel').addEventListener('click', () => { modal.remove(); if (onCancel) onCancel(); }); modal.querySelector('#confirm-ok').addEventListener('click', () => { modal.remove(); if (onConfirm) onConfirm(); }); modal.querySelector('.modal-backdrop').addEventListener('click', () => { modal.remove(); if (onCancel) onCancel(); }); } // ============================================ // CSS Animations (inject once) // ============================================ const styleEl = document.createElement('style'); styleEl.textContent = ` @keyframes slide-in { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes fade-out { from { opacity: 1; } to { opacity: 0; } } .animate-slide-in { animation: slide-in 0.3s ease-out; } .animate-fade-out { animation: fade-out 0.3s ease-out; } `; document.head.appendChild(styleEl);