|
| 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. |
0 commit comments