diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-29 20:19:34 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-29 20:19:34 +0300 |
| commit | 4a70c0ea083306cb79d0fa325d331ffa4333154a (patch) | |
| tree | 84df7e2f781974ef45ac8c34f1a59e80fdd5c41d /web/js/admin.js | |
| parent | cf30414c2a0696cc75c615f4da66ea85d0522c42 (diff) | |
feat: create web PWA frontend
Diffstat (limited to 'web/js/admin.js')
| -rw-r--r-- | web/js/admin.js | 101 |
1 files changed, 101 insertions, 0 deletions
diff --git a/web/js/admin.js b/web/js/admin.js new file mode 100644 index 0000000..c992d1d --- /dev/null +++ b/web/js/admin.js @@ -0,0 +1,101 @@ +import { API } from './api.js'; + +export function initAdmin() { + const btn = document.getElementById('admin-toggle'); + const modal = document.getElementById('admin-modal'); + const closeBtn = document.getElementById('admin-close'); + const rescanBtn = document.getElementById('admin-rescan'); + const trashBtn = document.getElementById('admin-trash'); + const form = document.getElementById('admin-user-form'); + + btn?.addEventListener('click', () => { + modal?.classList.add('open'); + refreshAdmin(); + }); + closeBtn?.addEventListener('click', () => modal?.classList.remove('open')); + modal?.addEventListener('click', (e) => { if (e.target === modal) modal.classList.remove('open'); }); + + rescanBtn?.addEventListener('click', async () => { + rescanBtn.disabled = true; + try { await API.rescan(); toast('Rescan triggered'); } + catch (err) { toast(err.message, 'error'); } + finally { rescanBtn.disabled = false; } + }); + + trashBtn?.addEventListener('click', async () => { + try { + const data = await API.trash(); + renderTrash(data); + } catch (err) { toast(err.message, 'error'); } + }); + + form?.addEventListener('submit', async (e) => { + e.preventDefault(); + const fd = new FormData(form); + const body = { + username: fd.get('username'), + password: fd.get('password'), + is_admin: !!fd.get('is_admin'), + }; + try { + await API.createUser(body); + form.reset(); + toast('User created'); + await refreshAdmin(); + } catch (err) { toast(err.message, 'error'); } + }); +} + +async function refreshAdmin() { + try { + const [users, perms] = await Promise.all([API.users(), API.permissions()]); + renderUsers(users); + renderPermissions(perms); + } catch (err) { toast(err.message, 'error'); } +} + +function esc(s) { + return (s ?? '').replace(/[&<>"']/g, (c) => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); +} + +function renderUsers(users) { + const el = document.getElementById('admin-users'); + if (!el) return; + el.innerHTML = `<ul style="list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:0.35rem;"> + ${users.map((u) => `<li style="display:flex;align-items:center;gap:0.5rem;justify-content:space-between;padding:0.25rem 0.35rem;border-radius:var(--radius-sm);${u.is_admin ? 'background:var(--accent-soft);' : ''}"> + <span>${esc(u.username)}${u.is_admin ? ' <span class="badge">admin</span>' : ''}</span> + <button class="btn btn-danger btn-sm" data-id="${u.id}">Remove</button> + </li>`).join('')} + </ul>`; + el.querySelectorAll('button[data-id]').forEach((b) => { + b.addEventListener('click', async () => { + try { await API.deleteUser(b.dataset.id); toast('User removed'); await refreshAdmin(); } + catch (err) { toast(err.message, 'error'); } + }); + }); +} + +function renderPermissions(data) { + const el = document.getElementById('admin-permissions'); + if (!el || !data) return; + el.innerHTML = `<p class="text-muted" style="font-size:0.8rem;">Use the API to manage permissions directly.</p>`; +} + +function renderTrash(data) { + const el = document.getElementById('admin-users'); + if (!el || !Array.isArray(data)) return; + el.innerHTML = `<h4 style="font-size:0.9rem;">Trash</h4><ul style="list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:0.35rem;"> + ${data.map((m) => `<li style="display:flex;align-items:center;justify-content:space-between;padding:0.25rem 0.35rem;border-radius:var(--radius-sm);"> + <span>${esc(m.file_name)}</span> + <span class="text-muted" style="font-size:0.75rem;">${esc(m.deleted_at || '')}</span> + </li>`).join('')} + </ul>`; +} + +function toast(msg, type = 'info') { + const t = document.getElementById('toast'); + if (!t) return; + t.textContent = msg; + t.className = 'toast show ' + type; + setTimeout(() => t.classList.remove('show'), 2800); +} |
