Skip to content

Commit eb2bf74

Browse files
authored
Merge pull request #67 from ssdeanx/develop
feat: add admin UI components and enhance client identity management
2 parents 3a7d3e7 + 7b285b6 commit eb2bf74

20 files changed

Lines changed: 552 additions & 25 deletions

File tree

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
"use client"
2+
3+
import Link from 'next/link'
4+
import type { Route } from 'next'
5+
import { usePathname } from 'next/navigation'
6+
import { cn } from '@/lib/utils'
7+
import {
8+
LayoutDashboard,
9+
Bot,
10+
Workflow,
11+
Network,
12+
Wrench,
13+
Settings,
14+
} from 'lucide-react'
15+
16+
type NavItem = {
17+
title: string
18+
href: Route
19+
icon: React.ComponentType<{ className?: string }>
20+
description: string
21+
}
22+
23+
const navItems: NavItem[] = [
24+
{
25+
title: 'Overview',
26+
href: '/admin' as Route,
27+
icon: LayoutDashboard,
28+
description: 'Quick launch + discovery',
29+
},
30+
{
31+
title: 'Agents',
32+
href: '/admin/agents' as Route,
33+
icon: Bot,
34+
description: 'All configured agents',
35+
},
36+
{
37+
title: 'Workflows',
38+
href: '/admin/workflows' as Route,
39+
icon: Workflow,
40+
description: 'All workflows + steps',
41+
},
42+
{
43+
title: 'Networks',
44+
href: '/admin/networks' as Route,
45+
icon: Network,
46+
description: 'Agent routing networks',
47+
},
48+
{
49+
title: 'Tools',
50+
href: '/admin/tools' as Route,
51+
icon: Wrench,
52+
description: 'Tooling overview',
53+
},
54+
{
55+
title: 'Settings',
56+
href: '/admin/settings' as Route,
57+
icon: Settings,
58+
description: 'Local preferences',
59+
},
60+
]
61+
62+
export function AdminSidebar() {
63+
const pathname = usePathname()
64+
65+
return (
66+
<aside className="hidden w-80 shrink-0 border-r bg-card lg:block">
67+
<div className="sticky top-0 flex h-screen flex-col">
68+
<div className="border-b p-6">
69+
<div className="text-sm text-muted-foreground">AgentStack</div>
70+
<div className="text-lg font-semibold">Admin</div>
71+
</div>
72+
73+
<nav className="flex-1 space-y-1 p-3">
74+
{navItems.map((item) => {
75+
const active = pathname === item.href || pathname?.startsWith(`${item.href}/`)
76+
const Icon = item.icon
77+
78+
return (
79+
<Link
80+
key={item.href}
81+
href={item.href}
82+
className={cn(
83+
'flex items-start gap-3 rounded-lg px-3 py-3 transition-colors',
84+
active
85+
? 'bg-primary/10 text-foreground'
86+
: 'text-muted-foreground hover:bg-muted/60 hover:text-foreground'
87+
)}
88+
>
89+
<Icon className="mt-0.5 h-5 w-5" />
90+
<div className="min-w-0">
91+
<div className="font-medium leading-none">{item.title}</div>
92+
<div className="mt-1 text-xs leading-snug text-muted-foreground">
93+
{item.description}
94+
</div>
95+
</div>
96+
</Link>
97+
)
98+
})}
99+
</nav>
100+
101+
<div className="border-t p-4 text-xs text-muted-foreground">
102+
Protected area • Dev session cookie
103+
</div>
104+
</div>
105+
</aside>
106+
)
107+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { ReactNode } from 'react'
2+
3+
export function AdminPageShell({ title, description, children }: { title: string; description?: string; children: ReactNode }) {
4+
return (
5+
<div className="flex h-full flex-col overflow-hidden">
6+
<div className="border-b bg-background/60 px-6 py-5">
7+
<h1 className="text-xl font-semibold">{title}</h1>
8+
{description ? (
9+
<p className="mt-1 text-sm text-muted-foreground">{description}</p>
10+
) : null}
11+
</div>
12+
<div className="flex-1 overflow-auto p-6">{children}</div>
13+
</div>
14+
)
15+
}

app/admin/agents/page.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { AdminPageShell } from '../_components/page-shell'
2+
import { listAgents } from '../config/registry'
3+
import { Card, CardContent, CardHeader, CardTitle } from '@/ui/card'
4+
5+
export default function AdminAgentsPage() {
6+
const agents = listAgents()
7+
8+
return (
9+
<AdminPageShell title="Agents" description="All agents configured for Chat + routing/coordination.">
10+
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
11+
{agents.map((a) => (
12+
<Card key={a.id}>
13+
<CardHeader>
14+
<CardTitle className="text-base">{a.name}</CardTitle>
15+
<div className="text-xs text-muted-foreground">{a.id}</div>
16+
</CardHeader>
17+
<CardContent className="text-sm text-muted-foreground">{a.description}</CardContent>
18+
</Card>
19+
))}
20+
</div>
21+
</AdminPageShell>
22+
)
23+
}

app/admin/config/registry.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { AGENT_CONFIGS } from '@/app/chat/config/agents'
2+
import { NETWORK_CONFIGS } from '@/app/networks/config/networks'
3+
import { WORKFLOW_CONFIGS } from '@/app/workflows/config/workflows'
4+
5+
export interface RegistryItem {
6+
id: string
7+
name: string
8+
description: string
9+
}
10+
11+
12+
export function listAgents(): RegistryItem[] {
13+
return Object.values(AGENT_CONFIGS)
14+
.map((a) => ({ id: a.id, name: a.name, description: a.description }))
15+
.sort((a, b) => a.name.localeCompare(b.name))
16+
}
17+
18+
export function listNetworks(): RegistryItem[] {
19+
return Object.values(NETWORK_CONFIGS)
20+
.map((n) => ({ id: n.id, name: n.name, description: n.description }))
21+
.sort((a, b) => a.name.localeCompare(b.name))
22+
}
23+
24+
export function listWorkflows(): RegistryItem[] {
25+
return Object.values(WORKFLOW_CONFIGS)
26+
.map((w) => ({ id: w.id, name: w.name, description: w.description }))
27+
.sort((a, b) => a.name.localeCompare(b.name))
28+
}

app/admin/layout.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { ReactNode } from 'react'
2+
import { AdminSidebar } from './_components/admin-sidebar'
3+
4+
export default function AdminLayout({ children }: { children: ReactNode }) {
5+
return (
6+
<div className="flex h-screen bg-background">
7+
<AdminSidebar />
8+
<main className="flex min-w-0 flex-1 flex-col">{children}</main>
9+
</div>
10+
)
11+
}

app/admin/networks/page.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { AdminPageShell } from '../_components/page-shell'
2+
import { listNetworks } from '../config/registry'
3+
import { Card, CardContent, CardHeader, CardTitle } from '@/ui/card'
4+
5+
export default function AdminNetworksPage() {
6+
const networks = listNetworks()
7+
8+
return (
9+
<AdminPageShell title="Networks" description="All agent networks configured for the Networks UI.">
10+
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
11+
{networks.map((n) => (
12+
<Card key={n.id}>
13+
<CardHeader>
14+
<CardTitle className="text-base">{n.name}</CardTitle>
15+
<div className="text-xs text-muted-foreground">{n.id}</div>
16+
</CardHeader>
17+
<CardContent className="text-sm text-muted-foreground">{n.description}</CardContent>
18+
</Card>
19+
))}
20+
</div>
21+
</AdminPageShell>
22+
)
23+
}

app/admin/page.tsx

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import Link from 'next/link'
2+
import type { Route } from 'next'
3+
import { AdminPageShell } from './_components/page-shell'
4+
import { Card, CardContent, CardHeader, CardTitle } from '@/ui/card'
5+
import { Badge } from '@/ui/badge'
6+
import { listAgents, listNetworks, listWorkflows } from './config/registry'
7+
8+
export default function AdminHomePage() {
9+
const agents = listAgents()
10+
const networks = listNetworks()
11+
const workflows = listWorkflows()
12+
13+
return (
14+
<AdminPageShell
15+
title="Admin Overview"
16+
description="Quick launch into Chat / Networks / Workflows, plus discovery of everything registered in the UI configs."
17+
>
18+
<div className="grid gap-4 md:grid-cols-3">
19+
<Card>
20+
<CardHeader>
21+
<CardTitle className="flex items-center justify-between">
22+
<span>Quick Launch</span>
23+
<Badge variant="secondary">UI</Badge>
24+
</CardTitle>
25+
</CardHeader>
26+
<CardContent className="space-y-2 text-sm">
27+
<Link href={'/chat' as Route} className="block underline-offset-4 hover:underline">
28+
Open Chat
29+
</Link>
30+
<Link href={'/networks' as Route} className="block underline-offset-4 hover:underline">
31+
Open Networks
32+
</Link>
33+
<Link href={'/workflows' as Route} className="block underline-offset-4 hover:underline">
34+
Open Workflows
35+
</Link>
36+
</CardContent>
37+
</Card>
38+
39+
<Card>
40+
<CardHeader>
41+
<CardTitle>Registry Counts</CardTitle>
42+
</CardHeader>
43+
<CardContent className="space-y-2 text-sm">
44+
<div className="flex items-center justify-between">
45+
<span>Agents</span>
46+
<Badge variant="secondary">{agents.length}</Badge>
47+
</div>
48+
<div className="flex items-center justify-between">
49+
<span>Workflows</span>
50+
<Badge variant="secondary">{workflows.length}</Badge>
51+
</div>
52+
<div className="flex items-center justify-between">
53+
<span>Networks</span>
54+
<Badge variant="secondary">{networks.length}</Badge>
55+
</div>
56+
</CardContent>
57+
</Card>
58+
59+
<Card>
60+
<CardHeader>
61+
<CardTitle>Where this data comes from</CardTitle>
62+
</CardHeader>
63+
<CardContent className="text-sm text-muted-foreground">
64+
This Admin UI reads from local config files:
65+
<ul className="mt-2 list-disc pl-5">
66+
<li>
67+
<code className="rounded bg-muted px-1 py-0.5">app/chat/config/agents.ts</code>
68+
</li>
69+
<li>
70+
<code className="rounded bg-muted px-1 py-0.5">app/networks/config/networks.ts</code>
71+
</li>
72+
<li>
73+
<code className="rounded bg-muted px-1 py-0.5">app/workflows/config/workflows.ts</code>
74+
</li>
75+
</ul>
76+
</CardContent>
77+
</Card>
78+
</div>
79+
</AdminPageShell>
80+
)
81+
}

app/admin/settings/page.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { AdminPageShell } from '../_components/page-shell'
2+
import { Card, CardContent, CardHeader, CardTitle } from '@/ui/card'
3+
4+
export default function AdminSettingsPage() {
5+
return (
6+
<AdminPageShell title="Settings" description="Client-side preferences and session controls.">
7+
<div className="grid gap-4 md:grid-cols-2">
8+
<Card>
9+
<CardHeader>
10+
<CardTitle className="text-base">Session</CardTitle>
11+
</CardHeader>
12+
<CardContent className="text-sm text-muted-foreground">
13+
This Admin area is protected by a simple dev cookie. When you wire real auth, we’ll replace this
14+
with a server-set, httpOnly session.
15+
</CardContent>
16+
</Card>
17+
18+
<Card>
19+
<CardHeader>
20+
<CardTitle className="text-base">Identity</CardTitle>
21+
</CardHeader>
22+
<CardContent className="text-sm text-muted-foreground">
23+
Chat/Network/Workflow UI now uses a generated per-browser `userId` and per-surface IDs (thread/run)
24+
instead of hardcoded values.
25+
</CardContent>
26+
</Card>
27+
</div>
28+
</AdminPageShell>
29+
)
30+
}

app/admin/tools/page.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { AdminPageShell } from '../_components/page-shell'
2+
import { Card, CardContent, CardHeader, CardTitle } from '@/ui/card'
3+
4+
export default function AdminToolsPage() {
5+
return (
6+
<AdminPageShell
7+
title="Tools"
8+
description="Tools run inside Mastra agents. This section is a starting point for tool discovery and settings."
9+
>
10+
<div className="grid gap-4 md:grid-cols-2">
11+
<Card>
12+
<CardHeader>
13+
<CardTitle className="text-base">Tool discovery</CardTitle>
14+
</CardHeader>
15+
<CardContent className="text-sm text-muted-foreground">
16+
For now, tools are documented and used via agents. Next steps here are to add a local tool registry
17+
(or read descriptions directly from the tool definitions) without relying on the HTTP API.
18+
</CardContent>
19+
</Card>
20+
21+
<Card>
22+
<CardHeader>
23+
<CardTitle className="text-base">Observability</CardTitle>
24+
</CardHeader>
25+
<CardContent className="text-sm text-muted-foreground">
26+
Tool calls already emit `tool-*` and `data-tool-*` parts (per Mastra AI SDK UI docs). We can surface
27+
progress events and recent tool runs here next.
28+
</CardContent>
29+
</Card>
30+
</div>
31+
</AdminPageShell>
32+
)
33+
}

app/admin/workflows/page.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { AdminPageShell } from '../_components/page-shell'
2+
import { listWorkflows } from '../config/registry'
3+
import { Card, CardContent, CardHeader, CardTitle } from '@/ui/card'
4+
5+
export default function AdminWorkflowsPage() {
6+
const workflows = listWorkflows()
7+
8+
return (
9+
<AdminPageShell title="Workflows" description="All workflows configured for the Workflow Studio UI.">
10+
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
11+
{workflows.map((w) => (
12+
<Card key={w.id}>
13+
<CardHeader>
14+
<CardTitle className="text-base">{w.name}</CardTitle>
15+
<div className="text-xs text-muted-foreground">{w.id}</div>
16+
</CardHeader>
17+
<CardContent className="text-sm text-muted-foreground">{w.description}</CardContent>
18+
</Card>
19+
))}
20+
</div>
21+
</AdminPageShell>
22+
)
23+
}

0 commit comments

Comments
 (0)