feat: Initialize Next.js project with Prisma, Tailwind CSS, and API routes for device management and testing.

This commit is contained in:
2025-12-22 22:21:27 -05:00
commit 2e415d1897
33 changed files with 8222 additions and 0 deletions

152
src/app/admin/page.tsx Normal file
View File

@@ -0,0 +1,152 @@
"use client";
import { useState, useEffect } from "react";
import { Shield, Smartphone, ArrowRight, Settings2, PlusCircle, CheckCircle2, XCircle, Clock } from "lucide-react";
interface Device {
id: string;
serialNumber: string;
macAddress: string;
name: string;
status: string;
firmwareVersion: string;
activeSlot: string;
}
export default function AdminPage() {
const [devices, setDevices] = useState<Device[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch("/api/devices")
.then(res => res.json())
.then(data => {
setDevices(data);
setLoading(false);
});
}, []);
const handleEnroll = async (id: string) => {
const res = await fetch(`/api/devices/${id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ status: "ENROLLED" }),
});
if (res.ok) {
setDevices(devices.map(d => d.id === id ? { ...d, status: "ENROLLED" } : d));
}
};
return (
<div className="space-y-8">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold tracking-tight mb-2 flex items-center gap-3">
<Shield className="w-8 h-8 text-sky-500" />
Command Center
</h1>
<p className="text-slate-400">Manage device fleet, enrollment, and firmware orchestration.</p>
</div>
<button className="bg-sky-500 hover:bg-sky-400 text-white px-4 py-2 rounded-xl text-sm font-bold flex items-center gap-2 transition-all shadow-[0_0_20px_rgba(56,189,248,0.2)]">
<PlusCircle className="w-4 h-4" />
Proactive Provisioning
</button>
</div>
<div className="grid grid-cols-1 gap-6">
{/* Device Management Table */}
<div className="bg-white/[0.02] border border-white/5 rounded-3xl overflow-hidden backdrop-blur-md">
<div className="px-6 py-4 border-b border-white/5 bg-white/[0.01] flex items-center justify-between">
<h2 className="text-xs font-bold uppercase tracking-widest text-slate-500 italic">Connected Hardware Tree</h2>
</div>
<div className="overflow-x-auto">
<table className="w-full text-left">
<thead>
<tr className="border-b border-white/5 text-[10px] font-bold uppercase tracking-widest text-slate-600">
<th className="px-6 py-4">Hardware ID</th>
<th className="px-6 py-4">Designation</th>
<th className="px-6 py-4">Architecture</th>
<th className="px-6 py-4">Orchestration</th>
<th className="px-6 py-4">Deployment</th>
<th className="px-6 py-4 text-right">Control</th>
</tr>
</thead>
<tbody className="divide-y divide-white/[0.02]">
{loading ? (
<tr>
<td colSpan={6} className="px-6 py-12 text-center text-slate-500">Scanning network for head units...</td>
</tr>
) : devices.length === 0 ? (
<tr>
<td colSpan={6} className="px-6 py-12 text-center text-slate-500">No devices detected in local mesh.</td>
</tr>
) : (
devices.map((device) => (
<tr key={device.id} className="hover:bg-white/[0.01] transition-colors group">
<td className="px-6 py-4">
<div className="flex items-center gap-3">
<div className={`w-2 h-2 rounded-full ${device.status === 'ENROLLED' ? 'bg-sky-400' : 'bg-amber-400 animate-pulse'}`} />
<div className="flex flex-col">
<span className="font-mono text-[10px] text-slate-400">{device.macAddress}</span>
<span className="text-xs font-bold text-slate-200">{device.serialNumber}</span>
</div>
</div>
</td>
<td className="px-6 py-4">
<input
type="text"
defaultValue={device.name}
className="bg-transparent border-b border-white/5 text-xs focus:border-sky-500 outline-none w-full pb-1 transition-colors"
placeholder="Assign static name..."
/>
</td>
<td className="px-6 py-4 text-[10px] font-mono text-slate-500 uppercase">
RPi Zero 2 W
</td>
<td className="px-6 py-4">
<div className="flex items-center gap-1.5">
<Settings2 className="w-3.5 h-3.5 text-slate-500" />
<span className="text-[10px] font-bold text-slate-400 bg-white/5 py-0.5 px-2 rounded uppercase">
Slot {device.activeSlot} (v{device.firmwareVersion})
</span>
</div>
</td>
<td className="px-6 py-4">
{device.status === "ENROLLED" ? (
<span className="text-[10px] font-black text-emerald-500/80 tracking-tighter uppercase flex items-center gap-1">
<CheckCircle2 className="w-3 h-3" /> Enrolled
</span>
) : (
<span className="text-[10px] font-black text-amber-500/80 tracking-tighter uppercase flex items-center gap-1">
<Clock className="w-3 h-3" /> Awaiting
</span>
)}
</td>
<td className="px-6 py-4 text-right">
{device.status === "PENDING" ? (
<button
onClick={() => handleEnroll(device.id)}
className="bg-sky-500/10 hover:bg-sky-500/20 text-sky-400 text-[10px] font-black py-1.5 px-3 rounded uppercase transition-colors flex items-center gap-2 ml-auto"
>
Enroll <ArrowRight className="w-3 h-3" />
</button>
) : (
<button
className="text-slate-600 hover:text-rose-500 transition-colors"
title="Decommission Hardware"
>
<XCircle className="w-4 h-4" />
</button>
)}
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,24 @@
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
export async function GET() {
try {
const [deviceCount, testCount, onlineCount, recentTests] = await Promise.all([
prisma.device.count(),
prisma.test.count(),
prisma.device.count({ where: { status: "ENROLLED" } }),
prisma.test.findMany({
take: 10,
orderBy: { timestamp: "desc" },
include: { device: true },
}),
]);
return NextResponse.json({
stats: { deviceCount, testCount, onlineCount },
recentTests,
});
} catch (error) {
return NextResponse.json({ error: "Internal Server Error" }, { status: 500 });
}
}

View File

@@ -0,0 +1,24 @@
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
export async function PATCH(
req: Request,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params;
const { status, name } = await req.json();
const device = await prisma.device.update({
where: { id },
data: {
status: status || undefined,
name: name || undefined
},
});
return NextResponse.json(device);
} catch (error) {
return NextResponse.json({ error: "Internal Server Error" }, { status: 500 });
}
}

View File

@@ -0,0 +1,44 @@
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
export async function POST(req: Request) {
try {
const { serialNumber, macAddress } = await req.json();
if (!serialNumber || !macAddress) {
return NextResponse.json(
{ error: "Serial number and MAC address are required" },
{ status: 400 }
);
}
// Check if device already exists
let device = await prisma.device.findFirst({
where: {
OR: [
{ serialNumber },
{ macAddress }
]
}
});
if (!device) {
// Register as pending
device = await prisma.device.create({
data: {
serialNumber,
macAddress,
status: "PENDING",
},
});
}
return NextResponse.json(device);
} catch (error) {
console.error("Registration error:", error);
return NextResponse.json(
{ error: "Internal Server Error" },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,13 @@
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
export async function GET() {
try {
const devices = await prisma.device.findMany({
orderBy: { createdAt: "desc" },
});
return NextResponse.json(devices);
} catch (error) {
return NextResponse.json({ error: "Internal Server Error" }, { status: 500 });
}
}

View File

@@ -0,0 +1,58 @@
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
export async function POST(req: Request) {
try {
const {
serialNumber,
type,
status,
hdmi5v,
edid1080p,
edid4k120,
diodeResults,
rawOutput,
summary
} = await req.json();
const device = await prisma.device.findUnique({
where: { serialNumber }
});
if (!device) {
return NextResponse.json({ error: "Device not found" }, { status: 404 });
}
if (device.status !== "ENROLLED") {
return NextResponse.json({ error: "Device not enrolled" }, { status: 403 });
}
const test = await prisma.test.create({
data: {
deviceId: device.id,
type,
status,
hdmi5v,
edid1080p,
edid4k120,
diodeResults: diodeResults ? JSON.stringify(diodeResults) : null,
rawOutput,
summary,
},
});
// Update last heartbeat
await prisma.device.update({
where: { id: device.id },
data: { lastHeartbeat: new Date() }
});
return NextResponse.json(test);
} catch (error) {
console.error("Test reporting error:", error);
return NextResponse.json(
{ error: "Internal Server Error" },
{ status: 500 }
);
}
}

BIN
src/app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

26
src/app/globals.css Normal file
View File

@@ -0,0 +1,26 @@
@import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}

55
src/app/layout.tsx Normal file
View File

@@ -0,0 +1,55 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "HDMI Tester | Enterprise Dashboard",
description: "Advanced HDMI testing and modular device management",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" className="dark" suppressHydrationWarning>
<body className={`${inter.className} bg-[#0a0a0c] text-slate-200 min-h-screen antialiased`} suppressHydrationWarning>
<div className="fixed inset-0 bg-[radial-gradient(circle_at_50%_0%,_rgba(56,189,248,0.08),transparent_50%)] pointer-events-none" />
<div className="relative flex flex-col min-h-screen">
<nav className="border-b border-white/5 bg-[#0a0a0c]/80 backdrop-blur-xl sticky top-0 z-50">
<div className="container mx-auto px-6 h-16 flex items-center justify-between">
<div className="flex items-center gap-2 group cursor-pointer">
<div className="w-8 h-8 bg-sky-500 rounded-lg flex items-center justify-center group-hover:shadow-[0_0_20px_rgba(56,189,248,0.4)] transition-all duration-300">
<span className="text-white font-bold text-lg">H</span>
</div>
<span className="text-xl font-bold tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-white to-white/60">
HDMI<span className="text-sky-400">Tester</span>
</span>
</div>
<div className="flex items-center gap-8">
<a href="/" className="text-sm font-medium text-slate-400 hover:text-white transition-colors">Dashboard</a>
<a href="/admin" className="text-sm font-medium text-slate-400 hover:text-white transition-colors">Admin</a>
<div className="h-4 w-px bg-white/10" />
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
<span className="text-xs font-semibold uppercase tracking-widest text-slate-500">Live</span>
</div>
</div>
</div>
</nav>
<main className="flex-1 container mx-auto px-6 py-10">
{children}
</main>
<footer className="border-t border-white/5 py-8 mt-auto">
<div className="container mx-auto px-6 text-center text-slate-500 text-xs">
&copy; 2025 HDMI Tester Enterprise. All rights reserved.
</div>
</footer>
</div>
</body>
</html>
);
}

257
src/app/page.tsx Normal file
View File

@@ -0,0 +1,257 @@
"use client";
import { useState, useEffect } from "react";
import { Activity, Cpu, CheckCircle2, AlertCircle, Clock, Zap } from "lucide-react";
import Link from "next/link";
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
interface Device {
id: string;
name: string | null;
serialNumber: string;
status: string;
}
interface Test {
id: string;
type: string;
status: string;
summary: string;
timestamp: string;
device: Device;
}
interface DashboardData {
stats: {
deviceCount: number;
testCount: number;
onlineCount: number;
};
recentTests: Test[];
}
export default function DashboardPage() {
const [data, setData] = useState<DashboardData | null>(null);
const [loading, setLoading] = useState(true);
const [lastUpdate, setLastUpdate] = useState<Date>(new Date());
const [isUpdating, setIsUpdating] = useState(false);
const fetchData = async () => {
try {
setIsUpdating(true);
const res = await fetch("/api/dashboard/stats");
const newData = await res.json();
setData(newData);
setLastUpdate(new Date());
setLoading(false);
setTimeout(() => setIsUpdating(false), 1000);
} catch (error) {
console.error("Failed to fetch dashboard data:", error);
}
};
useEffect(() => {
fetchData();
const interval = setInterval(fetchData, 3000); // Poll every 3 seconds
return () => clearInterval(interval);
}, []);
if (loading || !data) {
return (
<div className="flex items-center justify-center min-h-[60vh]">
<div className="flex flex-col items-center gap-4">
<div className="w-12 h-12 border-4 border-sky-500/20 border-t-sky-500 rounded-full animate-spin" />
<p className="text-slate-500 font-mono text-xs uppercase tracking-widest animate-pulse">Syncing with hardware mesh...</p>
</div>
</div>
);
}
return (
<div className="space-y-10">
{/* Header with Live Indicator */}
<div className="flex items-center justify-between">
<h1 className="text-sm font-black uppercase tracking-[0.3em] text-slate-500 flex items-center gap-3">
<span className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-sky-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-sky-500"></span>
</span>
Real-time Telemetry
</h1>
<div className="text-[10px] font-mono text-slate-600 uppercase">
Last sync: {lastUpdate.toLocaleTimeString()}
</div>
</div>
{/* Hero Stats */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{[
{ label: "Active Nodes", value: data.stats.onlineCount, icon: Activity, color: "text-sky-400", glow: "shadow-sky-500/10" },
{ label: "Tests Conducted", value: data.stats.testCount, icon: Zap, color: "text-amber-400", glow: "shadow-amber-500/10" },
{ label: "Registered Hardware", value: data.stats.deviceCount, icon: Cpu, color: "text-purple-400", glow: "shadow-purple-500/10" },
].map((stat, i) => (
<div
key={i}
className={cn(
"bg-white/[0.03] border border-white/5 rounded-2xl p-6 backdrop-blur-sm relative overflow-hidden group transition-all duration-700",
isUpdating && "scale-[1.01] bg-white/[0.05] border-white/10",
stat.glow
)}
>
<div className="absolute inset-0 bg-gradient-to-br from-white/[0.02] to-transparent pointer-events-none" />
<div className="flex items-center justify-between relative z-10">
<div>
<p className="text-slate-500 text-xs font-bold uppercase tracking-widest mb-1">{stat.label}</p>
<h3 className={cn(
"text-4xl font-bold tracking-tight transition-all duration-500",
isUpdating ? "scale-110 " + stat.color : "text-white"
)}>
{stat.value}
</h3>
</div>
<stat.icon className={cn(
"w-10 h-10 transition-all duration-700",
isUpdating ? "opacity-100 scale-110" : "opacity-20",
stat.color
)} />
</div>
</div>
))}
</div>
{/* Main Content Grid */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Recent Activity Feed */}
<div className="lg:col-span-2 space-y-4">
<div className="flex items-center justify-between mb-2">
<h2 className="text-xl font-bold flex items-center gap-2">
<Clock className="w-5 h-5 text-sky-400" />
Latest Test Sequences
</h2>
</div>
<div className="bg-white/[0.02] border border-white/5 rounded-3xl overflow-hidden backdrop-blur-md">
<table className="w-full text-left">
<thead>
<tr className="border-b border-white/5 bg-white/[0.01]">
<th className="px-6 py-4 text-xs font-bold uppercase tracking-widest text-slate-500">Source</th>
<th className="px-6 py-4 text-xs font-bold uppercase tracking-widest text-slate-500">Operation</th>
<th className="px-6 py-4 text-xs font-bold uppercase tracking-widest text-slate-500">Status</th>
<th className="px-6 py-4 text-xs font-bold uppercase tracking-widest text-slate-500">Metric</th>
<th className="px-6 py-4 text-xs font-bold uppercase tracking-widest text-slate-500">Execution</th>
<th className="px-6 py-4 text-xs font-bold uppercase tracking-widest text-slate-500"></th>
</tr>
</thead>
<tbody className="divide-y divide-white/[0.02]">
{data.recentTests.length === 0 ? (
<tr>
<td colSpan={6} className="px-6 py-12 text-center text-slate-500 italic">No telemetry data reported yet.</td>
</tr>
) : (
data.recentTests.map((test) => (
<tr key={test.id} className="hover:bg-white/[0.02] transition-colors group animate-in fade-in slide-in-from-left-2 duration-500">
<td className="px-6 py-4">
<div className="flex flex-col">
<span className="font-semibold text-slate-200">{test.device.name || "Head Unit"}</span>
<span className="text-[10px] text-slate-500 font-mono">{test.device.serialNumber}</span>
</div>
</td>
<td className="px-6 py-4">
<span className="text-sm px-2 py-1 rounded-md bg-white/5 border border-white/5 text-slate-300 font-medium whitespace-nowrap">
{test.type.replace("_", " ")}
</span>
</td>
<td className="px-6 py-4">
{test.status === "PASS" ? (
<div className="flex items-center gap-1.5 text-emerald-400 text-sm font-bold">
<CheckCircle2 className="w-4 h-4" />
SUCCESS
</div>
) : (
<div className="flex items-center gap-1.5 text-rose-400 text-sm font-bold">
<AlertCircle className="w-4 h-4" />
FAILURE
</div>
)}
</td>
<td className="px-6 py-4">
<span className="text-xs text-slate-400 max-w-[200px] truncate block">{test.summary}</span>
</td>
<td className="px-6 py-4">
<div className="flex flex-col">
<span className="text-[10px] font-bold text-slate-400 uppercase">
{new Date(test.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
</span>
<span className="text-[9px] text-slate-600 font-medium">
{new Date(test.timestamp).toLocaleDateString([], { month: 'short', day: 'numeric' })}
</span>
</div>
</td>
<td className="px-6 py-4 text-right">
<Link
href={`/tests/${test.id}`}
className="text-xs font-bold text-sky-400 hover:text-sky-300 underline-offset-4 hover:underline"
>
INSIGHTS
</Link>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</div>
{/* Sidebar */}
<div className="space-y-6">
<div className="bg-sky-500/10 border border-sky-500/20 rounded-2xl p-6 relative overflow-hidden">
<div className="absolute top-0 right-0 p-4 opacity-10">
<Zap className="w-16 h-16 text-sky-400" />
</div>
<h4 className="font-bold text-sky-400 mb-2 flex items-center gap-2 relative z-10">
<Zap className="w-4 h-4" />
Intelligence Engine
</h4>
<p className="text-xs text-slate-400 leading-relaxed mb-4 relative z-10">
Real-time spectral analysis of HDMI bus metrics. All processing orchestrated in the cloud with sub-ms edge latency.
</p>
<div className="flex items-center gap-2 py-2 border-t border-sky-500/10 relative z-10">
<span className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span>
</span>
<span className="text-[10px] font-bold uppercase tracking-wider text-sky-200">Processing Engine Active</span>
</div>
</div>
<div className="bg-white/[0.03] border border-white/5 rounded-2xl p-6">
<div className="flex items-center justify-between mb-4">
<h4 className="font-bold text-slate-200 text-sm uppercase tracking-widest">Fleet Control</h4>
<Link href="/admin" className="text-[10px] text-sky-400 hover:underline uppercase font-bold">Manage Hub</Link>
</div>
<div className="space-y-4">
<div className="p-3 bg-white/5 rounded-xl border border-white/5 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-sky-500/10 flex items-center justify-center">
<Cpu className="w-4 h-4 text-sky-400" />
</div>
<div className="flex flex-col">
<span className="text-[10px] font-bold text-slate-300">Edge Provisioning</span>
<span className="text-[8px] text-slate-500 uppercase">Auto-Scale enabled</span>
</div>
</div>
<div className="w-1.5 h-1.5 rounded-full bg-emerald-500" />
</div>
</div>
</div>
</div>
</div>
</div>
);
}

139
src/app/tests/[id]/page.tsx Normal file
View File

@@ -0,0 +1,139 @@
import { prisma } from "@/lib/prisma";
import { notFound } from "next/navigation";
import { CheckCircle2, AlertCircle, Clock, Smartphone, Info, ShieldCheck, Gauge, ExternalLink } from "lucide-react";
import Link from "next/link";
async function getTestData(id: string) {
const test = await prisma.test.findUnique({
where: { id },
include: { device: true },
});
return test;
}
export default async function TestDetailsPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const test = await getTestData(id);
if (!test) {
notFound();
}
const diodeResults = test.diodeResults ? JSON.parse(test.diodeResults) as Record<string, number> : undefined;
return (
<div className="space-y-8 pb-20">
<div className="flex items-center gap-4 text-sm font-medium text-slate-500 mb-2">
<Link href="/" className="hover:text-sky-400 transition-colors">Dashboard</Link>
<span>/</span>
<span className="text-slate-300">Test Insights</span>
</div>
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div className="space-y-2">
<div className="flex items-center gap-4">
<h1 className="text-3xl font-bold tracking-tight text-white">
Sequence Analysis: <span className="font-mono text-sky-400">{test.id.slice(-8).toUpperCase()}</span>
</h1>
{test.status === "PASS" ? (
<span className="bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 px-3 py-1 rounded-full text-xs font-black uppercase tracking-widest flex items-center gap-1.5">
<CheckCircle2 className="w-3.5 h-3.5" /> Nominal
</span>
) : (
<span className="bg-rose-500/10 text-rose-400 border border-rose-500/20 px-3 py-1 rounded-full text-xs font-black uppercase tracking-widest flex items-center gap-1.5">
<AlertCircle className="w-3.5 h-3.5" /> Anomaly Detected
</span>
)}
</div>
<p className="text-slate-400 flex items-center gap-2">
<Smartphone className="w-4 h-4" />
Origin: <span className="font-bold text-slate-200">{test.device.name || "Unknown Head Unit"}</span>
<span className="text-slate-600 font-mono text-xs">({test.device.serialNumber})</span>
</p>
</div>
<div className="flex items-center gap-3">
<div className="text-right">
<p className="text-[10px] text-slate-500 font-bold uppercase tracking-widest">Execution Timestamp</p>
<p className="text-sm font-bold text-slate-300">{new Date(test.timestamp).toLocaleString()}</p>
</div>
<div className="w-10 h-10 rounded-xl bg-white/5 border border-white/10 flex items-center justify-center">
<Clock className="w-5 h-5 text-slate-400" />
</div>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Spectral Analysis Card */}
<div className="lg:col-span-2 bg-white/[0.02] border border-white/5 rounded-3xl p-8 backdrop-blur-md relative overflow-hidden group">
<div className="absolute top-0 right-0 p-8 opacity-5">
<Gauge className="w-32 h-32" />
</div>
<div className="flex items-center gap-3 mb-8">
<Info className="w-5 h-5 text-sky-400" />
<h2 className="text-xs font-bold uppercase tracking-[0.2em] text-slate-500 italic">Spectral Diode Response</h2>
</div>
{diodeResults ? (
<div className="grid grid-cols-1 md:grid-cols-2 gap-y-6 gap-x-12">
{Object.entries(diodeResults).map(([pin, voltage]) => (
<div key={pin} className="space-y-2">
<div className="flex justify-between items-end text-[10px] font-bold uppercase tracking-wider">
<span className="text-slate-400">{pin.replace("_", " ")}</span>
<span className="text-sky-400 font-mono text-xs">{voltage.toFixed(3)}V</span>
</div>
<div className="h-1.5 w-full bg-white/5 rounded-full overflow-hidden border border-white/5">
<div
className="h-full bg-gradient-to-r from-sky-600 to-sky-400 rounded-full"
style={{ width: `${Math.min((voltage / 0.8) * 100, 100)}%` }}
/>
</div>
</div>
))}
</div>
) : (
<div className="text-center py-12 text-slate-500 italic">
No detailed diode metrics available for this sequence.
</div>
)}
</div>
{/* Intelligence Report Summary */}
<div className="bg-white/[0.02] border border-white/5 rounded-3xl p-8 flex flex-col gap-6">
<div>
<h3 className="text-xs font-bold uppercase tracking-widest text-slate-500 mb-4 flex items-center gap-2">
<ShieldCheck className="w-4 h-4 text-sky-400" />
Validation Report
</h3>
<div className="bg-white/5 border border-white/5 rounded-2xl p-4">
<p className="text-sm italic text-slate-300 leading-relaxed font-serif">
"{test.summary}"
</p>
</div>
</div>
<div className="mt-auto pt-6 border-t border-white/5">
<div className="grid grid-cols-2 gap-4">
<div className="bg-white/[0.01] border border-white/5 rounded-xl p-3">
<p className="text-[10px] font-bold uppercase text-slate-500 mb-1">Architecture</p>
<p className="text-xs font-bold text-slate-300">HDMI v1.4b</p>
</div>
<div className="bg-white/[0.01] border border-white/5 rounded-xl p-3">
<p className="text-[10px] font-bold uppercase text-slate-500 mb-1">Probe Type</p>
<p className="text-xs font-bold text-slate-300">{test.type}</p>
</div>
</div>
<Link
href="/admin"
className="mt-6 flex items-center justify-center gap-2 w-full py-3 bg-white/5 hover:bg-white/10 border border-white/10 rounded-2xl text-[10px] font-black uppercase tracking-widest transition-all text-slate-300"
>
Re-Orchestrate Node <ExternalLink className="w-3 h-3" />
</Link>
</div>
</div>
</div>
</div>
);
}

7
src/lib/prisma.ts Normal file
View File

@@ -0,0 +1,7 @@
import { PrismaClient } from "@prisma/client";
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
export const prisma = globalForPrisma.prisma || new PrismaClient();
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;