- Remove .prettierrc (dead config, Biome replaces Prettier) - Rename lighthouserc.json to lighthouserc.cjs with module.exports for ESM compat - Add logger.warn to empty catch blocks in auth, register, site-settings, prisma-cache, redis, security, rate-limit - Unify ActionResult type: action-helper.ts uses 'ok' consistent with safe-action-shared.ts - Add noUnusedLocals + noUnusedParameters to tsconfig + fix 25 pre-existing unused vars - Replace barrel export src/types/index.ts with direct @/types/common imports - Make trustHost conditional (development only) in auth.ts - Add pre-flight URL validation to update-Nitrov3.sh to catch image.library.url misconfigurations - Improve NITRO_IMAGE_LIBRARY_URL content validation in pre-flight & post-compute checks
91 lines
2.4 KiB
TypeScript
91 lines
2.4 KiB
TypeScript
import { redirect } from "next/navigation";
|
|
import { getTranslations } from "next-intl/server";
|
|
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 t = await getTranslations("pages.admin.devops");
|
|
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">
|
|
<p className="text-muted-foreground">
|
|
{total} total errors — showing page {page} of {lastPage}
|
|
</p>
|
|
|
|
<Card>
|
|
<CardContent className="p-0">
|
|
{errors.length === 0 ? (
|
|
<div className="admin-empty">{t("noEmulatorErrors")}</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>
|
|
);
|
|
}
|