125 lines
4.4 KiB
JavaScript
125 lines
4.4 KiB
JavaScript
import {
|
|
adminCreateUserAction,
|
|
adminDeleteUserAction,
|
|
adminLogoutAction,
|
|
adminResetUserAction,
|
|
} from '@/src/lib/actions.js';
|
|
import { all, runCleanupIfNeeded } from '@/src/lib/db.js';
|
|
import { formatTimestamp, readSearchParam } from '@/src/lib/format.js';
|
|
import { ensureCsrfToken, requireAdminUser } from '@/src/lib/security.js';
|
|
|
|
import { StatusMessage } from '../../_components/status-message.js';
|
|
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
export default async function AdminUsersPage({ searchParams }) {
|
|
await runCleanupIfNeeded();
|
|
await requireAdminUser();
|
|
|
|
const csrfToken = await ensureCsrfToken();
|
|
const users = await all('SELECT username, created_at FROM users ORDER BY username ASC');
|
|
|
|
const resolvedSearchParams = await searchParams;
|
|
const error = readSearchParam(resolvedSearchParams, 'error');
|
|
const success = readSearchParam(resolvedSearchParams, 'success');
|
|
|
|
return (
|
|
<main className="page-shell">
|
|
<header className="page-header">
|
|
<div className="header-main">
|
|
<h1>Benutzerverwaltung</h1>
|
|
<p className="muted">Konten erstellen, Passwort setzen oder Benutzer entfernen.</p>
|
|
</div>
|
|
|
|
<div className="toolbar">
|
|
<a className="chip" href="/manage/admin/dashboard">
|
|
Zur Adminübersicht
|
|
</a>
|
|
<form className="inline-form" action={adminLogoutAction}>
|
|
<input type="hidden" name="csrfToken" value={csrfToken} />
|
|
<button className="btn secondary" type="submit">
|
|
Abmelden
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</header>
|
|
|
|
<StatusMessage error={error} success={success} />
|
|
|
|
<section className="panel">
|
|
<h2>Neuen Benutzer anlegen</h2>
|
|
<form className="form-grid" action={adminCreateUserAction}>
|
|
<input type="hidden" name="csrfToken" value={csrfToken} />
|
|
|
|
<label className="field">
|
|
Benutzername
|
|
<input className="input" name="username" autoComplete="username" required />
|
|
</label>
|
|
|
|
<label className="field">
|
|
Passwort
|
|
<input className="input" name="password" type="password" autoComplete="new-password" required />
|
|
</label>
|
|
|
|
<button className="btn" type="submit">
|
|
Benutzer erstellen
|
|
</button>
|
|
</form>
|
|
</section>
|
|
|
|
<section className="panel">
|
|
<h2>Bestehende Benutzer</h2>
|
|
{users.length === 0 ? (
|
|
<p className="muted">Noch keine Benutzer vorhanden.</p>
|
|
) : (
|
|
<div className="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Benutzername</th>
|
|
<th>Erstellt</th>
|
|
<th>Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{users.map((user) => (
|
|
<tr key={user.username}>
|
|
<td>{user.username}</td>
|
|
<td>{formatTimestamp(user.created_at)}</td>
|
|
<td>
|
|
<div className="stack-actions">
|
|
<form className="inline-form" action={adminResetUserAction}>
|
|
<input type="hidden" name="csrfToken" value={csrfToken} />
|
|
<input type="hidden" name="username" value={user.username} />
|
|
<input
|
|
className="input small"
|
|
name="password"
|
|
type="password"
|
|
placeholder="Neues Passwort"
|
|
required
|
|
/>
|
|
<button className="btn" type="submit">
|
|
Passwort setzen
|
|
</button>
|
|
</form>
|
|
|
|
<form className="inline-form" action={adminDeleteUserAction}>
|
|
<input type="hidden" name="csrfToken" value={csrfToken} />
|
|
<input type="hidden" name="username" value={user.username} />
|
|
<button className="btn danger" type="submit">
|
|
Benutzer löschen
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
</section>
|
|
</main>
|
|
);
|
|
}
|