Skip to content

Commit cf471ae

Browse files
committed
Set Up React + Typescript for Frontend
Signed-off-by: JaredforReal <[email protected]>
1 parent c70411e commit cf471ae

28 files changed

+4658
-211
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

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: 83 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -25,41 +25,75 @@ Unified dashboard that brings together Configuration Management, an Interactive
2525

2626
These are sufficient to embed and proxy—no need to duplicate core functionality.
2727

28-
## Architecture (MVP)
29-
30-
- frontend/ (SPA)
31-
- Tabs: Monitoring, Config Viewer, Playground
32-
- Iframes for Grafana dashboards and Open WebUI
33-
- Simple viewer for router config JSON
34-
- backend/ (Go HTTP server)
35-
- Serves static frontend
36-
- Reverse proxy with auth/cors/csp controls:
37-
- `GET /embedded/grafana/*` → Grafana
38-
- `GET /embedded/prometheus/*` → Prometheus (optional link-outs)
39-
- `GET /embedded/openwebui/*` → Open WebUI (optional)
40-
- `GET /api/router/*` → Router Classification API (`:8080`)
41-
- `GET /metrics/router` → Router `/metrics` (optional aggregation later)
42-
- Normalizes headers for iframe embedding: strips/overrides `X-Frame-Options` and `Content-Security-Policy` frame-ancestors as needed
43-
- Central point for JWT/OIDC in the future (forward or exchange tokens to upstreams)
44-
45-
## Directory layout
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
4667

4768
```
4869
dashboard/
49-
├── frontend/ # UI for configuration, playground, monitoring
50-
│ ├─ Monitoring (iframe Grafana)
51-
│ ├─ Config Viewer (fetch /api/router/config/classification)
52-
│ └─ Playground (iframe Open WebUI)
53-
├── backend/ # Go proxy, auth, thin API
54-
│ ├─ /embedded/grafana → Grafana
55-
│ ├─ /embedded/prometheus → Prometheus
56-
│ ├─ /embedded/openwebui → Open WebUI
57-
│ └─ /api/router/* → Semantic Router API
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)
5892
├── deploy/
59-
│ ├── docker/ # Docker Compose setup for the dashboard
60-
── kubernetes/ # K8s manifests (Service/Ingress/ConfigMap)
61-
│ └── local/ # Local/dev launcher
62-
└── helm-chart/ # (optional) Helm chart for dashboard
93+
│ ├── docker/ # Docker Compose overlay (deprecated)
94+
── kubernetes/ # K8s manifests (Service/Ingress/ConfigMap)
95+
── README.md # This file
96+
└── RISKS.md # Security considerations
6397
```
6498

6599
## Environment-agnostic configuration
@@ -190,16 +224,24 @@ docker compose -f tools/observability/docker-compose.obs.yml up -d
190224
cd src/semantic-router
191225
go run cmd/main.go -config ../../config/config.yaml
192226

193-
# 3. Start the Dashboard backend (local development)
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)
194236
cd dashboard/backend
195237
export TARGET_GRAFANA_URL=http://localhost:3000
196238
export TARGET_PROMETHEUS_URL=http://localhost:9090
197239
export TARGET_ROUTER_API_URL=http://localhost:8080
198240
export TARGET_ROUTER_METRICS_URL=http://localhost:9190/metrics
199-
go run main.go -port=8700 -static=../frontend
241+
go run main.go -port=8700 -static=../frontend/dist
200242

201-
# 4. Open your browser
202-
open http://localhost:8700
243+
# For development, use the Vite dev server at http://localhost:3001
244+
# For production preview, build first: cd frontend && npm run build
203245
```
204246

205247
### Method 3: Rebuild Dashboard Only
@@ -227,9 +269,12 @@ docker logs -f semantic-router-dashboard
227269

228270
### Dockerfile Build
229271

230-
- A multi-stage build (Go builder → distroless) is defined in `dashboard/backend/Dockerfile`.
231-
- An independent Go module `dashboard/backend/go.mod` isolates dependencies.
232-
- Frontend static assets are packaged into the image at `/app/frontend`.
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`.
233278

234279
### Grafana Embedding Support
235280

dashboard/RISKS.md

Lines changed: 0 additions & 64 deletions
This file was deleted.

dashboard/backend/Dockerfile

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,27 @@
1-
FROM golang:1.24 AS builder
1+
# Stage 1: Build frontend with Node.js
2+
FROM node:20-alpine AS frontend-builder
3+
WORKDIR /app/frontend
4+
COPY dashboard/frontend/package.json dashboard/frontend/tsconfig.json dashboard/frontend/tsconfig.node.json dashboard/frontend/vite.config.ts ./
5+
COPY dashboard/frontend/src ./src
6+
COPY dashboard/frontend/public ./public
7+
COPY dashboard/frontend/index.html ./
8+
RUN npm install
9+
RUN npm run build
10+
11+
# Stage 2: Build backend with Go
12+
FROM golang:1.24 AS backend-builder
213
WORKDIR /app
3-
# Copy source and frontend assets
414
COPY dashboard/backend/ /app/dashboard/backend/
5-
COPY dashboard/frontend/ /app/dashboard/frontend/
6-
# Build the binary
715
WORKDIR /app/dashboard/backend
816
RUN go mod download || true
917
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /app/dashboard-backend main.go
1018

19+
# Stage 3: Final runtime image
1120
FROM alpine:3.19
1221
RUN apk --no-cache add ca-certificates wget
1322
WORKDIR /app
14-
COPY --from=builder /app/dashboard-backend /app/dashboard-backend
15-
COPY --from=builder /app/dashboard/frontend /app/frontend
23+
COPY --from=backend-builder /app/dashboard-backend /app/dashboard-backend
24+
COPY --from=frontend-builder /app/frontend/dist /app/frontend
1625
RUN addgroup -g 65532 nonroot && \
1726
adduser -D -u 65532 -G nonroot nonroot && \
1827
chown -R nonroot:nonroot /app

dashboard/backend/main.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,32 @@ func staticFileServer(staticDir string) http.Handler {
9090
// Serve index.html for root and for unknown routes (SPA)
9191
p := r.URL.Path
9292
full := path.Join(staticDir, path.Clean(p))
93-
// If path has no extension and isn't a file, serve index.html
94-
if _, err := os.Stat(full); err != nil || strings.HasSuffix(p, "/") {
93+
94+
// Check if file exists
95+
info, err := os.Stat(full)
96+
if err == nil {
97+
// File exists
98+
if !info.IsDir() {
99+
// It's a file, serve it
100+
fs.ServeHTTP(w, r)
101+
return
102+
}
103+
// It's a directory, try index.html
104+
indexPath := path.Join(full, "index.html")
105+
if _, err := os.Stat(indexPath); err == nil {
106+
http.ServeFile(w, r, indexPath)
107+
return
108+
}
109+
}
110+
111+
// File doesn't exist or is directory without index.html
112+
// For SPA routing: serve index.html for routes without file extension
113+
if !strings.Contains(path.Base(p), ".") {
95114
http.ServeFile(w, r, path.Join(staticDir, "index.html"))
96115
return
97116
}
117+
118+
// Otherwise let the file server handle it (will return 404)
98119
fs.ServeHTTP(w, r)
99120
})
100121
}

dashboard/frontend/.gitkeep

Whitespace-only changes.

0 commit comments

Comments
 (0)