Merge branch origin/codex/draft-initial-prism-console-design into main

This commit is contained in:
Alexa Amundson
2025-11-24 01:04:43 -06:00
13 changed files with 632 additions and 54 deletions

View File

@@ -0,0 +1,44 @@
import Panel from "@/components/Panel";
import StatusPill, { Status } from "@/components/StatusPill";
const agents: { name: string; capabilities: string; status: Status; lastRun: string }[] = [
{ name: "atlas", capabilities: "retrieval, summarization", status: "healthy", lastRun: "2m ago" },
{ name: "spectrum", capabilities: "graphs, timelines", status: "running", lastRun: "7m ago" },
{ name: "trace", capabilities: "provenance, SIG", status: "degraded", lastRun: "15m ago" },
];
export default function AgentsPage() {
return (
<div className="space-y-6">
<div>
<p className="text-xs uppercase tracking-[0.3em] text-zinc-500">Agents</p>
<h1 className="text-3xl font-bold text-white">Active agents</h1>
<p className="text-zinc-400 mt-2">Line of sight across the agent village, including hot and failing workers.</p>
</div>
<Panel title="Roster" subtitle="Agents, capabilities, and last activity">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{agents.map((agent) => (
<div key={agent.name} className="rounded-2xl border border-zinc-800 bg-zinc-900/60 p-4 hover:border-emerald-500/40">
<div className="flex items-center justify-between">
<div>
<p className="text-lg font-semibold text-white">{agent.name}</p>
<p className="text-xs text-zinc-500">{agent.capabilities}</p>
</div>
<StatusPill status={agent.status as any} />
</div>
<div className="mt-4 flex items-center justify-between text-xs text-zinc-400">
<span>Last run</span>
<span className="font-mono text-sm text-zinc-100">{agent.lastRun}</span>
</div>
<div className="mt-3 flex items-center gap-2 text-xs text-zinc-300">
<button className="rounded-full border border-zinc-700 px-3 py-1 hover:border-emerald-500/40 hover:text-emerald-100">Inspect</button>
<button className="rounded-full border border-zinc-700 px-3 py-1 hover:border-emerald-500/40 hover:text-emerald-100">Open logs</button>
</div>
</div>
))}
</div>
</Panel>
</div>
);
}

View File

@@ -1,15 +1,51 @@
import Panel from "@/components/Panel";
import StatusPill, { Status } from "@/components/StatusPill";
const deployments: { id: string; target: string; status: Status; when: string }[] = [
{ id: "deploy-152", target: "stage", status: "running", when: "Now" },
{ id: "deploy-151", target: "stage", status: "healthy", when: "3h ago" },
{ id: "deploy-150", target: "prod", status: "healthy", when: "12h ago" },
];
export default function DeploymentsPage() { export default function DeploymentsPage() {
return ( return (
<div> <div className="space-y-6">
<h1 className="text-3xl font-bold mb-4">Deployments</h1> <div>
<p className="text-zinc-600 dark:text-zinc-400 mb-8"> <p className="text-xs uppercase tracking-[0.3em] text-zinc-500">Deploy</p>
Track and manage your application deployments. <h1 className="text-3xl font-bold text-white">Deployments</h1>
</p> <p className="text-zinc-400 mt-2">Track history, velocity, and current pushes across environments.</p>
<div className="border border-zinc-200 dark:border-zinc-800 rounded-lg p-6">
<p className="text-sm text-zinc-500 dark:text-zinc-500">
Deployment history and controls will be available here.
</p>
</div> </div>
<Panel title="Recent deployments" subtitle="Latest pushes into stage and prod">
<div className="overflow-hidden rounded-xl border border-zinc-800">
<table className="w-full text-sm">
<thead className="bg-zinc-900/60 text-zinc-400">
<tr>
<th className="px-4 py-3 text-left font-medium">Deployment</th>
<th className="px-4 py-3 text-left font-medium">Target</th>
<th className="px-4 py-3 text-left font-medium">When</th>
<th className="px-4 py-3 text-left font-medium">Status</th>
<th className="px-4 py-3 text-left font-medium">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-zinc-800 bg-black/20">
{deployments.map((deploy) => (
<tr key={deploy.id} className="hover:bg-zinc-900/40">
<td className="px-4 py-3 font-mono text-sm text-white">{deploy.id}</td>
<td className="px-4 py-3 text-zinc-300 uppercase">{deploy.target}</td>
<td className="px-4 py-3 text-zinc-300">{deploy.when}</td>
<td className="px-4 py-3"><StatusPill status={deploy.status as any} /></td>
<td className="px-4 py-3 text-xs">
<button className="rounded-full border border-zinc-700 px-3 py-1 text-zinc-200 hover:border-emerald-500/40 hover:text-emerald-100">
Rollback
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</Panel>
</div> </div>
); );
} }

View File

@@ -1,15 +1,28 @@
import Panel from "@/components/Panel";
export default function EnvironmentsPage() { export default function EnvironmentsPage() {
return ( return (
<div> <div className="space-y-6">
<h1 className="text-3xl font-bold mb-4">Environments</h1> <div>
<p className="text-zinc-600 dark:text-zinc-400 mb-8"> <p className="text-xs uppercase tracking-[0.3em] text-zinc-500">Deploy</p>
Manage your deployment environments here. <h1 className="text-3xl font-bold text-white">Environments</h1>
</p> <p className="text-zinc-400 mt-2">Promote and observe the dev / stage / prod worldlines.</p>
<div className="border border-zinc-200 dark:border-zinc-800 rounded-lg p-6">
<p className="text-sm text-zinc-500 dark:text-zinc-500">
Environment configuration and management will be available here.
</p>
</div> </div>
<Panel title="Environment matrix" subtitle="dev → stage → prod">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{["dev", "stage", "prod"].map((env) => (
<div key={env} className="rounded-2xl border border-zinc-800 bg-zinc-900/60 p-4">
<p className="text-lg font-semibold text-white uppercase">{env}</p>
<p className="text-xs text-zinc-500">Configuration coming soon.</p>
<div className="mt-4 flex items-center gap-2 text-xs text-zinc-300">
<button className="rounded-full border border-zinc-700 px-3 py-1 hover:border-emerald-500/40 hover:text-emerald-100">View config</button>
<button className="rounded-full border border-zinc-700 px-3 py-1 hover:border-emerald-500/40 hover:text-emerald-100">Deploy</button>
</div>
</div>
))}
</div>
</Panel>
</div> </div>
); );
} }

View File

@@ -0,0 +1,56 @@
import Panel from "@/components/Panel";
import StatusPill, { Status } from "@/components/StatusPill";
const anchors: { anchor: string; bound: string; status: Status; worldline: string }[] = [
{ anchor: "PS-SHA∞:0x9af...c12", bound: "atlas", status: "healthy", worldline: "WL-17" },
{ anchor: "PS-SHA∞:0x7db...a45", bound: "spectrum", status: "running", worldline: "WL-08" },
{ anchor: "PS-SHA∞:0x5c2...fe1", bound: "trace", status: "investigating", worldline: "WL-03" },
];
export default function IdentityPage() {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<p className="text-xs uppercase tracking-[0.3em] text-zinc-500">Identity</p>
<h1 className="text-3xl font-bold text-white">SIG / PS-SHA</h1>
<p className="text-zinc-400 mt-2">Map programs, runs, and agents back to their provenance anchors.</p>
</div>
<button className="rounded-full border border-emerald-500/40 bg-emerald-500/10 px-4 py-2 text-sm font-medium text-emerald-100 hover:bg-emerald-500/20">
Open worldline
</button>
</div>
<Panel title="Anchors" subtitle="Verified identity bindings">
<div className="overflow-hidden rounded-xl border border-zinc-800">
<table className="w-full text-sm">
<thead className="bg-zinc-900/60 text-zinc-400">
<tr>
<th className="px-4 py-3 text-left font-medium">Anchor</th>
<th className="px-4 py-3 text-left font-medium">Bound entity</th>
<th className="px-4 py-3 text-left font-medium">Worldline</th>
<th className="px-4 py-3 text-left font-medium">Status</th>
<th className="px-4 py-3 text-left font-medium">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-zinc-800 bg-black/20">
{anchors.map((anchor) => (
<tr key={anchor.anchor} className="hover:bg-zinc-900/40">
<td className="px-4 py-3 font-mono text-xs text-white">{anchor.anchor}</td>
<td className="px-4 py-3 text-zinc-300">{anchor.bound}</td>
<td className="px-4 py-3 text-zinc-300">{anchor.worldline}</td>
<td className="px-4 py-3"><StatusPill status={anchor.status as any} /></td>
<td className="px-4 py-3 text-xs">
<button className="rounded-full border border-zinc-700 px-3 py-1 text-zinc-200 hover:border-emerald-500/40 hover:text-emerald-100">
Trace
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</Panel>
</div>
);
}

View File

@@ -0,0 +1,84 @@
import Panel from "@/components/Panel";
import StatusPill, { Status } from "@/components/StatusPill";
const queues: { name: string; depth: number; status: Status }[] = [
{ name: "default", depth: 42, status: "running" },
{ name: "priority", depth: 6, status: "healthy" },
{ name: "slow", depth: 18, status: "degraded" },
];
const jobs: { id: string; queue: string; status: Status; runtime: string }[] = [
{ id: "JOB-2033", queue: "priority", status: "running", runtime: "00:48" },
{ id: "JOB-2031", queue: "default", status: "healthy", runtime: "03:11" },
{ id: "JOB-2029", queue: "slow", status: "degraded", runtime: "12:45" },
];
export default function JobsPage() {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<p className="text-xs uppercase tracking-[0.3em] text-zinc-500">Operator</p>
<h1 className="text-3xl font-bold text-white">Jobs & Queues</h1>
<p className="text-zinc-400 mt-2">Borrowed from operator: queue depth, inflight work, and retry heat.</p>
</div>
<button className="rounded-full border border-emerald-500/40 bg-emerald-500/10 px-4 py-2 text-sm font-medium text-emerald-100 hover:bg-emerald-500/20">
Drain queue
</button>
</div>
<Panel title="Queues" subtitle="Depth and health by queue">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{queues.map((queue) => (
<div key={queue.name} className="rounded-2xl border border-zinc-800 bg-zinc-900/60 p-4 hover:border-emerald-500/40">
<div className="flex items-center justify-between">
<div>
<p className="text-lg font-semibold text-white">{queue.name}</p>
<p className="text-xs text-zinc-500">Queue depth</p>
</div>
<StatusPill status={queue.status as any} />
</div>
<p className="mt-3 text-3xl font-semibold text-white">{queue.depth}</p>
<p className="text-xs text-zinc-500">messages</p>
<div className="mt-4 flex items-center gap-2 text-xs text-zinc-300">
<button className="rounded-full border border-zinc-700 px-3 py-1 hover:border-emerald-500/40 hover:text-emerald-100">Inspect</button>
<button className="rounded-full border border-zinc-700 px-3 py-1 hover:border-emerald-500/40 hover:text-emerald-100">Retry all</button>
</div>
</div>
))}
</div>
</Panel>
<Panel title="Recent jobs" subtitle="Last 10 jobs across queues">
<div className="overflow-hidden rounded-xl border border-zinc-800">
<table className="w-full text-sm">
<thead className="bg-zinc-900/60 text-zinc-400">
<tr>
<th className="px-4 py-3 text-left font-medium">Job</th>
<th className="px-4 py-3 text-left font-medium">Queue</th>
<th className="px-4 py-3 text-left font-medium">Runtime</th>
<th className="px-4 py-3 text-left font-medium">Status</th>
<th className="px-4 py-3 text-left font-medium">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-zinc-800 bg-black/20">
{jobs.map((job) => (
<tr key={job.id} className="hover:bg-zinc-900/40">
<td className="px-4 py-3 font-mono text-sm text-white">{job.id}</td>
<td className="px-4 py-3 text-zinc-300">{job.queue}</td>
<td className="px-4 py-3 text-zinc-300">{job.runtime}</td>
<td className="px-4 py-3"><StatusPill status={job.status as any} /></td>
<td className="px-4 py-3 text-xs">
<button className="rounded-full border border-zinc-700 px-3 py-1 text-zinc-200 hover:border-emerald-500/40 hover:text-emerald-100">
Retry
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</Panel>
</div>
);
}

View File

@@ -1,6 +1,7 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import "./globals.css"; import "./globals.css";
import Sidebar from "@/components/Sidebar"; import Sidebar from "@/components/Sidebar";
import TopBar from "@/components/TopBar";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Prism Console - BlackRoad OS", title: "Prism Console - BlackRoad OS",
@@ -14,12 +15,15 @@ export default function RootLayout({
}>) { }>) {
return ( return (
<html lang="en"> <html lang="en">
<body className="antialiased"> <body className="antialiased bg-[#05060a] text-zinc-100">
<div className="flex"> <div className="flex min-h-screen bg-gradient-to-br from-[#0b0d14] via-[#0a0b12] to-[#05060a]">
<Sidebar /> <Sidebar />
<main className="flex-1 p-8"> <div className="flex-1 flex flex-col border-l border-[#111827] bg-black/20 backdrop-blur-xl">
{children} <TopBar />
</main> <main className="flex-1 p-8">
{children}
</main>
</div>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -0,0 +1,63 @@
import Panel from "@/components/Panel";
import StatusPill, { Status } from "@/components/StatusPill";
const programs: { name: string; id: string; version: string; owner: string; status: Status }[] = [
{ name: "Atlas-extract", id: "LCP-023", version: "1.4.2", owner: "atlas", status: "healthy" },
{ name: "Sigmaplex sync", id: "LCP-011", version: "2.0.0", owner: "trace", status: "degraded" },
{ name: "Worldline audit", id: "LCP-007", version: "0.9.8", owner: "spectrum", status: "healthy" },
];
export default function LucidiaProgramsPage() {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<p className="text-xs uppercase tracking-[0.3em] text-zinc-500">Lucidia</p>
<h1 className="text-3xl font-bold text-white">Programs</h1>
<p className="text-zinc-400 mt-2">Catalog of Lucidia programs available in this environment.</p>
</div>
<button className="rounded-full border border-emerald-500/40 bg-emerald-500/10 px-4 py-2 text-sm font-medium text-emerald-100 hover:bg-emerald-500/20">
New run
</button>
</div>
<Panel subtitle="Name, id, owner, version" title="Program catalog">
<div className="overflow-hidden rounded-xl border border-zinc-800">
<table className="w-full text-sm">
<thead className="bg-zinc-900/60 text-zinc-400">
<tr>
<th className="px-4 py-3 text-left font-medium">Program</th>
<th className="px-4 py-3 text-left font-medium">ID</th>
<th className="px-4 py-3 text-left font-medium">Version</th>
<th className="px-4 py-3 text-left font-medium">Owner</th>
<th className="px-4 py-3 text-left font-medium">Status</th>
<th className="px-4 py-3 text-left font-medium">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-zinc-800 bg-black/20">
{programs.map((program) => (
<tr key={program.id} className="hover:bg-zinc-900/40">
<td className="px-4 py-3 text-white font-medium">{program.name}</td>
<td className="px-4 py-3 font-mono text-xs text-zinc-400">{program.id}</td>
<td className="px-4 py-3 text-zinc-300">{program.version}</td>
<td className="px-4 py-3 text-zinc-300">{program.owner}</td>
<td className="px-4 py-3"><StatusPill status={program.status as any} /></td>
<td className="px-4 py-3">
<div className="flex items-center gap-2 text-xs">
<button className="rounded-full border border-zinc-700 px-3 py-1 text-zinc-200 hover:border-emerald-500/40 hover:text-emerald-100">
Open
</button>
<button className="rounded-full border border-zinc-700 px-3 py-1 text-zinc-200 hover:border-emerald-500/40 hover:text-emerald-100">
Run
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</Panel>
</div>
);
}

View File

@@ -0,0 +1,53 @@
import Panel from "@/components/Panel";
import StatusPill, { Status } from "@/components/StatusPill";
const runs: { id: string; program: string; status: Status; duration: string; environment: string }[] = [
{ id: "RUN-8421", program: "Atlas-extract", status: "running", duration: "08:24", environment: "stage" },
{ id: "RUN-8418", program: "Sigmaplex sync", status: "degraded", duration: "32:15", environment: "stage" },
{ id: "RUN-8413", program: "Worldline audit", status: "healthy", duration: "12:03", environment: "prod" },
];
export default function LucidiaRunsPage() {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<p className="text-xs uppercase tracking-[0.3em] text-zinc-500">Lucidia</p>
<h1 className="text-3xl font-bold text-white">Runs & Windows</h1>
<p className="text-zinc-400 mt-2">Nested execution, window-in-window state, and quick pivots to nodes.</p>
</div>
<button className="rounded-full border border-emerald-500/40 bg-emerald-500/10 px-4 py-2 text-sm font-medium text-emerald-100 hover:bg-emerald-500/20">
Launch run
</button>
</div>
<Panel title="Active runs" subtitle="Click through to inspect nodes and logs">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{runs.map((run) => (
<div key={run.id} className="rounded-2xl border border-zinc-800 bg-zinc-900/60 p-4 hover:border-emerald-500/40">
<div className="flex items-center justify-between">
<div>
<p className="font-mono text-sm text-white">{run.id}</p>
<p className="text-xs text-zinc-500">{run.program}</p>
</div>
<StatusPill status={run.status as any} />
</div>
<div className="mt-3 flex items-center justify-between text-xs text-zinc-400">
<span>Duration</span>
<span className="font-mono text-sm text-zinc-100">{run.duration}</span>
</div>
<div className="mt-2 flex items-center justify-between text-xs text-zinc-400">
<span>Environment</span>
<span className="rounded-full border border-zinc-700 px-2 py-1 text-emerald-100 bg-emerald-500/10">{run.environment}</span>
</div>
<div className="mt-4 flex items-center gap-2 text-xs text-zinc-300">
<button className="rounded-full border border-zinc-700 px-3 py-1 hover:border-emerald-500/40 hover:text-emerald-100">Open window</button>
<button className="rounded-full border border-zinc-700 px-3 py-1 hover:border-emerald-500/40 hover:text-emerald-100">View graph</button>
</div>
</div>
))}
</div>
</Panel>
</div>
);
}

View File

@@ -1,29 +1,122 @@
import Panel from "@/components/Panel";
import StatusPill, { Status } from "@/components/StatusPill";
export default function Home() { export default function Home() {
const health: { name: string; status: Status; detail: string }[] = [
{ name: "Core", status: "healthy", detail: "All signals within thresholds" },
{ name: "Operator", status: "degraded", detail: "Retry spike detected" },
{ name: "Web", status: "healthy", detail: "All edges responding" },
{ name: "Infra", status: "investigating", detail: "Reviewing overnight alerts" },
];
const metrics = [
{ label: "Active runs", value: 18, trend: "+4 vs last hour" },
{ label: "Failed runs (24h)", value: 3, trend: "-2 vs baseline" },
{ label: "Queue depth", value: 42, trend: "+11 inflight" },
{ label: "Agents online", value: 12, trend: "3 running hot" },
];
const live: { title: string; time: string; status: Status }[] = [
{ title: "Window #5 recovered", time: "1m ago", status: "healthy" },
{ title: "Agent atlas retrying", time: "4m ago", status: "degraded" },
{ title: "New SIG anchor published", time: "12m ago", status: "running" },
];
return ( return (
<div> <div className="space-y-8">
<h1 className="text-3xl font-bold mb-4">Welcome to Prism Console</h1> <div>
<p className="text-zinc-600 dark:text-zinc-400"> <p className="text-xs uppercase tracking-[0.3em] text-zinc-500">Prism Console</p>
Manage your BlackRoad OS environments, deployments, and observability from this console. <h1 className="text-4xl font-bold text-white">System Overview</h1>
</p> <p className="text-zinc-400 mt-2 max-w-3xl">
<div className="mt-8 grid grid-cols-1 md:grid-cols-3 gap-6"> The glass cockpit for Lucidia programs, nested runs, agents, and the operator stack. Everything you
<div className="border border-zinc-200 dark:border-zinc-800 rounded-lg p-6"> and your agents need to steer the worldlines in one place.
<h2 className="text-xl font-semibold mb-2">Environments</h2> </p>
<p className="text-sm text-zinc-600 dark:text-zinc-400"> </div>
View and manage your deployment environments
</p> <div className="grid grid-cols-1 xl:grid-cols-3 gap-6">
</div> <Panel title="Health" subtitle="Core, operator, web, infra at a glance">
<div className="border border-zinc-200 dark:border-zinc-800 rounded-lg p-6"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<h2 className="text-xl font-semibold mb-2">Deployments</h2> {health.map((item) => (
<p className="text-sm text-zinc-600 dark:text-zinc-400"> <div key={item.name} className="flex items-start justify-between rounded-xl border border-zinc-800 bg-zinc-900/40 p-4">
Track and control your application deployments <div>
</p> <p className="text-sm text-zinc-400">{item.name}</p>
</div> <p className="text-sm text-zinc-500 mt-1">{item.detail}</p>
<div className="border border-zinc-200 dark:border-zinc-800 rounded-lg p-6"> </div>
<h2 className="text-xl font-semibold mb-2">Logs</h2> <StatusPill status={item.status} />
<p className="text-sm text-zinc-600 dark:text-zinc-400"> </div>
Monitor and analyze system logs ))}
</p> </div>
</div> </Panel>
<Panel title="Ops pulse" subtitle="Runs, agents, queues" action={<a className="text-emerald-300 hover:text-emerald-200" href="/lucidia/runs">Open runs</a>}>
<div className="grid grid-cols-2 gap-4">
{metrics.map((metric) => (
<div key={metric.label} className="rounded-xl border border-zinc-800 bg-zinc-900/50 p-4">
<p className="text-sm text-zinc-400">{metric.label}</p>
<p className="text-3xl font-semibold text-white mt-1">{metric.value}</p>
<p className="text-xs text-emerald-300 mt-1">{metric.trend}</p>
</div>
))}
</div>
</Panel>
<Panel title="Live signals" subtitle="Last 15 minutes" action={<a className="text-emerald-300 hover:text-emerald-200" href="/jobs">Jump to jobs</a>}>
<div className="space-y-3">
{live.map((event) => (
<div key={event.title} className="flex items-center justify-between rounded-xl border border-zinc-800 bg-zinc-900/40 px-4 py-3">
<div>
<p className="text-sm font-medium text-white">{event.title}</p>
<p className="text-xs text-zinc-500">{event.time}</p>
</div>
<StatusPill status={event.status} />
</div>
))}
</div>
</Panel>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<Panel title="Lucidia programs" subtitle="Catalog of programs ready to run" action={<a className="text-emerald-300 hover:text-emerald-200" href="/lucidia/programs">Browse all</a>}>
<div className="space-y-3">
{["Atlas-extract", "Sigmaplex sync", "Worldline audit"].map((program) => (
<div key={program} className="flex items-center justify-between rounded-lg border border-zinc-800 bg-zinc-900/40 px-4 py-3">
<div>
<p className="font-medium text-white">{program}</p>
<p className="text-xs text-zinc-500">Lucidia program</p>
</div>
<button className="text-xs font-medium text-emerald-200 border border-emerald-500/50 rounded-full px-3 py-1 hover:bg-emerald-500/10">
Open
</button>
</div>
))}
</div>
</Panel>
<Panel title="Runs & windows" subtitle="Nested execution view" action={<a className="text-emerald-300 hover:text-emerald-200" href="/lucidia/runs">View runs</a>}>
<div className="space-y-3">
{["RUN-8421", "RUN-8418", "RUN-8413"].map((run) => (
<div key={run} className="flex items-center justify-between rounded-lg border border-zinc-800 bg-zinc-900/40 px-4 py-3">
<div>
<p className="font-mono text-sm text-white">{run}</p>
<p className="text-xs text-zinc-500">Lucidia window-in-window</p>
</div>
<StatusPill status="running" />
</div>
))}
</div>
</Panel>
<Panel title="Agents" subtitle="Online + hot + failing" action={<a className="text-emerald-300 hover:text-emerald-200" href="/agents">Inspect agents</a>}>
<div className="space-y-3">
{["atlas", "spectrum", "trace"].map((agent) => (
<div key={agent} className="flex items-center justify-between rounded-lg border border-zinc-800 bg-zinc-900/40 px-4 py-3">
<div>
<p className="font-medium text-white">{agent}</p>
<p className="text-xs text-zinc-500">Agent</p>
</div>
<StatusPill status="healthy" />
</div>
))}
</div>
</Panel>
</div> </div>
</div> </div>
); );

View File

@@ -0,0 +1,27 @@
import { ReactNode } from "react";
interface PanelProps {
title?: string;
subtitle?: string;
action?: ReactNode;
children: ReactNode;
}
export default function Panel({ title, subtitle, action, children }: PanelProps) {
return (
<section className="rounded-2xl border border-zinc-800 bg-zinc-900/70 shadow-[0_20px_60px_-25px_rgba(0,0,0,0.6)]">
{(title || action || subtitle) && (
<div className="flex items-start justify-between gap-4 border-b border-zinc-800 px-5 py-4">
<div>
{title && <h2 className="text-lg font-semibold text-zinc-100">{title}</h2>}
{subtitle && <p className="text-sm text-zinc-500">{subtitle}</p>}
</div>
{action && <div className="text-sm text-zinc-400">{action}</div>}
</div>
)}
<div className="px-5 py-4 text-zinc-200">
{children}
</div>
</section>
);
}

View File

@@ -4,19 +4,25 @@ import Link from "next/link";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
const navItems = [ const navItems = [
{ name: "Overview", href: "/" },
{ name: "Lucidia Programs", href: "/lucidia/programs" },
{ name: "Runs & Windows", href: "/lucidia/runs" },
{ name: "Agents", href: "/agents" },
{ name: "Jobs & Queues", href: "/jobs" },
{ name: "Identity / SIG", href: "/identity" },
{ name: "Environments", href: "/environments" }, { name: "Environments", href: "/environments" },
{ name: "Deployments", href: "/deployments" }, { name: "Deployments", href: "/deployments" },
{ name: "Logs", href: "/logs" },
]; ];
export default function Sidebar() { export default function Sidebar() {
const pathname = usePathname(); const pathname = usePathname();
return ( return (
<aside className="w-64 bg-zinc-900 text-white min-h-screen p-6"> <aside className="w-72 bg-black/50 text-white min-h-screen p-6 border-r border-[#111827] backdrop-blur-xl">
<div className="mb-8"> <div className="mb-8">
<h1 className="text-xl font-bold">Prism Console</h1> <p className="text-xs uppercase tracking-[0.2em] text-zinc-500">BlackRoad OS</p>
<p className="text-sm text-zinc-400 mt-1">BlackRoad OS</p> <h1 className="text-2xl font-bold text-white">Prism Console</h1>
<p className="text-sm text-zinc-400 mt-1">Control room</p>
</div> </div>
<nav> <nav>
<ul className="space-y-2"> <ul className="space-y-2">
@@ -24,10 +30,10 @@ export default function Sidebar() {
<li key={item.href}> <li key={item.href}>
<Link <Link
href={item.href} href={item.href}
className={`block px-4 py-2 rounded transition-colors ${ className={`block px-4 py-2 rounded-xl transition-colors border ${
pathname === item.href pathname === item.href
? "bg-zinc-700 text-white" ? "bg-emerald-500/10 text-emerald-100 border-emerald-500/40"
: "text-zinc-300 hover:bg-zinc-800" : "text-zinc-300 border-transparent hover:border-zinc-800 hover:bg-zinc-900"
}`} }`}
> >
{item.name} {item.name}

View File

@@ -0,0 +1,26 @@
export type Status = "healthy" | "degraded" | "failed" | "running" | "idle" | "investigating";
interface StatusPillProps {
status: Status;
label?: string;
}
const statusStyles: Record<Status, string> = {
healthy: "bg-emerald-500/15 text-emerald-200 border-emerald-500/40",
degraded: "bg-amber-500/15 text-amber-200 border-amber-500/40",
failed: "bg-rose-500/15 text-rose-200 border-rose-500/40",
running: "bg-sky-500/15 text-sky-200 border-sky-500/40",
idle: "bg-zinc-700/40 text-zinc-200 border-zinc-600/70",
investigating: "bg-indigo-500/15 text-indigo-200 border-indigo-500/40",
};
export default function StatusPill({ status, label }: StatusPillProps) {
return (
<span
className={`inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs font-medium ${statusStyles[status]}`}
>
<span className="h-2 w-2 rounded-full bg-current" aria-hidden />
<span className="capitalize">{label ?? status}</span>
</span>
);
}

View File

@@ -0,0 +1,73 @@
"use client";
import { useState } from "react";
const environments = ["dev", "stage", "prod"];
const healthIndicators = [
{ label: "core", status: "healthy" },
{ label: "operator", status: "degraded" },
{ label: "web", status: "healthy" },
{ label: "infra", status: "investigating" },
];
const statusColor: Record<string, string> = {
healthy: "bg-emerald-500/20 text-emerald-200 border-emerald-500/40",
degraded: "bg-amber-500/20 text-amber-200 border-amber-500/40",
investigating: "bg-sky-500/20 text-sky-200 border-sky-500/40",
};
export default function TopBar() {
const [environment, setEnvironment] = useState("stage");
return (
<header className="flex items-center justify-between px-8 py-4 border-b border-[#111827] bg-black/30 backdrop-blur">
<div className="flex items-center gap-6">
<div>
<p className="text-xs uppercase tracking-[0.2em] text-zinc-500">Environment</p>
<div className="flex items-center gap-2 mt-1">
{environments.map((env) => (
<button
key={env}
onClick={() => setEnvironment(env)}
className={`rounded-full px-3 py-1 text-xs font-medium border transition-colors duration-200 ${
environment === env
? "bg-emerald-500/10 border-emerald-400 text-emerald-100"
: "border-zinc-800 text-zinc-400 hover:text-zinc-100 hover:border-zinc-700"
}`}
>
{env}
</button>
))}
</div>
</div>
<div className="hidden md:flex items-center gap-3">
{healthIndicators.map((item) => (
<div
key={item.label}
className={`flex items-center gap-2 rounded-full border px-3 py-1 text-xs font-medium ${
statusColor[item.status]
}`}
>
<span className="h-2 w-2 rounded-full bg-current" aria-hidden />
<span className="capitalize">{item.label}</span>
<span className="text-[11px] uppercase tracking-wide opacity-80">{item.status}</span>
</div>
))}
</div>
</div>
<div className="flex items-center gap-4">
<div className="hidden md:flex items-center gap-2 rounded-full border border-zinc-800 bg-zinc-900/60 px-3 py-1 text-xs text-zinc-400">
<span className="text-zinc-500">Search</span>
<span className="font-mono text-[11px] text-zinc-500">/</span>
</div>
<div className="flex items-center gap-3 rounded-full border border-zinc-800 bg-zinc-900/60 px-3 py-1 text-xs text-zinc-300">
<span className="h-2.5 w-2.5 rounded-full bg-emerald-400" aria-hidden />
<div className="flex flex-col leading-tight">
<span className="text-[11px] uppercase tracking-[0.2em] text-zinc-500">Identity</span>
<span className="font-mono text-sm text-zinc-200">founder@blackroad</span>
</div>
</div>
</div>
</header>
);
}