// ============================================================ // ADMIN.JS — Admin panel: users, settings, stats // ============================================================ (function() { var loaded = false; // Load admin panel when tab is clicked document.addEventListener('click', function(e) { var btn = e.target.closest('.tab-btn'); if (btn && btn.getAttribute('data-tab') === 'admin') { if (!loaded) { loadAdmin(); loaded = true; } } // Refresh button if (e.target.closest('#btn-refresh-users')) { loadUsers(); } // Toggle registration if (e.target.closest('#btn-toggle-reg')) { toggleRegistration(); } }); function loadAdmin() { loadSettings(); loadUsers(); } // ---- SETTINGS + STATS ---- function loadSettings() { fetch('/api/admin/settings', { headers: getAuthHeaders() }) .then(function(r) { return r.json(); }) .then(function(data) { if (!data.success) return; var stats = data.stats || {}; var settings = data.settings || {}; var el = document.getElementById('stat-users'); if (el) el.textContent = stats.totalUsers !== undefined ? stats.totalUsers : '—'; el = document.getElementById('stat-api-total'); if (el) el.textContent = stats.totalApiCalls !== undefined ? stats.totalApiCalls : '—'; el = document.getElementById('stat-api-today'); if (el) el.textContent = stats.todayApiCalls !== undefined ? stats.todayApiCalls : '—'; updateRegStatus(settings.registrationEnabled !== false); }) .catch(function(err) { console.error('[Admin] Settings load failed:', err); }); } function updateRegStatus(enabled) { var text = document.getElementById('reg-status-text'); var btn = document.getElementById('btn-toggle-reg'); if (text) { text.innerHTML = 'Registration is currently ' + (enabled ? '✅ Enabled' : '❌ Disabled') + ''; } if (btn) btn.textContent = enabled ? 'Disable Registration' : 'Enable Registration'; window._adminRegEnabled = enabled; } function toggleRegistration() { var newVal = !window._adminRegEnabled; fetch('/api/admin/settings/registration', { method: 'POST', headers: getAuthHeaders(), body: JSON.stringify({ enabled: newVal }) }) .then(function(r) { return r.json(); }) .then(function(data) { if (data.success) { updateRegStatus(newVal); showToast('Registration ' + (newVal ? 'enabled' : 'disabled'), 'success'); } else { showToast(data.error || 'Failed', 'error'); } }) .catch(function() { showToast('Request failed', 'error'); }); } // ---- USERS ---- function loadUsers() { var tbody = document.getElementById('admin-users-body'); if (!tbody) return; tbody.innerHTML = 'Loading...'; fetch('/api/admin/users', { headers: getAuthHeaders() }) .then(function(r) { return r.json(); }) .then(function(data) { if (!data.success) { tbody.innerHTML = 'Failed to load users'; return; } renderUsers(data.users || []); }) .catch(function() { tbody.innerHTML = 'Request failed'; }); } function renderUsers(users) { var tbody = document.getElementById('admin-users-body'); if (!tbody) return; var currentUser = JSON.parse(localStorage.getItem('ped_scribe_user') || '{}'); if (users.length === 0) { tbody.innerHTML = 'No users found'; return; } tbody.innerHTML = users.map(function(u) { var isSelf = currentUser.id && u.id === currentUser.id; var roleColor = u.role === 'admin' ? 'var(--blue)' : 'var(--g500)'; var statusColor = u.disabled ? 'var(--red)' : (u.email_verified ? 'var(--green)' : 'var(--amber)'); var statusText = u.disabled ? 'Disabled' : (u.email_verified ? 'Active' : 'Unverified'); var joined = u.created_at ? new Date(u.created_at).toLocaleDateString() : '—'; var actions = []; if (!isSelf) { if (!u.email_verified) { actions.push(''); } if (u.disabled) { actions.push(''); } else { actions.push(''); } if (u.role === 'admin') { actions.push(''); } else { actions.push(''); } actions.push(''); actions.push(''); } else { actions.push('(you)'); } return '' + '
' + esc(u.name) + '
' + esc(u.email) + '
' + '' + (u.role || 'user') + '' + '' + statusText + '' + '' + joined + '' + '' + actions.join(' ') + '' + ''; }).join(''); // Bind action buttons tbody.querySelectorAll('.admin-action').forEach(function(btn) { btn.addEventListener('click', function() { handleUserAction(btn.dataset.action, btn.dataset.id, btn.dataset.email, btn.dataset.name); }); }); } function handleUserAction(action, userId, email, name) { switch (action) { case 'verify': if (!confirm('Verify email for ' + email + '?')) return; adminPost('/api/admin/users/' + userId + '/verify', {}, function() { showToast(email + ' verified', 'success'); loadUsers(); }); break; case 'disable': if (!confirm('Disable account for ' + email + '?')) return; adminPost('/api/admin/users/' + userId + '/disable', {}, function() { showToast(email + ' disabled', 'info'); loadUsers(); }); break; case 'enable': adminPost('/api/admin/users/' + userId + '/enable', {}, function() { showToast(email + ' enabled', 'success'); loadUsers(); }); break; case 'promote': if (!confirm('Grant admin role to ' + email + '?')) return; adminPost('/api/admin/users/' + userId + '/role', { role: 'admin' }, function() { showToast(email + ' is now admin', 'success'); loadUsers(); }); break; case 'demote': if (!confirm('Remove admin role from ' + email + '?')) return; adminPost('/api/admin/users/' + userId + '/role', { role: 'user' }, function() { showToast(email + ' demoted to user', 'info'); loadUsers(); }); break; case 'reset-pw': var newPw = prompt('New password for ' + email + ' (8+ characters):'); if (!newPw) return; if (newPw.length < 8) { showToast('Password must be 8+ characters', 'error'); return; } adminPost('/api/admin/users/' + userId + '/reset-password', { newPassword: newPw }, function() { showToast('Password reset for ' + email, 'success'); }); break; case 'delete': if (!confirm('Permanently delete user ' + (name || email) + '?\n\nThis cannot be undone.')) return; fetch('/api/admin/users/' + userId, { method: 'DELETE', headers: getAuthHeaders() }) .then(function(r) { return r.json(); }) .then(function(data) { if (data.success) { showToast(email + ' deleted', 'info'); loadUsers(); } else showToast(data.error || 'Delete failed', 'error'); }) .catch(function() { showToast('Request failed', 'error'); }); break; } } function adminPost(url, body, onSuccess) { fetch(url, { method: 'POST', headers: getAuthHeaders(), body: JSON.stringify(body) }) .then(function(r) { return r.json(); }) .then(function(data) { if (data.success) { onSuccess(data); } else showToast(data.error || 'Request failed', 'error'); }) .catch(function() { showToast('Request failed', 'error'); }); } function esc(str) { if (!str) return ''; return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } })();