96 lines
3.9 KiB
Plaintext
96 lines
3.9 KiB
Plaintext
---
|
|
import Admin from '../../layouts/Admin.astro';
|
|
import { getDb } from '../../lib/db';
|
|
import { listUsers } from '../../lib/auth';
|
|
export const prerender = false;
|
|
const users = listUsers(getDb());
|
|
const me = Astro.locals.user!;
|
|
---
|
|
<Admin title="Utenti">
|
|
<h1>Utenti</h1>
|
|
|
|
<h2>Nuovo utente</h2>
|
|
<form id="new-user" style="max-width:420px">
|
|
<input class="afield" name="username" placeholder="Nome utente" required />
|
|
<input class="afield" name="password" type="text" placeholder="Password (min 8)" required />
|
|
<select class="afield" name="role">
|
|
<option value="user">user</option>
|
|
<option value="superuser">superuser</option>
|
|
<option value="admin">admin</option>
|
|
</select>
|
|
<button class="abtn" type="submit">Crea</button>
|
|
<span class="form-msg" id="new-msg"></span>
|
|
</form>
|
|
|
|
<h2 style="margin-top:30px">Elenco</h2>
|
|
<table>
|
|
<thead><tr><th>Utente</th><th>Ruolo</th><th></th></tr></thead>
|
|
<tbody>
|
|
{users.map((u) => (
|
|
<tr data-id={u.id}>
|
|
<td>{u.username}{u.id === me.id && ' (tu)'}</td>
|
|
<td>
|
|
<select class="role-sel" data-id={u.id}>
|
|
{['user', 'superuser', 'admin'].map((r) => <option value={r} selected={u.role === r}>{r}</option>)}
|
|
</select>
|
|
</td>
|
|
<td>
|
|
<button class="abtn" data-reset={u.id}>Reset password</button>
|
|
{u.id !== me.id && <button class="abtn abtn--danger" data-del={u.id}>Elimina</button>}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
<p class="form-msg" id="row-msg"></p>
|
|
</Admin>
|
|
|
|
<script>
|
|
const rowMsg = document.getElementById('row-msg')!;
|
|
const show = (el: HTMLElement, text: string, ok = false) => {
|
|
el.textContent = text;
|
|
el.className = 'form-msg ' + (ok ? 'form-msg--ok' : 'form-msg--err');
|
|
};
|
|
|
|
document.getElementById('new-user')!.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
const f = e.target as HTMLFormElement;
|
|
const body = {
|
|
username: (f.elements.namedItem('username') as HTMLInputElement).value,
|
|
password: (f.elements.namedItem('password') as HTMLInputElement).value,
|
|
role: (f.elements.namedItem('role') as HTMLSelectElement).value,
|
|
};
|
|
const res = await fetch('/api/admin/users', {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
|
|
});
|
|
const msg = document.getElementById('new-msg') as HTMLElement;
|
|
if (res.ok) { show(msg, 'Creato ✓', true); setTimeout(() => location.reload(), 600); }
|
|
else show(msg, (await res.json()).error ?? 'Errore');
|
|
});
|
|
|
|
document.querySelectorAll<HTMLSelectElement>('.role-sel').forEach((sel) =>
|
|
sel.addEventListener('change', async () => {
|
|
const res = await fetch(`/api/admin/users/${sel.dataset.id}/role`, {
|
|
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ role: sel.value }),
|
|
});
|
|
if (res.ok) show(rowMsg, 'Ruolo aggiornato ✓', true);
|
|
else { show(rowMsg, (await res.json()).error ?? 'Errore'); location.reload(); }
|
|
}));
|
|
|
|
document.querySelectorAll<HTMLButtonElement>('[data-reset]').forEach((b) =>
|
|
b.addEventListener('click', async () => {
|
|
if (!confirm('Generare una nuova password per questo utente?')) return;
|
|
const res = await fetch(`/api/admin/users/${b.dataset.reset}/reset-password`, { method: 'POST' });
|
|
if (res.ok) { const { password } = await res.json(); prompt('Nuova password (copiala e consegnala ora):', password); }
|
|
else show(rowMsg, (await res.json()).error ?? 'Errore');
|
|
}));
|
|
|
|
document.querySelectorAll<HTMLButtonElement>('[data-del]').forEach((b) =>
|
|
b.addEventListener('click', async () => {
|
|
if (!confirm('Eliminare definitivamente questo utente?')) return;
|
|
const res = await fetch(`/api/admin/users/${b.dataset.del}`, { method: 'DELETE' });
|
|
if (res.ok) location.reload();
|
|
else show(rowMsg, (await res.json()).error ?? 'Errore');
|
|
}));
|
|
</script>
|