Skip to content

Commit ed81750

Browse files
authored
feat: Modern Dashboard MVP (#388)
1 parent ea0c6ea commit ed81750

36 files changed

+7221
-7
lines changed

.gitignore

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,4 +128,18 @@ results/
128128
.augment
129129

130130
# Claude Code configuration (should not be committed)
131-
CLAUDE.md
131+
CLAUDE.md
132+
133+
# Dashboard frontend (React + TypeScript + Vite)
134+
dashboard/frontend/node_modules/
135+
dashboard/frontend/dist/
136+
dashboard/frontend/build/
137+
dashboard/frontend/.vite/
138+
dashboard/frontend/*.local
139+
140+
# Dashboard backend build artifacts
141+
dashboard/backend/dashboard-backend
142+
dashboard/backend/dashboard-backend.exe
143+
144+
# Keep old HTML backup for reference
145+
dashboard/frontend/index.html.old

Dockerfile.extproc

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,16 @@ FROM golang:1.24 AS go-builder
5757

5858
WORKDIR /app
5959

60+
ENV GOPROXY=https://goproxy.cn,direct
61+
ENV GOSUMDB=sum.golang.google.cn
62+
6063
# Copy Go module files first for better layer caching
6164
RUN mkdir -p src/semantic-router
6265
COPY src/semantic-router/go.mod src/semantic-router/go.sum src/semantic-router/
6366
COPY candle-binding/go.mod candle-binding/semantic-router.go candle-binding/
6467

65-
# Download Go dependencies (cached layer)
66-
RUN cd src/semantic-router && go mod download
68+
RUN cd src/semantic-router && go mod download && \
69+
cd /app/candle-binding && go mod download
6770

6871
# Copy semantic-router source code
6972
COPY src/semantic-router/ src/semantic-router/

dashboard/.dockerignore

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Node
2+
node_modules/
3+
npm-debug.log
4+
yarn-error.log
5+
package-lock.json
6+
yarn.lock
7+
8+
# Build outputs
9+
dist/
10+
build/
11+
.vite/
12+
.docusaurus/
13+
14+
# IDE
15+
.vscode/
16+
.idea/
17+
*.swp
18+
*.swo
19+
*~
20+
21+
# OS
22+
.DS_Store
23+
Thumbs.db
24+
25+
# Temp
26+
*.log
27+
*.tmp
28+
.cache/
29+
30+
# Keep necessary files
31+
!dashboard/frontend/package.json
32+
!dashboard/frontend/tsconfig*.json
33+
!dashboard/frontend/vite.config.ts

dashboard/README.md

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
# Semantic Router Modern Dashboard
2+
3+
Unified dashboard that brings together Configuration Management, an Interactive Playground, and Real-time Monitoring & Observability. It provides a single entry point across local, Docker Compose, and Kubernetes deployments.
4+
5+
## Goals
6+
7+
- Single landing page for new/existing users
8+
- Embed Observability (Grafana/Prometheus) and Playground (Open WebUI) via iframes behind a single backend proxy for auth and CORS/CSP control
9+
- Read-only configuration viewer powered by the existing Semantic Router Classification API
10+
- Environment-agnostic: consistent URLs and behavior for local dev, Compose, and K8s
11+
12+
## What’s already in this repo (reused)
13+
14+
- Prometheus + Grafana
15+
- Docker Compose services in `docker-compose.yml` (ports: Prometheus 9090, Grafana 3000)
16+
- Local observability in `docker-compose.obs.yml` (host network)
17+
- K8s manifests under `deploy/kubernetes/observability/{prometheus,grafana}`
18+
- Provisioned datasource and dashboard in `tools/observability/`
19+
- Router metrics and API
20+
- Metrics at `:9190/metrics` (Prometheus format)
21+
- Classification API on `:8080` with endpoints like `GET /api/v1`, `GET /config/classification`
22+
- Open WebUI integration
23+
- Pipe in `tools/openwebui-pipe/vllm_semantic_router_pipe.py`
24+
- Doc in `website/docs/tutorials/observability/open-webui-integration.md`
25+
26+
These are sufficient to embed and proxy—no need to duplicate core functionality.
27+
28+
## Architecture
29+
30+
### Frontend (React + TypeScript + Vite)
31+
32+
Modern SPA built with:
33+
34+
- **React 18** with TypeScript for type safety
35+
- **Vite 5** for fast development and optimized builds
36+
- **React Router v6** for client-side routing
37+
- **CSS Modules** for scoped styling with theme support (dark/light mode)
38+
39+
Pages:
40+
41+
- **Monitoring** (`/monitoring`): Grafana dashboard embedding with custom path input
42+
- **Config** (`/config`): Real-time configuration viewer with multiple endpoints
43+
- **Playground** (`/playground`): Open WebUI interface for testing
44+
45+
Features:
46+
47+
- 🌓 Dark/Light theme toggle with localStorage persistence
48+
- 📱 Responsive design
49+
- ⚡ Fast navigation with React Router
50+
- 🎨 Modern UI inspired by vLLM website design
51+
52+
### Backend (Go HTTP Server)
53+
54+
- Serves static frontend (Vite production build)
55+
- Reverse proxy with auth/cors/csp controls:
56+
- `GET /embedded/grafana/*` → Grafana
57+
- `GET /embedded/prometheus/*` → Prometheus (optional link-outs)
58+
- `GET /embedded/openwebui/*` → Open WebUI (optional)
59+
- `GET /api/router/*` → Router Classification API (`:8080`)
60+
- `GET /metrics/router` → Router `/metrics` (optional aggregation later)
61+
- `GET /healthz` → Health check endpoint
62+
- Normalizes headers for iframe embedding: strips/overrides `X-Frame-Options` and `Content-Security-Policy` frame-ancestors as needed
63+
- SPA routing support: serves `index.html` for all non-asset routes
64+
- Central point for JWT/OIDC in the future (forward or exchange tokens to upstreams)
65+
66+
## Directory Layout
67+
68+
```
69+
dashboard/
70+
├── frontend/ # React + TypeScript SPA
71+
│ ├── src/
72+
│ │ ├── components/ # Reusable components
73+
│ │ │ ├── Layout.tsx # Main layout with header/nav
74+
│ │ │ └── Layout.module.css
75+
│ │ ├── pages/ # Page components
76+
│ │ │ ├── MonitoringPage.tsx # Grafana iframe with path control
77+
│ │ │ ├── ConfigPage.tsx # Config viewer with API fetch
78+
│ │ │ ├── PlaygroundPage.tsx # Open WebUI iframe
79+
│ │ │ └── *.module.css # Scoped styles per page
80+
│ │ ├── App.tsx # Root component with routing
81+
│ │ ├── main.tsx # Entry point
82+
│ │ └── index.css # Global styles & CSS variables
83+
│ ├── public/ # Static assets (vllm.png)
84+
│ ├── package.json # Node dependencies
85+
│ ├── tsconfig.json # TypeScript configuration
86+
│ ├── vite.config.ts # Vite build configuration
87+
│ └── index.html # SPA shell
88+
├── backend/ # Go reverse proxy server
89+
│ ├── main.go # Proxy routes & static file server
90+
│ ├── go.mod # Go module (minimal dependencies)
91+
│ └── Dockerfile # Multi-stage build (Node + Go + Alpine)
92+
├── deploy/
93+
│ ├── docker/ # Docker Compose overlay (deprecated)
94+
│ └── kubernetes/ # K8s manifests (Service/Ingress/ConfigMap)
95+
├── README.md # This file
96+
└── RISKS.md # Security considerations
97+
```
98+
99+
## Environment-agnostic configuration
100+
101+
The backend exposes a single port (default 8700) and proxies to targets defined via environment variables. This keeps frontend URLs stable and avoids CORS by same-origining everything under the dashboard host.
102+
103+
Required env vars (with sensible defaults per environment):
104+
105+
- `DASHBOARD_PORT` (default: 8700)
106+
- `TARGET_GRAFANA_URL`
107+
- `TARGET_PROMETHEUS_URL`
108+
- `TARGET_ROUTER_API_URL` (router `:8080`)
109+
- `TARGET_ROUTER_METRICS_URL` (router `:9190/metrics`)
110+
- `TARGET_OPENWEBUI_URL` (optional; enable playground tab only if present)
111+
- `ALLOW_IFRAME_EMBED` (default: true; backend will remove/override frame-busting headers)
112+
113+
Recommended upstream settings for embedding:
114+
115+
- Grafana: set `GF_SECURITY_ALLOW_EMBEDDING=true` and prefer `access: proxy` datasource (already configured)
116+
- Open WebUI: ensure CSP/frame-ancestors allows embedding, or rely on dashboard proxy to strip/override; configure Open WebUI auth/session to work under proxied path
117+
118+
## URL strategy (stable, user-facing)
119+
120+
- Dashboard Home: `http://<host>:8700/`
121+
- Monitoring tab: iframe `src="/embedded/grafana/d/<dashboard-uid>?kiosk&theme=light"`
122+
- Config tab: frontend fetch `GET /api/router/config/classification`
123+
- Playground tab: iframe `src="/embedded/openwebui/"` (rendered only if `TARGET_OPENWEBUI_URL` is set)
124+
125+
## Deployment matrix
126+
127+
1) Local dev (router and observability on host)
128+
129+
- Use `docker-compose.obs.yml` to start Prometheus (9090) and Grafana (3000) on host network
130+
- Start dashboard backend locally (port 8700)
131+
- Env examples:
132+
- `TARGET_GRAFANA_URL=http://localhost:3000`
133+
- `TARGET_PROMETHEUS_URL=http://localhost:9090`
134+
- `TARGET_ROUTER_API_URL=http://localhost:8080`
135+
- `TARGET_ROUTER_METRICS_URL=http://localhost:9190/metrics`
136+
- `TARGET_OPENWEBUI_URL=http://localhost:3001` (if running)
137+
138+
2) Docker Compose (all-in-one)
139+
140+
- Reuse services defined in root `docker-compose.yml`
141+
- Add dashboard and optional Open WebUI services in `dashboard/deploy/docker/compose.yml`
142+
- Env examples (inside compose network):
143+
- `TARGET_GRAFANA_URL=http://grafana:3000`
144+
- `TARGET_PROMETHEUS_URL=http://prometheus:9090`
145+
- `TARGET_ROUTER_API_URL=http://semantic-router:8080`
146+
- `TARGET_ROUTER_METRICS_URL=http://semantic-router:9190/metrics`
147+
- `TARGET_OPENWEBUI_URL=http://openwebui:8080` (if included)
148+
149+
3) Kubernetes
150+
151+
- Install/confirm Prometheus and Grafana via existing manifests in `deploy/kubernetes/observability`
152+
- Deploy dashboard in `dashboard/deploy/kubernetes/`
153+
- Configure the dashboard Deployment with in-cluster URLs:
154+
- `TARGET_GRAFANA_URL=http://grafana.<ns>.svc.cluster.local:3000`
155+
- `TARGET_PROMETHEUS_URL=http://prometheus.<ns>.svc.cluster.local:9090`
156+
- `TARGET_ROUTER_API_URL=http://semantic-router.<ns>.svc.cluster.local:8080`
157+
- `TARGET_ROUTER_METRICS_URL=http://semantic-router.<ns>.svc.cluster.local:9190/metrics`
158+
- `TARGET_OPENWEBUI_URL=http://openwebui.<ns>.svc.cluster.local:8080` (if installed)
159+
- Expose the dashboard via Ingress/Gateway to the outside; upstreams remain ClusterIP
160+
161+
## Security & access control
162+
163+
- MVP: bearer token/JWT support via `Authorization: Bearer <token>` in requests to `/api/router/*` (forwarded to router API)
164+
- Frame embedding: backend strips/overrides `X-Frame-Options` and `Content-Security-Policy` headers from upstreams to permit `frame-ancestors 'self'` only
165+
- Future: OIDC login on dashboard, session cookie, and per-route RBAC; signed proxy sessions to Grafana/Open WebUI
166+
167+
## Extensibility
168+
169+
- New panels: add tabs/components to `frontend/`
170+
- New integrations: add target env vars and a new `/embedded/<service>` route in backend proxy
171+
- Metrics aggregation: add `/api/metrics` in backend to produce derived KPIs from Prometheus
172+
173+
## Implementation milestones
174+
175+
1) MVP (this PR)
176+
177+
- Scaffold `dashboard/` (this README)
178+
- Backend: Go server with reverse proxies for `/embedded/*` and `/api/router/*`
179+
- Frontend: minimal SPA with three tabs and iframes + JSON viewer
180+
- Compose overlay: `dashboard/deploy/docker/compose.yml` to launch dashboard with existing stack
181+
182+
2) K8s manifests
183+
184+
- Deployment + Service + ConfigMap with env vars; optional Ingress
185+
- Document `kubectl port-forward` for dev
186+
187+
3) Auth hardening and polish
188+
189+
- Env toggles for anonymous/off
190+
- OIDC enablement behind a flag
191+
- Metrics summary endpoint
192+
193+
## Quick Start
194+
195+
### Method 1: One-click Start with Docker Compose (Recommended)
196+
197+
The Dashboard is integrated into the main Compose stack, requiring no extra configuration:
198+
199+
```bash
200+
# Run from the project root directory
201+
make docker-compose-up
202+
203+
# Or use docker compose directly
204+
docker compose -f deploy/docker-compose/docker-compose.yml up -d --build
205+
```
206+
207+
After startup, access:
208+
209+
- **Dashboard**: http://localhost:8700
210+
- **Grafana** (direct access): http://localhost:3000 (admin/admin)
211+
- **Prometheus** (direct access): http://localhost:9090
212+
213+
### Method 2: Local Development Mode
214+
215+
When developing the Dashboard code locally:
216+
217+
```bash
218+
# 1. Start the local Observability stack
219+
make o11y-local
220+
# Or
221+
docker compose -f tools/observability/docker-compose.obs.yml up -d
222+
223+
# 2. Start the Router (in another terminal)
224+
cd src/semantic-router
225+
go run cmd/main.go -config ../../config/config.yaml
226+
227+
# 3. Install frontend dependencies
228+
cd dashboard/frontend
229+
npm install
230+
231+
# 4. Start the frontend dev server (with HMR)
232+
npm run dev
233+
# Vite will start on http://localhost:3001 with proxy to backend
234+
235+
# 5. Start the Dashboard backend (in another terminal)
236+
cd dashboard/backend
237+
export TARGET_GRAFANA_URL=http://localhost:3000
238+
export TARGET_PROMETHEUS_URL=http://localhost:9090
239+
export TARGET_ROUTER_API_URL=http://localhost:8080
240+
export TARGET_ROUTER_METRICS_URL=http://localhost:9190/metrics
241+
go run main.go -port=8700 -static=../frontend/dist
242+
243+
# For development, use the Vite dev server at http://localhost:3001
244+
# For production preview, build first: cd frontend && npm run build
245+
```
246+
247+
### Method 3: Rebuild Dashboard Only
248+
249+
For a quick rebuild after code changes:
250+
251+
```bash
252+
# Rebuild the dashboard service
253+
docker compose -f deploy/docker-compose/docker-compose.yml build dashboard
254+
255+
# Restart the dashboard
256+
docker compose -f deploy/docker-compose/docker-compose.yml up -d dashboard
257+
258+
# View logs
259+
docker logs -f semantic-router-dashboard
260+
```
261+
262+
## Deployment Details
263+
264+
### Docker Compose Integration Notes
265+
266+
- The Dashboard service is integrated as a **default service** in `deploy/docker-compose/docker-compose.yml`.
267+
- No additional overlay files are needed; `make docker-compose-up` will automatically start all services.
268+
- The Dashboard depends on the `semantic-router` (for health checks), `grafana`, and `prometheus` services.
269+
270+
### Dockerfile Build
271+
272+
- A **3-stage multi-stage build** is defined in `dashboard/backend/Dockerfile`:
273+
1. **Node.js stage**: Builds the React frontend with Vite (`npm run build``dist/`)
274+
2. **Go builder stage**: Compiles the backend binary
275+
3. **Alpine runtime stage**: Combines backend + frontend dist in minimal image
276+
- An independent Go module `dashboard/backend/go.mod` isolates backend dependencies.
277+
- Frontend production build (`dist/`) is packaged into the image at `/app/frontend`.
278+
279+
### Grafana Embedding Support
280+
281+
Grafana is already configured for embedding in `deploy/docker-compose/docker-compose.yml`:
282+
283+
```yaml
284+
- GF_SECURITY_ALLOW_EMBEDDING=true
285+
- GF_SECURITY_COOKIE_SAMESITE=lax
286+
```
287+
288+
The Dashboard reverse proxy will automatically clean up `X-Frame-Options` and adjust CSP headers to ensure the iframe loads correctly.
289+
290+
### Health Check
291+
292+
The Dashboard provides a `/healthz` endpoint for container health checks:
293+
294+
```bash
295+
curl http://localhost:8700/healthz
296+
# Returns: {"status":"healthy","service":"semantic-router-dashboard"}
297+
```
298+
299+
## Notes
300+
301+
- The website/ (Docusaurus) remains for documentation. The dashboard is a runtime operator/try-it surface, not docs.
302+
- We’ll keep upstream services untouched and do all UX unification at the proxy + SPA layer.

dashboard/backend/.gitkeep

Whitespace-only changes.

0 commit comments

Comments
 (0)