feat: Add theme toggle, implement interactive simulator menu, and enhance dashboard stats API.

This commit is contained in:
2025-12-22 23:34:27 -05:00
parent 2e415d1897
commit dae7926eaa
12 changed files with 483 additions and 280 deletions

View File

@@ -107,32 +107,52 @@ def generate_mock_hdmi_test():
} }
if __name__ == "__main__": if __name__ == "__main__":
import sys
hu = HeadUnit() hu = HeadUnit()
if len(sys.argv) < 2: def print_menu():
print("Usage: python simulator.py [register|test|mode-dev|mode-prod|swap|loop]") print("\n" + "="*45)
sys.exit(1) print(f" HDMI TESTER - HARDWARE SIMULATOR (v1.2.0)")
print("="*45)
print(f" Status: [{hu.status}]")
print(f" Mode: [{hu.mode}]")
print(f" Active: [Slot {hu.active_slot}]")
print(f" Serial: [{hu.serial_number}]")
print("-" * 45)
print(" 1. Register / Sync Device")
print(" 2. Report Mock HDMI Test")
print(" 3. Switch to DEV Mode")
print(" 4. Switch to PROD Mode")
print(" 5. Swap Active Slot (A/B)")
print(" 6. Start Automated Loop")
print(" 0. Exit Orchestrator")
print("-" * 45)
cmd = sys.argv[1] while True:
print_menu()
choice = input("Select operation [0-6] >> ").strip()
if cmd == "register": if choice == "1":
hu.register()
elif cmd == "test":
hu.report_test(generate_mock_hdmi_test())
elif cmd == "mode-dev":
hu.switch_mode("DEV")
elif cmd == "mode-prod":
hu.switch_mode("PROD")
elif cmd == "swap":
hu.switch_slot()
elif cmd == "loop":
print("[*] Starting simulator loop. Ctrl+C to exit.")
while True:
hu.register() hu.register()
if hu.status == "ENROLLED": elif choice == "2":
hu.report_test(generate_mock_hdmi_test()) hu.report_test(generate_mock_hdmi_test())
time.sleep(10) elif choice == "3":
else: hu.switch_mode("DEV")
print("Unknown command.") elif choice == "4":
hu.switch_mode("PROD")
elif choice == "5":
hu.switch_slot()
elif choice == "6":
print("\n[*] Entering automated telemetry loop. Press Ctrl+C to return to menu.")
try:
while True:
hu.register()
if hu.status == "ENROLLED":
hu.report_test(generate_mock_hdmi_test())
time.sleep(5)
except KeyboardInterrupt:
print("\n[!] Loop interrupted by user.")
elif choice == "0":
print("[*] Powering down head unit...")
break
else:
print("[!] Invalid selection.")

11
package-lock.json generated
View File

@@ -12,6 +12,7 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"lucide-react": "^0.562.0", "lucide-react": "^0.562.0",
"next": "16.1.1", "next": "16.1.1",
"next-themes": "^0.4.6",
"prisma": "^6.19.1", "prisma": "^6.19.1",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",
@@ -5291,6 +5292,16 @@
} }
} }
}, },
"node_modules/next-themes": {
"version": "0.4.6",
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
"integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
}
},
"node_modules/next/node_modules/postcss": { "node_modules/next/node_modules/postcss": {
"version": "8.4.31", "version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",

View File

@@ -13,6 +13,7 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"lucide-react": "^0.562.0", "lucide-react": "^0.562.0",
"next": "16.1.1", "next": "16.1.1",
"next-themes": "^0.4.6",
"prisma": "^6.19.1", "prisma": "^6.19.1",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",

Binary file not shown.

View File

@@ -1,7 +1,8 @@
"use client"; "use client";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { Shield, Smartphone, ArrowRight, Settings2, PlusCircle, CheckCircle2, XCircle, Clock } from "lucide-react"; import { Shield, ArrowRight, Settings2, PlusCircle, CheckCircle2, XCircle, Clock } from "lucide-react";
import Link from "next/link";
interface Device { interface Device {
id: string; id: string;
@@ -37,105 +38,118 @@ export default function AdminPage() {
} }
}; };
const handleUpdateName = async (id: string, name: string) => {
await fetch(`/api/devices/${id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name }),
});
setDevices(devices.map(d => d.id === id ? { ...d, name } : d));
};
return ( return (
<div className="space-y-8"> <div className="space-y-8 animate-in fade-in duration-700">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<h1 className="text-3xl font-bold tracking-tight mb-2 flex items-center gap-3"> <h1 className="text-3xl font-black tracking-tighter mb-2 flex items-center gap-4 text-foreground uppercase">
<Shield className="w-8 h-8 text-sky-500" /> <div className="w-12 h-12 bg-sky-500/10 border border-sky-500/20 rounded-2xl flex items-center justify-center">
<Shield className="w-6 h-6 text-sky-500" />
</div>
Command Center Command Center
</h1> </h1>
<p className="text-slate-400">Manage device fleet, enrollment, and firmware orchestration.</p> <p className="text-slate-500 font-medium text-sm tracking-wide">Manage device fleet, enrollment, and firmware orchestration.</p>
</div> </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)]"> <button className="bg-sky-500 hover:bg-sky-400 text-white px-6 py-2.5 rounded-2xl text-[10px] font-black uppercase tracking-widest flex items-center gap-3 transition-all shadow-[0_0_25px_rgba(56,189,248,0.2)] hover:scale-105 active:scale-95">
<PlusCircle className="w-4 h-4" /> <PlusCircle className="w-4 h-4" />
Proactive Provisioning Proactive Provisioning
</button> </button>
</div> </div>
<div className="grid grid-cols-1 gap-6"> <div className="grid grid-cols-1 gap-6">
{/* Device Management Table */} <div className="bg-background border border-border rounded-[2rem] overflow-hidden backdrop-blur-xl">
<div className="bg-white/[0.02] border border-white/5 rounded-3xl overflow-hidden backdrop-blur-md"> <div className="px-8 py-6 border-b border-border bg-background/30 flex items-center justify-between">
<div className="px-6 py-4 border-b border-white/5 bg-white/[0.01] flex items-center justify-between"> <h2 className="text-[10px] font-black uppercase tracking-[0.3em] text-secondary">Connected Hardware Tree</h2>
<h2 className="text-xs font-bold uppercase tracking-widest text-slate-500 italic">Connected Hardware Tree</h2>
</div> </div>
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<table className="w-full text-left"> <table className="w-full text-left border-collapse">
<thead> <thead>
<tr className="border-b border-white/5 text-[10px] font-bold uppercase tracking-widest text-slate-600"> <tr className="border-b border-border text-[10px] font-black uppercase tracking-[0.2em] text-secondary">
<th className="px-6 py-4">Hardware ID</th> <th className="px-8 py-5">Hardware ID</th>
<th className="px-6 py-4">Designation</th> <th className="px-8 py-5">Designation</th>
<th className="px-6 py-4">Architecture</th> <th className="px-8 py-5">Architecture</th>
<th className="px-6 py-4">Orchestration</th> <th className="px-8 py-5">Orchestration</th>
<th className="px-6 py-4">Deployment</th> <th className="px-8 py-5">Deployment</th>
<th className="px-6 py-4 text-right">Control</th> <th className="px-8 py-5 text-right">Control</th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-white/[0.02]"> <tbody className="divide-y divide-white/[0.02]">
{loading ? ( {loading ? (
<tr> <tr>
<td colSpan={6} className="px-6 py-12 text-center text-slate-500">Scanning network for head units...</td> <td colSpan={6} className="px-8 py-20 text-center text-slate-500 font-medium italic">Scanning network for head units...</td>
</tr> </tr>
) : devices.length === 0 ? ( ) : devices.length === 0 ? (
<tr> <tr>
<td colSpan={6} className="px-6 py-12 text-center text-slate-500">No devices detected in local mesh.</td> <td colSpan={6} className="px-8 py-20 text-center text-slate-500 font-medium italic">No devices detected in local mesh.</td>
</tr> </tr>
) : ( ) : (
devices.map((device) => ( devices.map((device) => (
<tr key={device.id} className="hover:bg-white/[0.01] transition-colors group"> <tr key={device.id} className="hover:bg-white/[0.03] transition-all group">
<td className="px-6 py-4"> <td className="px-8 py-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-4">
<div className={`w-2 h-2 rounded-full ${device.status === 'ENROLLED' ? 'bg-sky-400' : 'bg-amber-400 animate-pulse'}`} /> <div className={`w-2 h-2 rounded-full shadow-[0_0_8px] ${device.status === 'ENROLLED' ? 'bg-sky-400 shadow-sky-400/50' : 'bg-amber-400 shadow-amber-400/50 animate-pulse'}`} />
<div className="flex flex-col"> <div className="flex flex-col">
<span className="font-mono text-[10px] text-slate-400">{device.macAddress}</span> <span className="text-xs font-black text-slate-200 uppercase tracking-tight">{device.serialNumber}</span>
<span className="text-xs font-bold text-slate-200">{device.serialNumber}</span> <span className="font-mono text-[9px] text-slate-500 uppercase tracking-tighter">{device.macAddress}</span>
</div> </div>
</div> </div>
</td> </td>
<td className="px-6 py-4"> <td className="px-8 py-4">
<input <input
type="text" type="text"
defaultValue={device.name} 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" onBlur={(e) => handleUpdateName(device.id, e.target.value)}
className="bg-secondary/5 border border-border rounded-xl px-4 py-2 text-[11px] text-foreground focus:border-sky-500/50 focus:bg-secondary/10 outline-none w-full transition-all font-bold placeholder:text-secondary/50 placeholder:font-normal"
placeholder="Assign static name..." placeholder="Assign static name..."
/> />
</td> </td>
<td className="px-6 py-4 text-[10px] font-mono text-slate-500 uppercase"> <td className="px-8 py-4 text-[10px] font-black font-mono text-slate-500 uppercase tracking-tighter">
RPi Zero 2 W RPi Zero 2 W
</td> </td>
<td className="px-6 py-4"> <td className="px-8 py-4">
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-3">
<Settings2 className="w-3.5 h-3.5 text-slate-500" /> <Settings2 className="w-4 h-4 text-slate-500" />
<span className="text-[10px] font-bold text-slate-400 bg-white/5 py-0.5 px-2 rounded uppercase"> <span className="text-[9px] font-black text-slate-400 bg-white/5 py-1.5 px-3 rounded-xl border border-white/5 uppercase tracking-widest group-hover:border-sky-500/20 group-hover:text-sky-400 transition-colors">
Slot {device.activeSlot} (v{device.firmwareVersion}) Slot {device.activeSlot} <span className="mx-1.5 opacity-20">|</span> v{device.firmwareVersion}
</span> </span>
</div> </div>
</td> </td>
<td className="px-6 py-4"> <td className="px-8 py-4">
{device.status === "ENROLLED" ? ( {device.status === "ENROLLED" ? (
<span className="text-[10px] font-black text-emerald-500/80 tracking-tighter uppercase flex items-center gap-1"> <div className="flex items-center gap-2 text-emerald-400 text-[10px] font-black uppercase tracking-[0.1em]">
<CheckCircle2 className="w-3 h-3" /> Enrolled <span className="w-1.5 h-1.5 rounded-full bg-emerald-500 shadow-[0_0_10px_rgba(16,185,129,0.5)] animate-pulse" />
</span> Active Mesh
</div>
) : ( ) : (
<span className="text-[10px] font-black text-amber-500/80 tracking-tighter uppercase flex items-center gap-1"> <div className="flex items-center gap-2 text-amber-500 text-[10px] font-black uppercase tracking-[0.1em]">
<Clock className="w-3 h-3" /> Awaiting <span className="w-1.5 h-1.5 rounded-full bg-amber-500 shadow-[0_0_10px_rgba(245,158,11,0.5)] animate-bounce" />
</span> Isolated
</div>
)} )}
</td> </td>
<td className="px-6 py-4 text-right"> <td className="px-8 py-4 text-right">
{device.status === "PENDING" ? ( {device.status === "PENDING" ? (
<button <button
onClick={() => handleEnroll(device.id)} 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" className="bg-sky-500 hover:bg-sky-400 text-white text-[10px] font-black py-2.5 px-5 rounded-2xl uppercase tracking-widest transition-all shadow-[0_0_20px_rgba(56,189,248,0.2)] hover:scale-105 active:scale-95 flex items-center gap-2 ml-auto"
> >
Enroll <ArrowRight className="w-3 h-3" /> Enroll Node <ArrowRight className="w-3 h-3" />
</button> </button>
) : ( ) : (
<button <button
className="text-slate-600 hover:text-rose-500 transition-colors" className="w-10 h-10 rounded-xl flex items-center justify-center text-slate-600 hover:text-rose-500 hover:bg-rose-500/10 transition-all ml-auto group"
title="Decommission Hardware" title="Decommission Hardware"
> >
<XCircle className="w-4 h-4" /> <XCircle className="w-5 h-5 group-hover:scale-110 transition-transform" />
</button> </button>
)} )}
</td> </td>

View File

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

View File

@@ -1,26 +1,31 @@
@import "tailwindcss"; @import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
}
@theme inline { @theme inline {
--color-background: var(--background); --color-background: var(--background);
--color-foreground: var(--foreground); --color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans); --color-border: var(--border);
--font-mono: var(--font-geist-mono); --color-secondary: var(--secondary);
} }
@media (prefers-color-scheme: dark) { :root {
:root { --background: #f8fafc;
--background: #0a0a0a; --foreground: #0f172a;
--foreground: #ededed; --border: rgba(15, 23, 42, 0.08);
} --secondary: #64748b;
}
[class~="dark"] {
--background: #0a0a0c;
--foreground: #f1f5f9;
--border: rgba(255, 255, 255, 0.05);
--secondary: #94a3b8;
}
* {
transition: background-color 0.3s ease, border-color 0.3s ease, color 0.15s ease;
} }
body { body {
background: var(--background); background: var(--background);
color: var(--foreground); color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
} }

View File

@@ -9,46 +9,59 @@ export const metadata: Metadata = {
description: "Advanced HDMI testing and modular device management", description: "Advanced HDMI testing and modular device management",
}; };
import Link from "next/link";
import { ThemeProvider } from "@/components/theme-provider";
import { ThemeToggle } from "@/components/theme-toggle";
export default function RootLayout({ export default function RootLayout({
children, children,
}: Readonly<{ }: Readonly<{
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return ( return (
<html lang="en" className="dark" suppressHydrationWarning> <html lang="en" suppressHydrationWarning>
<body className={`${inter.className} bg-[#0a0a0c] text-slate-200 min-h-screen antialiased`} suppressHydrationWarning> <body className={`${inter.className} min-h-screen antialiased transition-colors duration-300`} suppressHydrationWarning>
<div className="fixed inset-0 bg-[radial-gradient(circle_at_50%_0%,_rgba(56,189,248,0.08),transparent_50%)] pointer-events-none" /> <ThemeProvider
<div className="relative flex flex-col min-h-screen"> attribute="class"
<nav className="border-b border-white/5 bg-[#0a0a0c]/80 backdrop-blur-xl sticky top-0 z-50"> defaultTheme="dark"
<div className="container mx-auto px-6 h-16 flex items-center justify-between"> enableSystem
<div className="flex items-center gap-2 group cursor-pointer"> disableTransitionOnChange
<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 className="fixed inset-0 bg-[radial-gradient(circle_at_50%_0%,_rgba(56,189,248,0.08),transparent_50%)] pointer-events-none" />
</div> <div className="relative flex flex-col min-h-screen">
<span className="text-xl font-bold tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-white to-white/60"> <nav className="border-b border-border bg-background/80 backdrop-blur-xl sticky top-0 z-50">
HDMI<span className="text-sky-400">Tester</span> <div className="container mx-auto px-6 h-16 flex items-center justify-between">
</span> <Link href="/" className="flex items-center gap-2 group cursor-pointer transition-opacity hover:opacity-80">
</div> <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">
<div className="flex items-center gap-8"> <span className="text-white font-bold text-lg">H</span>
<a href="/" className="text-sm font-medium text-slate-400 hover:text-white transition-colors">Dashboard</a> </div>
<a href="/admin" className="text-sm font-medium text-slate-400 hover:text-white transition-colors">Admin</a> <span className="text-xl font-bold tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-foreground to-foreground/60">
<div className="h-4 w-px bg-white/10" /> HDMI<span className="text-sky-400">Tester</span>
<div className="flex items-center gap-2"> </span>
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" /> </Link>
<span className="text-xs font-semibold uppercase tracking-widest text-slate-500">Live</span> <div className="flex items-center gap-8">
<Link href="/" className="text-sm font-medium text-secondary hover:text-foreground transition-colors">Dashboard</Link>
<Link href="/admin" className="text-sm font-medium text-secondary hover:text-foreground transition-colors">Admin</Link>
<div className="h-4 w-px bg-border md:block hidden" />
<div className="flex items-center gap-2 pr-2">
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse shadow-[0_0_8px_rgba(34,197,94,0.5)]" />
<span className="text-xs font-semibold uppercase tracking-widest text-slate-500">Live</span>
</div>
<div className="h-4 w-px bg-border" />
<ThemeToggle />
</div> </div>
</div> </div>
</div> </nav>
</nav> <main className="flex-1 container mx-auto px-6 py-10">
<main className="flex-1 container mx-auto px-6 py-10"> {children}
{children} </main>
</main> <footer className="border-t border-border py-8 mt-auto">
<footer className="border-t border-white/5 py-8 mt-auto"> <div className="container mx-auto px-6 text-center text-secondary text-xs">
<div className="container mx-auto px-6 text-center text-slate-500 text-xs"> &copy; 2025 HDMI Tester Enterprise. All rights reserved.
&copy; 2025 HDMI Tester Enterprise. All rights reserved. </div>
</div> </footer>
</footer> </div>
</div> </ThemeProvider>
</body> </body>
</html> </html>
); );

View File

@@ -33,6 +33,7 @@ interface DashboardData {
onlineCount: number; onlineCount: number;
}; };
recentTests: Test[]; recentTests: Test[];
enrolledDevices: Device[];
} }
export default function DashboardPage() { export default function DashboardPage() {
@@ -57,34 +58,53 @@ export default function DashboardPage() {
useEffect(() => { useEffect(() => {
fetchData(); fetchData();
const interval = setInterval(fetchData, 3000); // Poll every 3 seconds const interval = setInterval(fetchData, 3000);
return () => clearInterval(interval); return () => clearInterval(interval);
}, []); }, []);
if (loading || !data) { if (loading || !data) {
return ( return (
<div className="flex items-center justify-center min-h-[60vh]"> <div className="flex items-center justify-center min-h-[60vh]">
<div className="flex flex-col items-center gap-4"> <div className="flex flex-col items-center gap-6">
<div className="w-12 h-12 border-4 border-sky-500/20 border-t-sky-500 rounded-full animate-spin" /> <div className="relative">
<p className="text-slate-500 font-mono text-xs uppercase tracking-widest animate-pulse">Syncing with hardware mesh...</p> <div className="w-16 h-16 border-4 border-sky-500/10 border-t-sky-500 rounded-full animate-spin" />
<div className="absolute inset-0 flex items-center justify-center">
<Zap className="w-6 h-6 text-sky-500 animate-pulse" />
</div>
</div>
<div className="text-center space-y-2">
<p className="text-slate-300 font-bold uppercase tracking-[0.2em] text-sm">Synchronizing Fleet</p>
<p className="text-slate-500 font-mono text-[10px]">Establishing secure tunnel to edge nodes...</p>
</div>
</div> </div>
</div> </div>
); );
} }
return ( return (
<div className="space-y-10"> <div className="space-y-10 animate-in fade-in duration-1000">
{/* Header with Live Indicator */} {/* Header with Live Indicator */}
<div className="flex items-center justify-between"> <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"> <div className="flex items-center gap-4">
<span className="relative flex h-2 w-2"> <div className="px-3 py-1 bg-sky-500/10 border border-sky-500/20 rounded-full flex items-center gap-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-sky-400 opacity-75"></span> <span className="relative flex h-2 w-2">
<span className="relative inline-flex rounded-full h-2 w-2 bg-sky-500"></span> <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-sky-400 opacity-75"></span>
</span> <span className="relative inline-flex rounded-full h-2 w-2 bg-sky-500"></span>
Real-time Telemetry </span>
</h1> <span className="text-[10px] font-black uppercase tracking-widest text-sky-400">Mesh Encoded</span>
<div className="text-[10px] font-mono text-slate-600 uppercase"> </div>
Last sync: {lastUpdate.toLocaleTimeString()} <h1 className="text-lg font-black uppercase tracking-[0.2em] text-foreground">
Operational Telemetry
</h1>
</div>
<div className="flex items-center gap-3">
<div className="text-[10px] font-mono text-slate-500 uppercase tracking-tighter">
Last sync: <span className="text-slate-300">{lastUpdate.toLocaleTimeString()}</span>
</div>
<div className={cn(
"w-2 h-2 rounded-full transition-all duration-500",
isUpdating ? "bg-sky-400 shadow-[0_0_8px_rgba(56,189,248,0.8)]" : "bg-white/10"
)} />
</div> </div>
</div> </div>
@@ -98,27 +118,32 @@ export default function DashboardPage() {
<div <div
key={i} key={i}
className={cn( 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", "bg-background border border-border 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", isUpdating && "bg-background/80 border-sky-500/20",
stat.glow stat.glow
)} )}
> >
<div className="absolute inset-0 bg-gradient-to-br from-white/[0.02] to-transparent pointer-events-none" /> <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 className="flex items-center justify-between relative z-10">
<div> <div>
<p className="text-slate-500 text-xs font-bold uppercase tracking-widest mb-1">{stat.label}</p> <p className="text-slate-500 text-[10px] font-black uppercase tracking-[0.2em] mb-2">{stat.label}</p>
<h3 className={cn( <h3 className={cn(
"text-4xl font-bold tracking-tight transition-all duration-500", "text-4xl font-black tracking-tighter transition-all duration-500 font-mono",
isUpdating ? "scale-110 " + stat.color : "text-white" isUpdating ? stat.color : "text-white"
)}> )}>
{stat.value} {stat.value.toString().padStart(2, '0')}
</h3> </h3>
</div> </div>
<stat.icon className={cn( <div className={cn(
"w-10 h-10 transition-all duration-700", "w-12 h-12 rounded-xl flex items-center justify-center transition-all duration-700",
isUpdating ? "opacity-100 scale-110" : "opacity-20", isUpdating ? "bg-white/10 scale-110" : "bg-white/5"
stat.color )}>
)} /> <stat.icon className={cn(
"w-6 h-6 transition-all duration-700",
isUpdating ? "opacity-100" : "opacity-40",
stat.color
)} />
</div>
</div> </div>
</div> </div>
))} ))}
@@ -129,65 +154,71 @@ export default function DashboardPage() {
{/* Recent Activity Feed */} {/* Recent Activity Feed */}
<div className="lg:col-span-2 space-y-4"> <div className="lg:col-span-2 space-y-4">
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<h2 className="text-xl font-bold flex items-center gap-2"> <h2 className="text-sm font-black uppercase tracking-[0.2em] flex items-center gap-3 text-slate-300">
<Clock className="w-5 h-5 text-sky-400" /> <Clock className="w-4 h-4 text-sky-400" />
Latest Test Sequences Live Sequence Stream
</h2> </h2>
<div className="h-px flex-1 mx-6 bg-gradient-to-r from-white/5 to-transparent" />
</div> </div>
<div className="bg-white/[0.02] border border-white/5 rounded-3xl overflow-hidden backdrop-blur-md"> <div className="bg-background border border-border rounded-2xl overflow-hidden backdrop-blur-md">
<table className="w-full text-left"> <table className="w-full text-left border-collapse">
<thead> <thead>
<tr className="border-b border-white/5 bg-white/[0.01]"> <tr className="border-b border-border bg-background/30">
<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-[10px] font-black uppercase tracking-[0.2em] text-slate-500">Node</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-[10px] font-black uppercase tracking-[0.2em] 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-[10px] font-black uppercase tracking-[0.2em] 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-[10px] font-black uppercase tracking-[0.2em] text-slate-500">Execution</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-[10px] font-black uppercase tracking-[0.2em] text-slate-500"></th>
<th className="px-6 py-4 text-xs font-bold uppercase tracking-widest text-slate-500"></th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-white/[0.02]"> <tbody className="divide-y divide-white/[0.02]">
{data.recentTests.length === 0 ? ( {data.recentTests.length === 0 ? (
<tr> <tr>
<td colSpan={6} className="px-6 py-12 text-center text-slate-500 italic">No telemetry data reported yet.</td> <td colSpan={5} className="px-6 py-16 text-center text-slate-500 italic font-medium">No telemetry data reported yet.</td>
</tr> </tr>
) : ( ) : (
data.recentTests.map((test) => ( 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"> <tr key={test.id} className="hover:bg-white/[0.04] transition-all group animate-in fade-in slide-in-from-left-2 duration-500">
<td className="px-6 py-4"> <td className="px-6 py-4">
<div className="flex flex-col"> <div className="flex items-center gap-3">
<span className="font-semibold text-slate-200">{test.device.name || "Head Unit"}</span> <div className="w-8 h-8 rounded-lg bg-white/5 border border-white/5 flex items-center justify-center font-mono text-[10px] font-bold text-sky-400">
<span className="text-[10px] text-slate-500 font-mono">{test.device.serialNumber}</span> HU
</div>
<div className="flex flex-col">
<span className="font-bold text-slate-200 text-xs">
{test.device.name && test.device.name !== "Unnamed Device" ? test.device.name : test.device.serialNumber}
</span>
<span className="text-[9px] text-slate-500 font-mono tracking-tighter uppercase whitespace-nowrap">
ID: {test.device.serialNumber}
</span>
</div>
</div> </div>
</td> </td>
<td className="px-6 py-4"> <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"> <span className="text-[10px] px-2 py-0.5 rounded bg-white/5 border border-white/5 text-slate-400 font-black uppercase tracking-widest whitespace-nowrap">
{test.type.replace("_", " ")} {test.type.replace("_", " ")}
</span> </span>
</td> </td>
<td className="px-6 py-4"> <td className="px-6 py-4">
{test.status === "PASS" ? ( {test.status === "PASS" ? (
<div className="flex items-center gap-1.5 text-emerald-400 text-sm font-bold"> <div className="flex items-center gap-2 text-emerald-400 text-[10px] font-black uppercase tracking-widest">
<CheckCircle2 className="w-4 h-4" /> <span className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse shadow-[0_0_8px_rgba(16,185,129,0.5)]" />
SUCCESS Nominal
</div> </div>
) : ( ) : (
<div className="flex items-center gap-1.5 text-rose-400 text-sm font-bold"> <div className="flex items-center gap-2 text-rose-400 text-[10px] font-black uppercase tracking-widest">
<AlertCircle className="w-4 h-4" /> <span className="w-1.5 h-1.5 rounded-full bg-rose-500 shadow-[0_0_8px_rgba(244,63,94,0.5)]" />
FAILURE Fault
</div> </div>
)} )}
</td> </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"> <td className="px-6 py-4">
<div className="flex flex-col"> <div className="flex flex-col">
<span className="text-[10px] font-bold text-slate-400 uppercase"> <span className="text-[10px] font-bold text-slate-300 font-mono">
{new Date(test.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })} {new Date(test.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false })}
</span> </span>
<span className="text-[9px] text-slate-600 font-medium"> <span className="text-[8px] text-slate-500 font-black uppercase tracking-widest">
{new Date(test.timestamp).toLocaleDateString([], { month: 'short', day: 'numeric' })} {new Date(test.timestamp).toLocaleDateString([], { month: 'short', day: 'numeric' })}
</span> </span>
</div> </div>
@@ -195,9 +226,9 @@ export default function DashboardPage() {
<td className="px-6 py-4 text-right"> <td className="px-6 py-4 text-right">
<Link <Link
href={`/tests/${test.id}`} href={`/tests/${test.id}`}
className="text-xs font-bold text-sky-400 hover:text-sky-300 underline-offset-4 hover:underline" className="px-3 py-1 bg-sky-500/10 hover:bg-sky-500/20 border border-sky-500/20 rounded-lg text-[9px] font-black text-sky-400 uppercase tracking-widest transition-all hover:scale-105 active:scale-95"
> >
INSIGHTS Insights
</Link> </Link>
</td> </td>
</tr> </tr>
@@ -210,44 +241,54 @@ export default function DashboardPage() {
{/* Sidebar */} {/* Sidebar */}
<div className="space-y-6"> <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="bg-gradient-to-br from-sky-500/10 to-transparent border border-sky-500/20 rounded-2xl p-6 relative overflow-hidden">
<div className="absolute top-0 right-0 p-4 opacity-10"> <div className="absolute top-0 right-0 p-4 opacity-5">
<Zap className="w-16 h-16 text-sky-400" /> <Zap className="w-20 h-20 text-sky-400" />
</div> </div>
<h4 className="font-bold text-sky-400 mb-2 flex items-center gap-2 relative z-10"> <h4 className="font-black text-sky-400 text-[10px] uppercase tracking-[0.3em] mb-4 flex items-center gap-2 relative z-10">
<Zap className="w-4 h-4" /> <Activity className="w-3 h-3" />
Intelligence Engine Intelligence Engine
</h4> </h4>
<p className="text-xs text-slate-400 leading-relaxed mb-4 relative z-10"> <div className="space-y-3 relative z-10">
Real-time spectral analysis of HDMI bus metrics. All processing orchestrated in the cloud with sub-ms edge latency. <p className="text-xs text-slate-400 leading-relaxed font-medium">
</p> Real-time spectral analysis of HDMI bus metrics. Processing orchestrated with <span className="text-sky-400 font-bold">sub-ms</span> edge latency.
<div className="flex items-center gap-2 py-2 border-t border-sky-500/10 relative z-10"> </p>
<span className="relative flex h-2 w-2"> <div className="flex items-center gap-2 py-3 border-t border-sky-500/10">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span> <span className="relative flex h-2 w-2">
<span className="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span> <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
</span> <span className="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span>
<span className="text-[10px] font-bold uppercase tracking-wider text-sky-200">Processing Engine Active</span> </span>
<span className="text-[9px] font-black uppercase tracking-[0.15em] text-white">Neural Engine Online</span>
</div>
</div> </div>
</div> </div>
<div className="bg-white/[0.03] border border-white/5 rounded-2xl p-6"> <div className="bg-background border border-border rounded-2xl p-6">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-6">
<h4 className="font-bold text-slate-200 text-sm uppercase tracking-widest">Fleet Control</h4> <h4 className="font-black text-slate-400 text-[10px] uppercase tracking-[0.3em]">Fleet Nodes</h4>
<Link href="/admin" className="text-[10px] text-sky-400 hover:underline uppercase font-bold">Manage Hub</Link> <Link href="/admin" className="text-[9px] text-sky-400 hover:text-sky-300 uppercase font-black tracking-widest">Manage All</Link>
</div> </div>
<div className="space-y-4"> <div className="space-y-3">
<div className="p-3 bg-white/5 rounded-xl border border-white/5 flex items-center justify-between"> {data.enrolledDevices.length === 0 ? (
<div className="flex items-center gap-3"> <p className="text-[10px] text-slate-500 italic text-center py-4 border border-dashed border-white/5 rounded-xl">No active nodes in mesh.</p>
<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" /> data.enrolledDevices.map((device) => (
<div key={device.id} className="p-3 bg-secondary/5 hover:bg-secondary/10 transition-colors rounded-xl border border-border flex items-center justify-between group cursor-default">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-sky-500/10 flex items-center justify-center group-hover:bg-sky-500/20 transition-colors">
<Cpu className="w-4 h-4 text-sky-400" />
</div>
<div className="flex flex-col">
<span className="text-[10px] font-black text-slate-200 uppercase tracking-wider">
{device.name && device.name !== "Unnamed Device" ? device.name : device.serialNumber}
</span>
<span className="text-[8px] text-slate-500 font-mono uppercase tracking-tighter">System Health: Optimal</span>
</div>
</div>
<div className="w-1.5 h-1.5 rounded-full bg-emerald-500 shadow-[0_0_6px_rgba(16,185,129,0.5)]" />
</div> </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> </div>

View File

@@ -1,6 +1,6 @@
import { prisma } from "@/lib/prisma"; import { prisma } from "@/lib/prisma";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { CheckCircle2, AlertCircle, Clock, Smartphone, Info, ShieldCheck, Gauge, ExternalLink } from "lucide-react"; import { CheckCircle2, AlertCircle, Clock, Smartphone, Info, ShieldCheck, Gauge, ExternalLink, Activity } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
async function getTestData(id: string) { async function getTestData(id: string) {
@@ -22,115 +22,158 @@ export default async function TestDetailsPage({ params }: { params: Promise<{ id
const diodeResults = test.diodeResults ? JSON.parse(test.diodeResults) as Record<string, number> : undefined; const diodeResults = test.diodeResults ? JSON.parse(test.diodeResults) as Record<string, number> : undefined;
return ( return (
<div className="space-y-8 pb-20"> <div className="space-y-10 pb-20 animate-in fade-in slide-in-from-bottom-4 duration-1000">
<div className="flex items-center gap-4 text-sm font-medium text-slate-500 mb-2"> <div className="flex items-center gap-3 text-[10px] font-black uppercase tracking-[0.2em] text-secondary">
<Link href="/" className="hover:text-sky-400 transition-colors">Dashboard</Link> <Link href="/" className="hover:text-sky-400 transition-colors flex items-center gap-2">
<span>/</span> <Activity className="w-3 h-3" /> Dashboard
<span className="text-slate-300">Test Insights</span> </Link>
<span className="opacity-20">/</span>
<span className="text-secondary/70">Node Insights</span>
</div> </div>
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6"> <div className="flex flex-col md:flex-row md:items-end justify-between gap-8 pb-8 border-b border-border">
<div className="space-y-2"> <div className="space-y-4">
<div className="flex items-center gap-4"> <div className="flex items-center gap-6">
<h1 className="text-3xl font-bold tracking-tight text-white"> <h1 className="text-5xl font-black tracking-tighter text-foreground font-mono">
Sequence Analysis: <span className="font-mono text-sky-400">{test.id.slice(-8).toUpperCase()}</span> {test.id.slice(-8).toUpperCase()}
</h1> </h1>
{test.status === "PASS" ? ( <div className="flex flex-col gap-2">
<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"> {test.status === "PASS" ? (
<CheckCircle2 className="w-3.5 h-3.5" /> Nominal <span className="bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 px-3 py-1 rounded-lg text-[10px] font-black uppercase tracking-widest flex items-center gap-2 shadow-[0_0_15px_rgba(16,185,129,0.1)]">
</span> <CheckCircle2 className="w-3.5 h-3.5" /> Nominal State
) : ( </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 className="bg-rose-500/10 text-rose-400 border border-rose-500/20 px-3 py-1 rounded-lg text-[10px] font-black uppercase tracking-widest flex items-center gap-2 shadow-[0_0_15px_rgba(244,63,94,0.1)]">
</span> <AlertCircle className="w-3.5 h-3.5" /> Critical Fault
)} </span>
)}
<div className="px-3 py-1 bg-secondary/5 border border-border rounded-lg text-[9px] font-bold text-secondary uppercase tracking-widest">
Sequence Verified
</div>
</div>
</div>
<div className="flex items-center gap-6">
<p className="text-secondary text-xs font-bold flex items-center gap-3 bg-secondary/5 px-4 py-2 rounded-2xl border border-border">
<Smartphone className="w-4 h-4 text-sky-400" />
<span className="text-secondary/80">Node:</span>
<span className="text-foreground">{test.device.name && test.device.name !== "Unnamed Device" ? test.device.name : test.device.serialNumber}</span>
<span className="text-secondary font-mono text-[10px] opacity-60">[{test.device.serialNumber}]</span>
</p>
</div> </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>
<div className="flex items-center gap-3"> <div className="flex items-center gap-6 bg-background border border-border p-4 rounded-3xl backdrop-blur-md">
<div className="text-right"> <div className="text-right">
<p className="text-[10px] text-slate-500 font-bold uppercase tracking-widest">Execution Timestamp</p> <p className="text-[10px] text-secondary font-black uppercase tracking-[0.2em] mb-1">Capture Timestamp</p>
<p className="text-sm font-bold text-slate-300">{new Date(test.timestamp).toLocaleString()}</p> <p className="text-sm font-black text-foreground font-mono tabular-nums">{new Date(test.timestamp).toLocaleString()}</p>
</div> </div>
<div className="w-10 h-10 rounded-xl bg-white/5 border border-white/10 flex items-center justify-center"> <div className="w-12 h-12 rounded-2xl bg-sky-500/10 border border-sky-500/20 flex items-center justify-center text-sky-400">
<Clock className="w-5 h-5 text-slate-400" /> <Clock className="w-6 h-6" />
</div> </div>
</div> </div>
</div> </div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Spectral Analysis Card */} {/* 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="lg:col-span-2 bg-background border border-border rounded-[2.5rem] p-10 backdrop-blur-xl relative overflow-hidden group">
<div className="absolute top-0 right-0 p-8 opacity-5"> <div className="absolute -top-24 -right-24 p-8 opacity-[0.03] transition-transform duration-1000 group-hover:scale-110 group-hover:rotate-12">
<Gauge className="w-32 h-32" /> <Gauge className="w-96 h-96" />
</div> </div>
<div className="flex items-center gap-3 mb-8"> <div className="flex items-center justify-between mb-12">
<Info className="w-5 h-5 text-sky-400" /> <div className="flex items-center gap-3">
<h2 className="text-xs font-bold uppercase tracking-[0.2em] text-slate-500 italic">Spectral Diode Response</h2> <div className="w-10 h-10 rounded-xl bg-sky-500/10 flex items-center justify-center">
<Info className="w-5 h-5 text-sky-400" />
</div>
<h2 className="text-[10px] font-black uppercase tracking-[0.3em] text-secondary">Response Spectrum Analysis</h2>
</div>
<div className="px-4 py-1.5 bg-sky-500/10 border border-sky-500/20 rounded-full text-[9px] font-black text-sky-400 uppercase tracking-widest">
Calibration: 0.8V VDD
</div>
</div> </div>
{diodeResults ? ( {diodeResults ? (
<div className="grid grid-cols-1 md:grid-cols-2 gap-y-6 gap-x-12"> <div className="grid grid-cols-1 md:grid-cols-2 gap-x-16 gap-y-10">
{Object.entries(diodeResults).map(([pin, voltage]) => ( {Object.entries(diodeResults).map(([pin, voltage]) => (
<div key={pin} className="space-y-2"> <div key={pin} className="space-y-4">
<div className="flex justify-between items-end text-[10px] font-bold uppercase tracking-wider"> <div className="flex justify-between items-end">
<span className="text-slate-400">{pin.replace("_", " ")}</span> <div className="flex flex-col">
<span className="text-sky-400 font-mono text-xs">{voltage.toFixed(3)}V</span> <span className="text-[10px] font-black uppercase tracking-[0.15em] text-slate-500 mb-0.5">{pin.replace("_", " ")}</span>
<span className="text-[8px] text-slate-600 font-black uppercase">Spectral Diode Path</span>
</div>
<span className="text-sky-400 font-mono text-lg font-black tracking-tight">{voltage.toFixed(3)}<span className="text-[10px] ml-0.5 opacity-60">V</span></span>
</div> </div>
<div className="h-1.5 w-full bg-white/5 rounded-full overflow-hidden border border-white/5"> <div className="h-3 w-full bg-white/5 rounded-full overflow-hidden p-0.5 border border-white/5">
<div <div
className="h-full bg-gradient-to-r from-sky-600 to-sky-400 rounded-full" className="h-full bg-gradient-to-r from-sky-600 via-sky-400 to-sky-300 rounded-full transition-all duration-1000 shadow-[0_0_12px_rgba(56,189,248,0.3)] relative group-hover:brightness-110"
style={{ width: `${Math.min((voltage / 0.8) * 100, 100)}%` }} style={{ width: `${Math.min((voltage / 0.8) * 100, 100)}%` }}
/> >
<div className="absolute inset-0 bg-gradient-to-b from-white/20 to-transparent opacity-50" />
</div>
</div> </div>
</div> </div>
))} ))}
</div> </div>
) : ( ) : (
<div className="text-center py-12 text-slate-500 italic"> <div className="text-center py-20 flex flex-col items-center gap-4">
No detailed diode metrics available for this sequence. <AlertCircle className="w-12 h-12 text-slate-700" />
<p className="text-slate-500 italic font-bold text-sm">No detailed spectral metrics available for this sequence.</p>
</div> </div>
)} )}
</div> </div>
{/* Intelligence Report Summary */} {/* Intelligence Report Summary */}
<div className="bg-white/[0.02] border border-white/5 rounded-3xl p-8 flex flex-col gap-6"> <div className="space-y-8">
<div> <div className="bg-background border border-border rounded-[2rem] p-8 flex flex-col gap-8 relative overflow-hidden">
<h3 className="text-xs font-bold uppercase tracking-widest text-slate-500 mb-4 flex items-center gap-2"> <div className="absolute top-0 right-0 p-6 opacity-[0.03]">
<ShieldCheck className="w-4 h-4 text-sky-400" /> <ShieldCheck className="w-20 h-20" />
Validation Report </div>
</h3>
<div className="bg-white/5 border border-white/5 rounded-2xl p-4"> <div>
<p className="text-sm italic text-slate-300 leading-relaxed font-serif"> <h3 className="text-[10px] font-black uppercase tracking-[0.3em] text-secondary mb-6 flex items-center gap-3">
"{test.summary}" <ShieldCheck className="w-4 h-4 text-sky-400" />
</p> Validation Report
</h3>
<div className="bg-gradient-to-br from-secondary/5 to-transparent border border-border rounded-3xl p-6 relative group text-foreground">
<p className="text-sm font-bold leading-loose italic">
"{test.summary}"
</p>
<div className="mt-4 pt-4 border-t border-border flex items-center justify-between">
<span className="text-[8px] font-black uppercase text-secondary tracking-widest">Synthesized by AI Engine</span>
<span className="text-emerald-500 text-[8px] font-black uppercase tracking-widest">Authenticated</span>
</div>
</div>
</div>
<div className="pt-4 space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="bg-secondary/5 border border-border rounded-2xl p-4 transition-colors hover:bg-secondary/10">
<p className="text-[9px] font-black uppercase text-secondary mb-2 tracking-widest">Architecture</p>
<p className="text-xs font-black text-foreground">HDMI v1.4b <span className="text-[8px] opacity-40 ml-1">ENCODED</span></p>
</div>
<div className="bg-secondary/5 border border-border rounded-2xl p-4 transition-colors hover:bg-secondary/10">
<p className="text-[9px] font-black uppercase text-secondary mb-2 tracking-widest">Type</p>
<p className="text-xs font-black text-foreground font-mono">{test.type}</p>
</div>
</div>
<Link
href="/admin"
className="flex items-center justify-center gap-3 w-full py-4 bg-sky-500/10 hover:bg-sky-500/20 border border-sky-500/20 rounded-2xl text-[10px] font-black uppercase tracking-[0.2em] transition-all text-sky-400 shadow-sm hover:shadow-sky-500/10 group"
>
Re-Orchestrate Node <ExternalLink className="w-3.5 h-3.5 group-hover:translate-x-0.5 group-hover:-translate-y-0.5 transition-transform" />
</Link>
</div> </div>
</div> </div>
<div className="mt-auto pt-6 border-t border-white/5"> <div className="bg-amber-500/5 border border-amber-500/10 rounded-2xl p-6">
<div className="grid grid-cols-2 gap-4"> <div className="flex items-center gap-3 mb-2">
<div className="bg-white/[0.01] border border-white/5 rounded-xl p-3"> <Info className="w-4 h-4 text-amber-500" />
<p className="text-[10px] font-bold uppercase text-slate-500 mb-1">Architecture</p> <span className="text-[9px] font-black uppercase tracking-widest text-amber-500">Security Advisory</span>
<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> </div>
<p className="text-[10px] text-amber-500/60 leading-relaxed font-medium">
<Link Telemetry data is signed with hardware-level HMAC. Direct physical access to the head unit is required for re-calibration.
href="/admin" </p>
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>
</div> </div>

View File

@@ -0,0 +1,11 @@
"use client";
import * as React from "react";
import { ThemeProvider as NextThemesProvider } from "next-themes";
export function ThemeProvider({
children,
...props
}: React.ComponentProps<typeof NextThemesProvider>) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}

View File

@@ -0,0 +1,38 @@
"use client";
import * as React from "react";
import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";
export function ThemeToggle() {
const { theme, setTheme } = useTheme();
const [mounted, setMounted] = React.useState(false);
// Avoid hydration mismatch
React.useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return (
<div className="w-10 h-10 rounded-xl bg-white/5 border border-white/10" />
);
}
return (
<button
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
className="w-10 h-10 rounded-xl bg-white/5 border border-white/10 flex items-center justify-center text-slate-400 hover:text-white hover:bg-white/10 transition-all duration-300 group relative overflow-hidden"
aria-label="Toggle theme"
>
<div className="absolute inset-0 bg-gradient-to-br from-sky-500/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" />
<div className="relative z-10">
{theme === "dark" ? (
<Sun className="w-5 h-5 animate-in zoom-in duration-300" />
) : (
<Moon className="w-5 h-5 animate-in zoom-in duration-300" />
)}
</div>
</button>
);
}