Files
Presentation-system/public/js/users.js
T

139 lines
6.7 KiB
JavaScript
Raw Normal View History

2026-09-13 20:23:05 +01:00
const $ = (s) => document.querySelector(s);
let me = null;
function toast(msg) {
const t = $('#toast');
t.textContent = msg; t.classList.add('show');
clearTimeout(toast._t); toast._t = setTimeout(() => t.classList.remove('show'), 2200);
}
async function api(path, opts = {}) {
const res = await fetch(path, opts);
if (res.status === 401) { location.href = '/login'; throw new Error('auth'); }
if (res.status === 403) { location.href = '/'; throw new Error('forbidden'); }
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(data.error || `Request failed (${res.status})`);
return data;
}
const esc = (s) => String(s ?? '').replace(/[&<>"']/g, (c) =>
({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
$('#logoutBtn').addEventListener('click', async () => {
await fetch('/api/logout', { method: 'POST' }); location.href = '/login';
});
// --- tabs ------------------------------------------------------------------
$('#tabs').addEventListener('click', (e) => {
const btn = e.target.closest('button'); if (!btn) return;
document.querySelectorAll('#tabs button').forEach((b) => b.classList.toggle('active', b === btn));
document.querySelectorAll('.tab-panel').forEach((p) => p.classList.toggle('active', p.id === `tab-${btn.dataset.tab}`));
if (btn.dataset.tab === 'logs') loadLogs();
if (btn.dataset.tab === 'mail') loadMail();
});
// --- users -----------------------------------------------------------------
async function loadUsers() {
const users = await api('/api/users');
const tbody = $('#userTable tbody');
tbody.innerHTML = '';
for (const u of users) {
const tr = document.createElement('tr');
const isMe = me && u.id === me.user.id;
tr.innerHTML = `
<td>${esc(u.username)}${isMe ? ' <span class="hint">(you)</span>' : ''}</td>
<td class="mono">${esc(u.email) || '—'}</td>
<td><span class="role-badge ${u.role}">${u.role}</span></td>
<td class="mono">${new Date(u.createdAt).toLocaleDateString()}</td>
<td><div class="row">
<button class="btn ghost small" data-role="${u.id}" data-current="${u.role}">${u.role === 'admin' ? 'Make user' : 'Make admin'}</button>
<button class="btn ghost small" data-pw="${u.id}">Reset password</button>
<button class="btn danger small" data-del="${u.id}" ${isMe ? 'disabled' : ''}>Delete</button>
</div></td>`;
tbody.appendChild(tr);
}
tbody.querySelectorAll('[data-role]').forEach((b) => b.addEventListener('click', async () => {
const next = b.dataset.current === 'admin' ? 'user' : 'admin';
try { await api(`/api/users/${b.dataset.role}/role`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ role: next }) }); loadUsers(); }
catch (e) { toast(e.message); }
}));
tbody.querySelectorAll('[data-pw]').forEach((b) => b.addEventListener('click', async () => {
const pw = prompt('New password for this user:');
if (!pw) return;
try { await api(`/api/users/${b.dataset.pw}/password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password: pw }) }); toast('Password updated'); }
catch (e) { toast(e.message); }
}));
tbody.querySelectorAll('[data-del]').forEach((b) => b.addEventListener('click', async () => {
if (!confirm('Delete this user?')) return;
try { await api(`/api/users/${b.dataset.del}`, { method: 'DELETE' }); loadUsers(); }
catch (e) { toast(e.message); }
}));
}
$('#nu_add').addEventListener('click', async () => {
$('#nu_err').textContent = '';
try {
await api('/api/users', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: $('#nu_name').value, email: $('#nu_email').value, password: $('#nu_pass').value, role: $('#nu_role').value })
});
$('#nu_name').value = $('#nu_email').value = $('#nu_pass').value = '';
toast('User created'); loadUsers();
} catch (e) { $('#nu_err').textContent = e.message; }
});
// --- mail ------------------------------------------------------------------
async function loadMail() {
const c = await api('/api/mail');
$('#m_enabled').checked = c.enabled;
$('#m_username').value = c.username || '';
$('#m_password').value = '';
$('#m_password').placeholder = c.hasPassword ? '•••••••• (unchanged)' : 'password';
$('#m_fromName').value = c.fromName || '';
$('#m_smtpHost').value = c.smtpHost; $('#m_smtpPort').value = c.smtpPort;
$('#m_imapHost').value = c.imapHost; $('#m_imapPort').value = c.imapPort;
$('#m_sentFolder').value = c.sentFolder; $('#m_saveToSent').checked = c.saveToSent;
}
$('#m_save').addEventListener('click', async () => {
$('#m_err').textContent = '';
const body = {
enabled: $('#m_enabled').checked, username: $('#m_username').value.trim(),
password: $('#m_password').value, fromName: $('#m_fromName').value,
smtpHost: $('#m_smtpHost').value.trim(), smtpPort: $('#m_smtpPort').value,
imapHost: $('#m_imapHost').value.trim(), imapPort: $('#m_imapPort').value,
sentFolder: $('#m_sentFolder').value.trim(), saveToSent: $('#m_saveToSent').checked
};
try { await api('/api/mail', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); toast('Mail settings saved'); loadMail(); }
catch (e) { $('#m_err').textContent = e.message; }
});
$('#m_test').addEventListener('click', async () => {
$('#m_err').textContent = '';
const to = $('#m_testTo').value.trim();
try { await api('/api/mail/test', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ to }) }); toast('Test email sent'); }
catch (e) { $('#m_err').textContent = e.message; }
});
// --- logs ------------------------------------------------------------------
async function loadLogs() {
const logs = await api('/api/logs');
const box = $('#logList');
if (!logs.length) { box.innerHTML = '<div class="empty">No activity logged yet.</div>'; return; }
box.innerHTML = logs.map((l) => `
<div class="log-line">
<span class="mono">${new Date(l.ts).toLocaleString()}</span>
<span class="lv ${l.level || 'info'}">${l.level || 'info'}</span>
<span class="actor">${esc(l.actor || '')}</span>
<span>${esc(l.message || '')}</span>
</div>`).join('');
}
$('#logRefresh').addEventListener('click', loadLogs);
$('#logClear').addEventListener('click', async () => {
if (!confirm('Clear the activity log?')) return;
await api('/api/logs', { method: 'DELETE' }); loadLogs();
});
// --- boot ------------------------------------------------------------------
(async () => {
try { me = await api('/api/me'); } catch { return; }
if (!me.authed) { location.href = '/login'; return; }
if (me.user.role !== 'admin') { location.href = '/'; return; }
loadUsers();
})();