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.
53 lines
2.3 KiB
TypeScript
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>
|
|
);
|
|
}
|