Skip to content

Commit 5ce3e67

Browse files
broomvaclaude
andauthored
feat: add Next.js dashboard with tRPC + CORS support
## Summary - Add a React dashboard (Next.js 16 + tRPC + shadcn/ui) forked from chat-js, stripped of all chat domain logic, rebuilt for Symphony orchestration monitoring - Add CORS layer to the Rust HTTP server (`symphony-observability`) for cross-origin dashboard access - Add `@symphony/client` TypeScript package mirroring Rust API types - Add Dockerfile + railway.toml for dashboard deployment ## Dashboard pages | Route | Description | |-------|-------------| | `/overview` | Live stat cards (running, retrying, tokens, runtime) + token area chart | | `/issues` | Running + retrying session tables with drill-down links | | `/issues/[identifier]` | Issue detail page with state, tokens, retry info | | `/workspaces` | Workspace cards with status badges | | `/metrics` | Token breakdown, session counts, config, runtime stats | | `/controls` | Trigger poll + graceful shutdown with confirmation | ## Architecture ``` Browser → Next.js (port 3000) → tRPC → SymphonyClient → Symphony daemon (port 8080) (server-side) /api/v1/* ``` - tRPC routers run server-side, calling Symphony's REST API - `SYMPHONY_API_TOKEN` stays server-side (never sent to browser) - React Query auto-polls at 5s intervals - Configurable CORS via `SYMPHONY_CORS_ORIGINS` env var ## Rust changes - `crates/symphony-observability/src/server.rs`: Added `CorsLayer` with configurable origins - New test: `cors_preflight_returns_headers` - All 260 Rust tests pass ## Test plan - [x] `make smoke` — all Rust tests pass (including new CORS test) - [x] `tsc --noEmit` — zero TypeScript errors - [x] `bun run build` compiles (needs `.env.local` with `DATABASE_URL` + `AUTH_SECRET`) - [ ] Start daemon → `bun run dev` → verify live data on localhost:3000 - [ ] Deploy dashboard to Railway/Vercel with Postgres 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
1 parent dc22b0f commit 5ce3e67

117 files changed

Lines changed: 8022 additions & 1 deletion

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,11 @@ skills-lock.json
4848
# Claude Code worktrees and local state
4949
.claude/worktrees/
5050
.claude/ralph-loop.local.md
51+
52+
# Dashboard (Next.js)
53+
dashboard/node_modules/
54+
dashboard/.next/
55+
dashboard/apps/*/node_modules/
56+
dashboard/apps/*/.next/
57+
dashboard/packages/*/node_modules/
58+
dashboard/.env.local

Makefile

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: smoke check test build clean clippy fmt publish publish-dry-run install changelog release harness-audit entropy-check control-refresh control-validate conversations eval-run eval-check eval-rollback
1+
.PHONY: smoke check test build clean clippy fmt publish publish-dry-run install changelog release harness-audit entropy-check control-refresh control-validate conversations eval-run eval-check eval-rollback dashboard-install dashboard-dev dashboard-build
22

33
# === GATES ===
44

@@ -94,6 +94,17 @@ release: smoke
9494
@echo "Release v$(VERSION) ready. Push with:"
9595
@echo " git push origin master v$(VERSION)"
9696

97+
# === DASHBOARD ===
98+
99+
dashboard-install:
100+
cd dashboard && bun install
101+
102+
dashboard-dev: dashboard-install
103+
cd dashboard && bun run dev
104+
105+
dashboard-build: dashboard-install
106+
cd dashboard && bun run build
107+
97108
# === CONTROL AUDIT ===
98109

99110
control-audit: smoke fmt-check

crates/symphony-observability/src/server.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use axum::{Json, Router, routing::get};
1515
use serde::Serialize;
1616
use symphony_core::OrchestratorState;
1717
use tokio::sync::Mutex;
18+
use tower_http::cors::{AllowOrigin, Any, CorsLayer};
1819

1920
/// Shared state for the HTTP server.
2021
#[derive(Clone)]
@@ -92,6 +93,29 @@ pub struct ErrorDetail {
9293
pub message: String,
9394
}
9495

96+
/// Build CORS layer from environment.
97+
///
98+
/// - `SYMPHONY_CORS_ORIGINS` — comma-separated allowed origins (e.g. `http://localhost:3000,https://app.example.com`)
99+
/// - If unset, defaults to permissive `Any` for development convenience.
100+
fn build_cors_layer() -> CorsLayer {
101+
match std::env::var("SYMPHONY_CORS_ORIGINS") {
102+
Ok(origins) if !origins.is_empty() => {
103+
let parsed: Vec<_> = origins
104+
.split(',')
105+
.filter_map(|o| o.trim().parse().ok())
106+
.collect();
107+
CorsLayer::new()
108+
.allow_origin(AllowOrigin::list(parsed))
109+
.allow_methods(Any)
110+
.allow_headers(Any)
111+
}
112+
_ => CorsLayer::new()
113+
.allow_origin(Any)
114+
.allow_methods(Any)
115+
.allow_headers(Any),
116+
}
117+
}
118+
95119
/// Build the HTTP router (S13.7).
96120
pub fn build_router(state: AppState) -> Router {
97121
// API routes — protected by optional bearer token auth
@@ -120,6 +144,7 @@ pub fn build_router(state: AppState) -> Router {
120144
.route("/readyz", get(readyz))
121145
.route("/metrics", get(get_prometheus_metrics))
122146
.merge(api_routes)
147+
.layer(build_cors_layer())
123148
.with_state(state)
124149
}
125150

@@ -978,6 +1003,22 @@ mod tests {
9781003
assert!(text.contains("symphony_issues_completed"));
9791004
}
9801005

1006+
#[tokio::test]
1007+
async fn cors_preflight_returns_headers() {
1008+
let state = make_app_state();
1009+
let app = build_router(state);
1010+
let req = Request::builder()
1011+
.method("OPTIONS")
1012+
.uri("/api/v1/state")
1013+
.header("origin", "http://localhost:3000")
1014+
.header("access-control-request-method", "GET")
1015+
.body(Body::empty())
1016+
.unwrap();
1017+
let resp = app.oneshot(req).await.unwrap();
1018+
assert!(resp.headers().contains_key("access-control-allow-origin"));
1019+
assert!(resp.headers().contains_key("access-control-allow-methods"));
1020+
}
1021+
9811022
#[tokio::test]
9821023
async fn prometheus_metrics_bypasses_auth() {
9831024
let state = AppState {

dashboard/.dockerignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
.next
3+
.env.local
4+
*.log
5+
.git

dashboard/.gitignore

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
node_modules
5+
.pnp
6+
.pnp.js
7+
8+
# testing
9+
coverage
10+
11+
# next.js
12+
.next/
13+
apps/*/.next/
14+
out/
15+
build
16+
next-env.d.ts
17+
18+
# misc
19+
.DS_Store
20+
*.pem
21+
22+
# debug
23+
npm-debug.log*
24+
yarn-debug.log*
25+
yarn-error.log*
26+
.pnpm-debug.log*
27+
28+
# local env files
29+
.env.local
30+
.neon-branch
31+
.env.development.local
32+
.env.test.local
33+
.env.production.local
34+
35+
# turbo
36+
.turbo
37+
38+
.env
39+
.vercel
40+
.env*.local
41+
42+
# Playwright
43+
/test-results/
44+
/playwright-report/
45+
/blob-report/
46+
/playwright/*
47+
apps/*/test-results/
48+
apps/*/playwright-report/
49+
apps/*/blob-report/
50+
apps/*/playwright/*
51+
52+
tsconfig.tsbuildinfo
53+
54+
packages/*/dist
55+
packages/cli/templates
56+
templates
57+
58+
# Evalite DB
59+
evals/db/
60+
apps/*/evals/db/
61+
62+
# Neon
63+
.neon
64+
65+
/.skillz
66+
/AGENTS.md
67+
/AGENTS.md.bak
68+
/CLAUDE.md
69+
/CLAUDE.md.bak
70+
# END Skiller Generated Files
71+
.devtools
72+
73+
.cursor/hooks/state

dashboard/Dockerfile

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Symphony Dashboard — multi-stage Docker build
2+
# For Railway / self-hosted deployment
3+
4+
FROM oven/bun:1.3 AS base
5+
WORKDIR /app
6+
7+
# Install dependencies
8+
FROM base AS deps
9+
COPY package.json bun.lock ./
10+
COPY apps/web/package.json ./apps/web/
11+
COPY packages/symphony-client/package.json ./packages/symphony-client/
12+
RUN bun install --frozen-lockfile
13+
14+
# Build the application
15+
FROM base AS builder
16+
COPY --from=deps /app/node_modules ./node_modules
17+
COPY --from=deps /app/apps/web/node_modules ./apps/web/node_modules
18+
COPY . .
19+
20+
# Build args become env vars for Next.js build
21+
ARG DATABASE_URL
22+
ARG AUTH_SECRET
23+
ARG SYMPHONY_API_URL=http://localhost:8080
24+
ARG SYMPHONY_API_TOKEN
25+
ARG APP_URL
26+
27+
ENV DATABASE_URL=${DATABASE_URL}
28+
ENV AUTH_SECRET=${AUTH_SECRET}
29+
ENV SYMPHONY_API_URL=${SYMPHONY_API_URL}
30+
ENV SYMPHONY_API_TOKEN=${SYMPHONY_API_TOKEN}
31+
ENV APP_URL=${APP_URL}
32+
33+
RUN bun run build
34+
35+
# Production image
36+
FROM base AS runner
37+
WORKDIR /app
38+
39+
ENV NODE_ENV=production
40+
41+
RUN addgroup --system --gid 1001 nodejs && \
42+
adduser --system --uid 1001 nextjs
43+
44+
COPY --from=builder /app/apps/web/.next/standalone ./
45+
COPY --from=builder /app/apps/web/.next/static ./apps/web/.next/static
46+
COPY --from=builder /app/apps/web/public ./apps/web/public
47+
48+
USER nextjs
49+
50+
EXPOSE 3000
51+
ENV PORT=3000
52+
ENV HOSTNAME="0.0.0.0"
53+
54+
CMD ["node", "apps/web/server.js"]

dashboard/LICENSE

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Copyright 2025 Francisco Moretti
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.

dashboard/README.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<div align="center">
2+
3+
<img src="apps/chat/app/icon.svg" alt="ChatJS" width="64" height="64">
4+
5+
# ChatJS
6+
7+
Stop rebuilding the same AI chat infrastructure. ChatJS gives you a production-ready foundation with authentication, 120+ models, streaming, and tools so you can focus on what makes your app unique.
8+
9+
[**Documentation**](https://chatjs.dev/docs) · [**Live Demo**](https://chatjs.dev)
10+
11+
![DemosOnly](https://github.com/user-attachments/assets/f12e89dd-c10c-4e06-9b1a-a9fbd809d234)
12+
13+
</div>
14+
15+
<br />
16+
17+
## CLI
18+
19+
Create a new ChatJS app:
20+
21+
```bash
22+
npx @chat-js/cli@latest create my-app
23+
```
24+
25+
The CLI walks you through gateway, features, and auth choices, generates `chat.config.ts`, and lists the env vars required by your selections.
26+
27+
## Features
28+
29+
- **120+ Models**: Claude, GPT, Gemini, Grok via one API
30+
- **Auth**: GitHub, Google, anonymous. Ready to go.
31+
- **Attachments**: Images, PDFs, docs. Drag and drop.
32+
- **Resumable Streams**: Continue generation after page refresh
33+
- **Branching**: Fork conversations, explore alternatives
34+
- **Sharing**: Share conversations with public links
35+
- **Web Search**: Real-time web search integration
36+
- **Image Generation**: AI-powered image creation
37+
- **Code Execution**: Run code snippets in sandbox
38+
- **MCP**: Model Context Protocol support
39+
40+
## Stack
41+
42+
- [Next.js](https://nextjs.org) - App Router, React Server Components
43+
- [TypeScript](https://www.typescriptlang.org) - Full type safety
44+
- [AI SDK](https://ai-sdk.dev/) - The AI Toolkit for TypeScript
45+
- [AI Gateway](https://vercel.com/ai-gateway) - Unified access to 120+ AI models
46+
- [Better Auth](https://www.better-auth.com) - Authentication & authorization
47+
- [Drizzle ORM](https://orm.drizzle.team) - Type-safe database queries
48+
- [PostgreSQL](https://www.postgresql.org) - Primary database
49+
- [Redis](https://redis.io) - Caching & resumable streams
50+
- [Vercel Blob](https://vercel.com/storage/blob) - Blob storage
51+
- [Shadcn/UI](https://ui.shadcn.com) - Beautiful, accessible components
52+
- [Tailwind CSS](https://tailwindcss.com) - Styling
53+
- [tRPC](https://trpc.io) - End-to-end type-safe APIs
54+
- [Zod](https://zod.dev) - Schema validation
55+
- [Zustand](https://docs.pmnd.rs/zustand) - State management
56+
- [Motion](https://motion.dev) - Animations
57+
- [t3-env](https://env.t3.gg) - Environment variables
58+
- [Pino](https://getpino.io) - Structured Logging
59+
- [Langfuse](https://langfuse.com) - LLM observability & analytics
60+
- [Vercel Analytics](https://vercel.com/analytics) - Web analytics
61+
- [Biome](https://biomejs.dev) - Code linting and formatting
62+
- [Ultracite](https://ultracite.ai) - Biome preset for humans and AI
63+
- [Streamdown](https://streamdown.ai/) - Markdown for AI streaming
64+
- [AI Elements](https://ai-sdk.dev/elements/overview) - AI-native Components
65+
- [AI SDK Tools](https://ai-sdk-tools.dev/) - Developer tools for AI SDK
66+
67+
## Monorepo Layout
68+
69+
- `apps/chat`: Next.js chat app
70+
- `apps/docs`: Mintlify docs
71+
- `packages/cli`: interactive scaffold CLI
72+
73+
## Development
74+
75+
- `bun dev:chat`: run chat app
76+
- `bun dev:docs`: run docs
77+
- `bun lint`: run workspace lint
78+
- `bun test:types`: run chat app typecheck
79+
80+
## Documentation
81+
82+
Visit [chatjs.dev/docs](https://chatjs.dev/docs) to view docs.
83+
84+
## License
85+
86+
Apache-2.0
87+
88+
<br />
89+
<a href="https://vercel.com/oss">
90+
<img alt="Vercel OSS Program" src="https://vercel.com/oss/program-badge.svg" />
91+
</a>
92+
<br />

dashboard/apps/web/.env.example

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Database
2+
DATABASE_URL=postgresql://localhost:5432/symphony_dashboard
3+
4+
# Auth
5+
AUTH_SECRET=change-me-to-a-random-string
6+
7+
# Symphony daemon
8+
SYMPHONY_API_URL=http://localhost:8080
9+
SYMPHONY_API_TOKEN=
10+
11+
# App URL (for non-localhost deployments)
12+
APP_URL=http://localhost:3000

0 commit comments

Comments
 (0)