// Admin alerts — bell icon + dropdown panel const adminAlerts = new Map(); let alertsPanelOpen = false; function escapeAlertText(value) { const esc = window.escapeHtml || ((text) => String(text)); return esc(value); } function renderAdminAlerts() { const badge = document.getElementById('admin-alerts-badge'); const list = document.getElementById('admin-alerts-list'); const empty = document.getElementById('admin-alerts-empty'); const count = adminAlerts.size; if (badge) { badge.textContent = count > 99 ? '99+' : String(count); badge.classList.toggle('admin-alerts-badge--visible', count > 0); } const bell = document.getElementById('admin-alerts-button'); if (bell) { bell.classList.toggle('admin-alerts-bell--active', count > 0); bell.setAttribute('aria-label', count > 0 ? `${count} active alert${count === 1 ? '' : 's'}` : 'Alerts'); } if (!list) return; if (count === 0) { list.innerHTML = ''; if (empty) empty.classList.add('admin-alerts-empty--visible'); return; } if (empty) empty.classList.remove('admin-alerts-empty--visible'); const items = Array.from(adminAlerts.values()).sort((a, b) => { const severityOrder = { error: 0, warning: 1, info: 2 }; const sa = severityOrder[a.severity] ?? 3; const sb = severityOrder[b.severity] ?? 3; if (sa !== sb) return sa - sb; return (a.title || '').localeCompare(b.title || ''); }); list.innerHTML = items.map(alert => { const severity = alert.severity || 'warning'; const icon = severity === 'error' ? 'fa-circle-exclamation' : severity === 'info' ? 'fa-circle-info' : 'fa-triangle-exclamation'; const source = alert.source ? `${escapeAlertText(alert.source)}` : ''; const details = Array.isArray(alert.details) && alert.details.length > 0 ? `` : ''; const viewTab = alert.tab ? `` : ''; const action = alert.actionLabel || alert.action?.type === 'remove-orphan-ip' ? `` : ''; return `

${escapeAlertText(alert.title || 'Alert')}

${source}

${alert.message || ''}

${details} ${(action || viewTab) ? `
${action}${viewTab}
` : ''}
`; }).join(''); } function setAdminAlert(id, alert) { if (!id) return; adminAlerts.set(id, { ...alert, id }); renderAdminAlerts(); } function clearAdminAlertsByPrefix(prefix) { if (!prefix) return; let changed = false; for (const id of adminAlerts.keys()) { if (id.startsWith(prefix)) { adminAlerts.delete(id); changed = true; } } if (changed) renderAdminAlerts(); } function clearAdminAlert(id) { if (!id) return; if (adminAlerts.delete(id)) { renderAdminAlerts(); } } function replaceServerAdminAlerts(alerts) { for (const id of [...adminAlerts.keys()]) { if (!id.startsWith('client-')) { adminAlerts.delete(id); } } for (const alert of alerts || []) { if (alert?.id) { adminAlerts.set(alert.id, alert); } } renderAdminAlerts(); } function toggleAdminAlertsPanel(forceOpen) { const panel = document.getElementById('admin-alerts-panel'); const button = document.getElementById('admin-alerts-button'); if (!panel || !button) return; alertsPanelOpen = typeof forceOpen === 'boolean' ? forceOpen : !alertsPanelOpen; panel.classList.toggle('admin-alerts-panel--open', alertsPanelOpen); button.setAttribute('aria-expanded', alertsPanelOpen ? 'true' : 'false'); } function closeAdminAlertsPanel() { if (alertsPanelOpen) toggleAdminAlertsPanel(false); } function handleAdminAlertClick(event) { const tabBtn = event.target.closest('[data-alert-tab]'); if (tabBtn) { const tabId = tabBtn.dataset.alertTab; const subTabId = tabBtn.dataset.alertSubtab; closeAdminAlertsPanel(); if (window.navigateToTab) { window.navigateToTab(tabId); } else { location.hash = tabId; } if (subTabId && window.showSubTab) { window.showSubTab(tabId, subTabId); } return; } const actionBtn = event.target.closest('[data-alert-action]'); if (actionBtn) { const alertId = actionBtn.dataset.alertId; const alert = adminAlerts.get(alertId); if (alert?.onAction) { alert.onAction(); } else if (alert?.action?.type === 'remove-orphan-ip' && alert.action.ip && window.removeOrphanedIp) { window.removeOrphanedIp(alert.action.ip); } else if (alert?.actionHandler && typeof window[alert.actionHandler] === 'function') { window[alert.actionHandler](); } return; } } function initAdminAlerts() { const button = document.getElementById('admin-alerts-button'); const panel = document.getElementById('admin-alerts-panel'); const list = document.getElementById('admin-alerts-list'); if (button) { button.addEventListener('click', (event) => { event.stopPropagation(); toggleAdminAlertsPanel(); }); } if (list) { list.addEventListener('click', handleAdminAlertClick); } document.addEventListener('click', (event) => { if (!alertsPanelOpen) return; if (panel?.contains(event.target) || button?.contains(event.target)) return; closeAdminAlertsPanel(); }); document.addEventListener('keydown', (event) => { if (event.key === 'Escape') closeAdminAlertsPanel(); }); renderAdminAlerts(); } window.setAdminAlert = setAdminAlert; window.clearAdminAlert = clearAdminAlert; window.clearAdminAlertsByPrefix = clearAdminAlertsByPrefix; window.replaceServerAdminAlerts = replaceServerAdminAlerts; window.toggleAdminAlertsPanel = toggleAdminAlertsPanel; window.closeAdminAlertsPanel = closeAdminAlertsPanel; window.renderAdminAlerts = renderAdminAlerts; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initAdminAlerts); } else { initAdminAlerts(); }