Skip to content

Commit b597b37

Browse files
yossiovadiaclauderootfs
committed
Openshift dashboard clean (vllm-project#469)
* feat(openshift): add dashboard deployment with OpenWebUI playground Add OpenShift deployment support for the semantic router dashboard with integrated OpenWebUI playground functionality. This enables running the dashboard on OpenShift with: - Dashboard UI for router configuration - OpenWebUI playground for LLM interaction - Dynamic hostname detection for OpenShift routes - Custom build system with frontend patches Components: deploy/openshift/dashboard/ ├── dashboard-deployment.yaml - Kubernetes resources (deployment, service, route) ├── PlaygroundPage.tsx.patch - Frontend patch for OpenShift URL detection ├── build-custom-dashboard.sh - Automated build and deployment script └── README.md - Build and deployment instructions Implementation Details: - PlaygroundPage patch detects OpenShift hostname and constructs OpenWebUI URL (dashboard-vllm-semantic-router-system → openwebui-vllm-semantic-router-system) - Uses direct OpenWebUI route instead of embedded proxy - Works with pre-built OpenWebUI images (no build-time configuration needed) - Build script applies patches and pushes custom image to OpenShift registry Deployment: 1. Deploy dashboard: oc apply -f deploy/openshift/dashboard/dashboard-deployment.yaml 2. Build custom image: ./deploy/openshift/dashboard/build-custom-dashboard.sh 3. Access playground at dashboard route /playground Testing: Verified end-to-end on OpenShift cluster: - Dashboard accessible via HTTPS route - OpenWebUI playground loads correctly in iframe - Dynamic URL construction works - All functionality working without 404 errors Signed-off-by: Yossi Ovadia <[email protected]> * feat(openshift): enable single-command dashboard deployment with OpenWebUI playground This commit improves the OpenShift dashboard deployment workflow by: 1. **Single-step deployment**: Users now only need to run `./deploy/openshift/dashboard/build-custom-dashboard.sh` for both fresh deployments and updates. The script automatically detects if the deployment exists and handles both scenarios. 2. **Automatic imagestream creation**: The build script now creates the `dashboard-custom` imagestream if it doesn't exist, eliminating manual setup steps. 3. **Updated deployment YAML**: Changed the default image reference from the base GitHub image to the custom OpenShift-built image (`image-registry.openshift-image-registry.svc:5000/...`), ensuring the patched version is deployed by default. 4. **Improved documentation**: Updated README with clear single-command instructions and explanation of how the PlaygroundPage.tsx patch enables OpenWebUI integration in OpenShift's route-based architecture. The PlaygroundPage.tsx patch enables hostname-aware URL construction, automatically detecting OpenShift routes and loading OpenWebUI correctly instead of showing "404: Not Found". 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> Signed-off-by: Yossi Ovadia <[email protected]> --------- Signed-off-by: Yossi Ovadia <[email protected]> Co-authored-by: Claude <[email protected]> Co-authored-by: Huamin Chen <[email protected]> Signed-off-by: Huamin Chen <[email protected]>
1 parent f13157c commit b597b37

File tree

4 files changed

+389
-0
lines changed

4 files changed

+389
-0
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React, { useState, useEffect } from 'react'
2+
import styles from './PlaygroundPage.module.css'
3+
4+
const PlaygroundPage: React.FC = () => {
5+
// Use the OpenWebUI route for OpenShift deployments, fallback to embedded proxy
6+
const getOpenWebUIUrl = () => {
7+
const hostname = window.location.hostname
8+
// In OpenShift, use the direct OpenWebUI route
9+
if (hostname.includes('dashboard-vllm-semantic-router-system')) {
10+
return hostname.replace('dashboard-vllm-semantic-router-system', 'openwebui-vllm-semantic-router-system')
11+
}
12+
// Default to embedded proxy for local development
13+
return '/embedded/openwebui/'
14+
}
15+
16+
const [openWebUIUrl] = useState(() => {
17+
const host = getOpenWebUIUrl()
18+
return host.startsWith('/') ? host : `${window.location.protocol}//${host}`
19+
})
20+
const [currentUrl, setCurrentUrl] = useState('')
21+
22+
// Auto-load on mount
23+
useEffect(() => {
24+
// Default to loading the configured URL on mount
25+
setCurrentUrl(openWebUIUrl)
26+
}, [openWebUIUrl]) // Load when URL changes
27+
28+
return (
29+
<div className={styles.container}>
30+
<div className={styles.iframeContainer}>
31+
{!currentUrl && (
32+
<div className={styles.placeholder}>
33+
<span className={styles.placeholderIcon}>🎮</span>
34+
<h3>Open WebUI Playground</h3>
35+
<p>
36+
Test your LLM models and semantic routing with Open WebUI.
37+
</p>
38+
<p className={styles.note}>
39+
Note: Open WebUI needs to be deployed separately. Check the dashboard README for instructions.
40+
</p>
41+
</div>
42+
)}
43+
44+
{currentUrl && (
45+
<iframe
46+
src={currentUrl}
47+
className={styles.iframe}
48+
title="Open WebUI Playground"
49+
allowFullScreen
50+
/>
51+
)}
52+
</div>
53+
</div>
54+
)
55+
}
56+
57+
export default PlaygroundPage
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Custom Dashboard with OpenWebUI Playground for OpenShift
2+
3+
This directory contains the OpenShift deployment configuration and custom build for the dashboard with OpenWebUI playground integration.
4+
5+
## Files
6+
7+
- `dashboard-deployment.yaml` - Kubernetes resources (Deployment, Service, Route, ConfigMap)
8+
- `build-custom-dashboard.sh` - Builds custom dashboard image with OpenWebUI integration patches
9+
- `PlaygroundPage.tsx.patch` - Frontend patch for OpenShift hostname-aware OpenWebUI URL construction
10+
- `README.md` - This file
11+
12+
## Quick Start
13+
14+
### Prerequisites
15+
16+
1. OpenShift cluster with `oc` CLI configured
17+
2. Semantic router and OpenWebUI already deployed in `vllm-semantic-router-system` namespace
18+
3. Docker configured to access OpenShift internal registry
19+
20+
### Deploy Dashboard
21+
22+
**Single command deployment:**
23+
24+
```bash
25+
./deploy/openshift/dashboard/build-custom-dashboard.sh
26+
```
27+
28+
This script automatically:
29+
30+
1. Creates the `dashboard-custom` imagestream if needed
31+
2. Builds the patched dashboard image with OpenWebUI integration
32+
3. Pushes the image to the OpenShift internal registry
33+
4. Applies the deployment YAML if the dashboard doesn't exist, or updates the image if it does
34+
5. Waits for the rollout to complete
35+
36+
### Access the Dashboard
37+
38+
```bash
39+
# Get the dashboard URL
40+
oc get route dashboard -n vllm-semantic-router-system -o jsonpath='https://{.spec.host}'
41+
```
42+
43+
Navigate to `/playground` to access the OpenWebUI playground.
44+
45+
## How It Works
46+
47+
### The PlaygroundPage.tsx Patch
48+
49+
OpenShift uses route-based URLs for services. The patch enables the frontend to:
50+
51+
1. Detect when running in OpenShift (by checking the hostname)
52+
2. Dynamically construct the correct OpenWebUI route URL
53+
3. Load OpenWebUI in the iframe using the direct route instead of an embedded proxy path
54+
55+
**Before (doesn't work in OpenShift):**
56+
57+
```javascript
58+
const openWebUIUrl = '/embedded/openwebui/'
59+
```
60+
61+
**After (works in OpenShift):**
62+
63+
```javascript
64+
const getOpenWebUIUrl = () => {
65+
const hostname = window.location.hostname
66+
if (hostname.includes('dashboard-vllm-semantic-router-system')) {
67+
return hostname.replace('dashboard-vllm-semantic-router-system', 'openwebui-vllm-semantic-router-system')
68+
}
69+
return '/embedded/openwebui/'
70+
}
71+
```
72+
73+
### Why a Custom Build?
74+
75+
The upstream dashboard uses `localhost:3001` for local OpenWebUI development. In OpenShift:
76+
77+
- Services are accessed via routes with unique hostnames
78+
- The OpenWebUI URL must be dynamically constructed based on the deployment environment
79+
- The patch is applied during build time to inject this logic
80+
81+
## Notes
82+
83+
- Patches are maintained separately and not committed to dashboard/
84+
- Only used for OpenShift demo deployment
85+
- Original dashboard code remains untouched for upstream compatibility
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# Script to build and deploy custom dashboard image to OpenShift
5+
# This builds the dashboard with OpenWebUI integration patches for demo purposes
6+
7+
NAMESPACE="vllm-semantic-router-system"
8+
IMAGE_NAME="dashboard-custom"
9+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10+
11+
echo "=========================================="
12+
echo "Building Custom Dashboard Image"
13+
echo "=========================================="
14+
15+
# Get OpenShift internal registry
16+
REGISTRY=$(oc get route default-route -n openshift-image-registry -o jsonpath='{.spec.host}' 2>/dev/null)
17+
if [ -z "$REGISTRY" ]; then
18+
echo "Error: Could not find OpenShift internal registry route"
19+
echo "Creating registry route..."
20+
oc patch configs.imageregistry.operator.openshift.io/cluster --type merge -p '{"spec":{"defaultRoute":true}}'
21+
echo "Waiting for route to be created..."
22+
sleep 5
23+
REGISTRY=$(oc get route default-route -n openshift-image-registry -o jsonpath='{.spec.host}')
24+
fi
25+
26+
echo "Registry: $REGISTRY"
27+
echo "Namespace: $NAMESPACE"
28+
29+
# Login to registry
30+
echo ""
31+
echo "Logging in to OpenShift registry..."
32+
TOKEN=$(oc whoami -t)
33+
docker login -u $(oc whoami) -p $TOKEN $REGISTRY
34+
35+
# Prepare build directory
36+
echo ""
37+
echo "Preparing build directory with patched files..."
38+
BUILD_DIR="/tmp/dashboard-build-$$"
39+
mkdir -p $BUILD_DIR
40+
cp -r dashboard $BUILD_DIR/
41+
42+
# Apply patches
43+
echo "Applying OpenWebUI integration patches..."
44+
if [ -f "$SCRIPT_DIR/main.go.patch" ]; then
45+
cp "$SCRIPT_DIR/main.go.patch" "$BUILD_DIR/dashboard/backend/main.go"
46+
echo " ✓ Applied main.go patch (OpenWebUI proxy + auth)"
47+
fi
48+
49+
if [ -f "$SCRIPT_DIR/PlaygroundPage.tsx.patch" ]; then
50+
cp "$SCRIPT_DIR/PlaygroundPage.tsx.patch" "$BUILD_DIR/dashboard/frontend/src/pages/PlaygroundPage.tsx"
51+
echo " ✓ Applied PlaygroundPage.tsx patch (proxy path fix)"
52+
fi
53+
54+
# Create Dockerfile
55+
echo ""
56+
echo "Creating Dockerfile..."
57+
cat > $BUILD_DIR/dashboard/Dockerfile.custom <<'DOCKERFILE_EOF'
58+
# Build frontend
59+
FROM node:18-alpine AS frontend-builder
60+
WORKDIR /app/frontend
61+
COPY frontend/package*.json ./
62+
RUN npm ci
63+
COPY frontend/ ./
64+
RUN npm run build
65+
66+
# Build backend
67+
FROM golang:1.21-alpine AS backend-builder
68+
WORKDIR /app/backend
69+
COPY backend/go.* ./
70+
RUN go mod download
71+
COPY backend/ ./
72+
RUN CGO_ENABLED=0 GOOS=linux go build -o dashboard-server .
73+
74+
# Final image
75+
FROM alpine:3.18
76+
RUN apk add --no-cache ca-certificates
77+
WORKDIR /app
78+
COPY --from=backend-builder /app/backend/dashboard-server .
79+
COPY --from=frontend-builder /app/frontend/dist ./frontend
80+
ENV DASHBOARD_STATIC_DIR=./frontend
81+
EXPOSE 8700
82+
CMD ["./dashboard-server"]
83+
DOCKERFILE_EOF
84+
85+
# Ensure imagestream exists
86+
echo ""
87+
if ! oc get imagestream $IMAGE_NAME -n $NAMESPACE &>/dev/null; then
88+
echo "Creating imagestream $IMAGE_NAME..."
89+
oc create imagestream $IMAGE_NAME -n $NAMESPACE
90+
else
91+
echo "Imagestream $IMAGE_NAME already exists"
92+
fi
93+
94+
# Build image
95+
echo ""
96+
echo "Building docker image for linux/amd64 with no cache..."
97+
cd $BUILD_DIR/dashboard
98+
docker buildx build --no-cache --platform linux/amd64 -f Dockerfile.custom -t $REGISTRY/$NAMESPACE/$IMAGE_NAME:latest --load .
99+
100+
echo ""
101+
echo "Pushing image to registry..."
102+
docker push $REGISTRY/$NAMESPACE/$IMAGE_NAME:latest
103+
104+
# Cleanup
105+
echo ""
106+
echo "Cleaning up build directory..."
107+
rm -rf $BUILD_DIR
108+
109+
# Apply deployment configuration if it doesn't exist
110+
echo ""
111+
if ! oc get deployment dashboard -n $NAMESPACE &>/dev/null; then
112+
echo "Dashboard deployment not found. Applying configuration..."
113+
oc apply -f "$SCRIPT_DIR/dashboard-deployment.yaml"
114+
else
115+
echo "Dashboard deployment already exists. Updating image..."
116+
oc set image deployment/dashboard dashboard=image-registry.openshift-image-registry.svc:5000/$NAMESPACE/$IMAGE_NAME:latest -n $NAMESPACE
117+
fi
118+
119+
echo ""
120+
echo "Waiting for deployment to roll out..."
121+
oc rollout status deployment/dashboard -n $NAMESPACE --timeout=5m
122+
123+
echo ""
124+
echo "=========================================="
125+
echo "Custom Dashboard Deployed Successfully!"
126+
echo "=========================================="
127+
echo ""
128+
echo "Access the dashboard at:"
129+
oc get route dashboard -n $NAMESPACE -o jsonpath='https://{.spec.host}'
130+
echo ""
131+
echo ""
132+
echo "Patches applied:"
133+
echo " - OpenWebUI proxy path fix (PlaygroundPage.tsx)"
134+
echo " - OpenWebUI static assets proxying (main.go)"
135+
echo " - Smart API routing for OpenWebUI (main.go)"
136+
echo " - Authorization header forwarding (main.go)"
137+
echo ""
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
---
2+
apiVersion: v1
3+
kind: ConfigMap
4+
metadata:
5+
name: dashboard-config
6+
namespace: vllm-semantic-router-system
7+
data:
8+
DASHBOARD_PORT: "8700"
9+
TARGET_GRAFANA_URL: "http://grafana:3000"
10+
TARGET_PROMETHEUS_URL: "http://prometheus:9090"
11+
TARGET_ROUTER_API_URL: "http://semantic-router:8080"
12+
TARGET_ROUTER_METRICS_URL: "http://semantic-router:9190/metrics"
13+
TARGET_OPENWEBUI_URL: "http://openwebui:3000"
14+
TARGET_JAEGER_URL: "http://jaeger:16686"
15+
ROUTER_CONFIG_PATH: "/app/config/config.yaml"
16+
17+
---
18+
apiVersion: apps/v1
19+
kind: Deployment
20+
metadata:
21+
name: dashboard
22+
namespace: vllm-semantic-router-system
23+
labels:
24+
app: dashboard
25+
spec:
26+
replicas: 1
27+
selector:
28+
matchLabels:
29+
app: dashboard
30+
template:
31+
metadata:
32+
labels:
33+
app: dashboard
34+
spec:
35+
containers:
36+
- name: dashboard
37+
image: image-registry.openshift-image-registry.svc:5000/vllm-semantic-router-system/dashboard-custom:latest
38+
imagePullPolicy: Always
39+
ports:
40+
- name: http
41+
containerPort: 8700
42+
protocol: TCP
43+
envFrom:
44+
- configMapRef:
45+
name: dashboard-config
46+
volumeMounts:
47+
- name: config
48+
mountPath: /app/config
49+
readOnly: true
50+
livenessProbe:
51+
httpGet:
52+
path: /healthz
53+
port: 8700
54+
initialDelaySeconds: 10
55+
periodSeconds: 30
56+
readinessProbe:
57+
httpGet:
58+
path: /healthz
59+
port: 8700
60+
initialDelaySeconds: 5
61+
periodSeconds: 10
62+
resources:
63+
requests:
64+
memory: "256Mi"
65+
cpu: "100m"
66+
limits:
67+
memory: "512Mi"
68+
cpu: "500m"
69+
volumes:
70+
- name: config
71+
configMap:
72+
name: semantic-router-config
73+
74+
---
75+
apiVersion: v1
76+
kind: Service
77+
metadata:
78+
name: dashboard
79+
namespace: vllm-semantic-router-system
80+
labels:
81+
app: dashboard
82+
spec:
83+
type: ClusterIP
84+
ports:
85+
- name: http
86+
port: 8700
87+
targetPort: 8700
88+
protocol: TCP
89+
selector:
90+
app: dashboard
91+
92+
---
93+
apiVersion: route.openshift.io/v1
94+
kind: Route
95+
metadata:
96+
name: dashboard
97+
namespace: vllm-semantic-router-system
98+
labels:
99+
app: dashboard
100+
spec:
101+
to:
102+
kind: Service
103+
name: dashboard
104+
weight: 100
105+
port:
106+
targetPort: http
107+
tls:
108+
termination: edge
109+
insecureEdgeTerminationPolicy: Redirect
110+
wildcardPolicy: None

0 commit comments

Comments
 (0)