// 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();