Create Prism console control room overview
This commit is contained in:
44
frontend/app/agents/page.tsx
Normal file
44
frontend/app/agents/page.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
56
frontend/app/identity/page.tsx
Normal file
56
frontend/app/identity/page.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
84
frontend/app/jobs/page.tsx
Normal file
84
frontend/app/jobs/page.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
63
frontend/app/lucidia/programs/page.tsx
Normal file
63
frontend/app/lucidia/programs/page.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
53
frontend/app/lucidia/runs/page.tsx
Normal file
53
frontend/app/lucidia/runs/page.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
27
frontend/components/Panel.tsx
Normal file
27
frontend/components/Panel.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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}
|
||||||
|
|||||||
26
frontend/components/StatusPill.tsx
Normal file
26
frontend/components/StatusPill.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
73
frontend/components/TopBar.tsx
Normal file
73
frontend/components/TopBar.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user