// public/assets/admin.js /* English comments: admin user management + audit */ function fmtDate(s) { if (!s) return ''; const d = new Date(s.replace(' ', 'T') + 'Z'); if (Number.isNaN(d.getTime())) return s; return d.toLocaleString(); } function adminApi(path, opts = {}) { return window.Api.request(path, opts); } function handleAdminError(err) { const message = err?.message || 'Request failed'; alert(message); } async function loadRoles() { const res = await adminApi('/api/admin/roles'); return res?.data?.items || []; } async function loadUsers() { const res = await adminApi('/api/admin/users'); return res?.data?.items || []; } async function loadAudit() { const res = await adminApi('/api/admin/audit', { method: 'POST', body: { page: 1, per_page: 50 }, }); return res?.data?.items || []; } function renderUsers(users, roles) { const body = UI.qs('adminUsers'); if (!body) return; body.innerHTML = ''; users.forEach((u) => { const tr = document.createElement('tr'); const rolesList = (u.roles || '').split(',').filter(Boolean); const status = u.status || ''; const roleOptions = roles.map((r) => { const selected = rolesList.includes(r.name) ? 'selected' : ''; return ``; }).join(''); tr.innerHTML = ` ${u.email || ''} ${status} ${fmtDate(u.last_login_at)}
`; body.appendChild(tr); }); } function renderAudit(items) { const body = UI.qs('adminAudit'); if (!body) return; body.innerHTML = ''; items.forEach((a) => { const tr = document.createElement('tr'); tr.innerHTML = ` ${fmtDate(a.created_at)} ${a.actor_email || a.actor_user_id || ''} ${a.action || ''} ${a.target_type || ''} ${a.target_id || ''} ${a.meta_json || ''} `; body.appendChild(tr); }); } let actionsBound = false; async function bindActions(roles) { if (actionsBound) return; const body = UI.qs('adminUsers'); if (!body) return; body.addEventListener('change', async (e) => { const sel = e.target; if (!sel || sel.dataset.action !== 'role') return; const userId = sel.dataset.id; const role = sel.value; try { await adminApi(`/api/admin/users/${userId}/roles`, { method: 'POST', body: JSON.stringify({ roles: [role] }), }); } catch (err) { handleAdminError(err); } }); body.addEventListener('click', async (e) => { const btn = e.target.closest('button'); if (!btn) return; const action = btn.dataset.action; const userId = btn.dataset.id; if (action === 'toggle') { const disabled = btn.dataset.status !== 'disabled'; try { await adminApi(`/api/admin/users/${userId}/disable`, { method: 'POST', body: JSON.stringify({ disabled }), }); init(); } catch (err) { handleAdminError(err); } } if (action === 'reset2fa') { try { await adminApi(`/api/admin/users/${userId}/reset-2fa`, { method: 'POST' }); } catch (err) { handleAdminError(err); } } }); actionsBound = true; } async function init() { window.Auth.requireAuth(); window.UI?.initHeader?.(); if (!window.Auth.isAdmin()) { UI.qs('adminUsers').innerHTML = 'Admin only'; return; } const [roles, users, audit] = await Promise.all([loadRoles(), loadUsers(), loadAudit()]); renderUsers(users, roles); renderAudit(audit); bindActions(roles); } const btnReloadUsers = UI.qs('btnReloadUsers'); if (btnReloadUsers) btnReloadUsers.addEventListener('click', init); const btnReloadAudit = UI.qs('btnReloadAudit'); if (btnReloadAudit) btnReloadAudit.addEventListener('click', init); init();