// User Management Page (function () { let usersData = []; window.initUsers = async function () { await loadUsers(); setupEventListeners(); }; async function loadUsers() { try { usersData = await window.api.get('/users'); renderUsersTable(); } catch (err) { console.error('Failed to load users:', err); const tbody = document.querySelector('#users-table tbody'); if (tbody) { tbody.innerHTML = `Failed to load users: ${err.message}`; } } } function renderUsersTable() { const tbody = document.querySelector('#users-table tbody'); if (!tbody) return; if (!usersData || usersData.length === 0) { tbody.innerHTML = 'No users found'; return; } tbody.innerHTML = usersData.map(user => { const roleBadge = user.role === 'admin' ? 'Admin' : 'Viewer'; const statusBadge = user.must_change_password ? 'Must Change Password' : 'Active'; const created = user.created_at ? luxon.DateTime.fromISO(user.created_at).toRelative() : 'Unknown'; return ` ${escapeHtml(user.username)} ${escapeHtml(user.display_name || user.username)} ${roleBadge} ${created} ${statusBadge}
`; }).join(''); } function setupEventListeners() { const addBtn = document.getElementById('add-user'); if (addBtn) { addBtn.onclick = () => showCreateModal(); } } // ── Create User Modal ────────────────────────────────────────── function showCreateModal() { const overlay = document.createElement('div'); overlay.className = 'modal-overlay'; overlay.id = 'user-modal-overlay'; overlay.innerHTML = ` `; document.body.appendChild(overlay); document.getElementById('user-modal-close').onclick = () => overlay.remove(); document.getElementById('user-modal-cancel').onclick = () => overlay.remove(); overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); }; document.getElementById('user-modal-save').onclick = async () => { const username = document.getElementById('new-username').value.trim(); const display_name = document.getElementById('new-display-name').value.trim() || null; const password = document.getElementById('new-password').value; const role = document.getElementById('new-role').value; if (!username) { window.authManager.showToast('Username is required', 'error'); return; } if (password.length < 4) { window.authManager.showToast('Password must be at least 4 characters', 'error'); return; } try { await window.api.post('/users', { username, password, display_name, role }); overlay.remove(); window.authManager.showToast(`User '${username}' created`, 'success'); await loadUsers(); } catch (err) { window.authManager.showToast(err.message, 'error'); } }; document.getElementById('new-username').focus(); } // ── Edit User Modal ──────────────────────────────────────────── window._editUser = function (id) { const user = usersData.find(u => u.id === id); if (!user) return; const overlay = document.createElement('div'); overlay.className = 'modal-overlay'; overlay.id = 'user-edit-overlay'; overlay.innerHTML = ` `; document.body.appendChild(overlay); document.getElementById('edit-modal-close').onclick = () => overlay.remove(); document.getElementById('edit-modal-cancel').onclick = () => overlay.remove(); overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); }; document.getElementById('edit-modal-save').onclick = async () => { const display_name = document.getElementById('edit-display-name').value.trim() || null; const role = document.getElementById('edit-role').value; try { await window.api.put(`/users/${id}`, { display_name, role }); overlay.remove(); window.authManager.showToast('User updated', 'success'); await loadUsers(); } catch (err) { window.authManager.showToast(err.message, 'error'); } }; }; // ── Reset Password Modal ─────────────────────────────────────── window._resetUserPassword = function (id, username) { const overlay = document.createElement('div'); overlay.className = 'modal-overlay'; overlay.id = 'pw-reset-overlay'; overlay.innerHTML = ` `; document.body.appendChild(overlay); document.getElementById('pw-modal-close').onclick = () => overlay.remove(); document.getElementById('pw-modal-cancel').onclick = () => overlay.remove(); overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); }; document.getElementById('pw-modal-save').onclick = async () => { const password = document.getElementById('reset-password').value; const must_change_password = document.getElementById('reset-must-change').checked; if (password.length < 4) { window.authManager.showToast('Password must be at least 4 characters', 'error'); return; } try { await window.api.put(`/users/${id}`, { password, must_change_password }); overlay.remove(); window.authManager.showToast(`Password reset for '${username}'`, 'success'); await loadUsers(); } catch (err) { window.authManager.showToast(err.message, 'error'); } }; document.getElementById('reset-password').focus(); }; // ── Delete User ──────────────────────────────────────────────── window._deleteUser = function (id, username) { if (!confirm(`Delete user '${username}'? This action cannot be undone.`)) return; window.api.delete(`/users/${id}`).then(() => { window.authManager.showToast(`User '${username}' deleted`, 'success'); loadUsers(); }).catch(err => { window.authManager.showToast(err.message, 'error'); }); }; // ── Helpers ──────────────────────────────────────────────────── function escapeHtml(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } })();