Files
Wabenhain/src/app/admin/audit-log/page.tsx
T
Nick Szaunig c7927f2883 feat: Wabenhain Honig-Shop Anwendung
Vollstaendige Next.js/Prisma-E-Commerce-Anwendung fuer eine Imkerei:
Shop, Warenkorb, Stripe-Checkout, Kundenkonto mit 2FA, Admin-Bereich
(Produkte, Ernten, Chargen, Bestellungen, Gutscheine, admin-editierbare
Website-Inhalte), i18n (DE/EN), Rate-Limiting/CSP/Audit-Log-Haertung,
PostgreSQL- und MySQL-Unterstuetzung ueber austauschbare Prisma-
Treiber-Adapter, Unit-/Komponenten-/E2E-Tests.
2026-08-17 14:30:28 +02:00

53 lines
2.3 KiB
TypeScript

import { prisma } from "@/lib/prisma";
import { BackLink } from "@/components/back-link";
import { formatDateTime } from "@/lib/format";
import { getLocale } from "@/i18n/locale";
import { getDictionary } from "@/i18n/get-dictionary";
const RECENT_LIMIT = 200;
export default async function AdminAuditLogPage() {
const t = getDictionary(await getLocale());
const entries = await prisma.auditLog.findMany({
orderBy: { createdAt: "desc" },
take: RECENT_LIMIT,
});
return (
<div>
<BackLink href="/admin" label={t.adminAuditLogPage.backToDashboard} />
<h1 className="text-2xl text-forest mb-2">{t.adminAuditLogPage.title}</h1>
<p className="text-sm text-ink/65 mb-8">{t.adminAuditLogPage.intro(RECENT_LIMIT)}</p>
<div className="overflow-x-auto bg-background border border-ink/10">
<table className="w-full text-sm">
<thead className="text-left text-ink/65 border-b border-ink/10">
<tr>
<th className="p-4">{t.adminAuditLogPage.colTimestamp}</th>
<th className="p-4">{t.adminAuditLogPage.colAdmin}</th>
<th className="p-4">{t.adminAuditLogPage.colAction}</th>
<th className="p-4">{t.adminAuditLogPage.colObject}</th>
<th className="p-4">{t.adminAuditLogPage.colDetails}</th>
</tr>
</thead>
<tbody className="divide-y divide-ink/10">
{entries.map((entry) => (
<tr key={entry.id} className="transition-colors hover:bg-ink/[0.03]">
<td className="p-4 whitespace-nowrap text-ink/70">{formatDateTime(entry.createdAt)}</td>
<td className="p-4 whitespace-nowrap">{entry.adminEmail}</td>
<td className="p-4 whitespace-nowrap font-mono text-xs text-forest">{entry.action}</td>
<td className="p-4 whitespace-nowrap text-ink/70">
{entry.entityType}
{entry.entityId ? <span className="text-ink/65"> #{entry.entityId.slice(-6)}</span> : null}
</td>
<td className="p-4 max-w-sm">{entry.detail ?? "—"}</td>
</tr>
))}
</tbody>
</table>
{entries.length === 0 ? <p className="p-6 text-ink/65">{t.adminAuditLogPage.noEntriesYet}</p> : null}
</div>
</div>
);
}