feat: add admin operations suite
Local Build and Deploy / deploy (push) Successful in 50s

This commit is contained in:
Simo committed 2026-07-12 20:11:28 +02:00
1 parent 21a61068fb
commit 60278b9e71
38 files changed
+3452

No files matched your search

+83
View File
@@ -0,0 +1,83 @@
import { redirect } from 'next/navigation'
import { Card, CardContent } from '@/components/ui/card'
import { canAccess, getAdminContext, PERMS } from '@/lib/permissions'
import { prisma } from '@/lib/prisma'
export default async function EmulatorErrorsPage({
searchParams,
}: {
searchParams: Promise<Record<string, string>>
}) {
const { session, permissions } = await getAdminContext()
if (!canAccess(permissions, PERMS.DEVOPS_VIEW, session.user.rank)) {
redirect('/admin')
}
const params = await searchParams
const page = Math.max(1, Number(params.page) || 1)
const perPage = 50
const [errors, total] = await Promise.all([
prisma.emulatorErrors.findMany({
orderBy: { id: 'desc' },
skip: (page - 1) * perPage,
take: perPage,
}),
prisma.emulatorErrors.count(),
])
const lastPage = Math.ceil(total / perPage)
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold tracking-tight">Emulator Errors</h1>
<p className="text-muted-foreground">
{total} total errors — showing page {page} of {lastPage}
</p>
</div>
<Card>
<CardContent className="p-0">
{errors.length === 0 ? (
<div className="p-6 text-center text-muted-foreground">No emulator errors found</div>
) : (
<div className="divide-y">
{errors.map((error) => (
<div key={error.id} className="p-4">
<div className="flex items-center justify-between mb-2">
<span className="font-mono text-xs text-muted-foreground">#{error.id}</span>
<span className="text-xs text-muted-foreground">
{new Date(error.timestamp * 1000).toLocaleString()}
</span>
</div>
<pre className="text-xs whitespace-pre-wrap break-all font-mono bg-muted p-2 rounded">
{Buffer.from(error.stacktrace).toString('utf-8')}
</pre>
</div>
))}
</div>
)}
</CardContent>
</Card>
{lastPage > 1 && (
<div className="flex justify-center gap-2">
{page > 1 && (
<a href={`?page=${page - 1}`} className="text-sm text-primary hover:underline">
Previous
</a>
)}
<span className="text-sm text-muted-foreground">
Page {page} of {lastPage}
</span>
{page < lastPage && (
<a href={`?page=${page + 1}`} className="text-sm text-primary hover:underline">
Next
</a>
)}
</div>
)}
</div>
)
}