Livrare LOT 1 - Didi
This commit is contained in:
commit
5380c3fc63
990 changed files with 133308 additions and 0 deletions
67
ai_platform/modules/dashboard/deploy/Dockerfile
Normal file
67
ai_platform/modules/dashboard/deploy/Dockerfile
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
# ---- Stage 1: build React SPA ----
|
||||
FROM node:20-alpine AS web-builder
|
||||
WORKDIR /web
|
||||
|
||||
# Build-time SPA config — pass via `--build-arg VITE_*=...`. Defaults
|
||||
# point at the DIDI SSO cluster, which is what production uses.
|
||||
ARG VITE_KEYCLOAK_URL=https://sso.clossers.com
|
||||
ARG VITE_KEYCLOAK_REALM=didi-clients
|
||||
ARG VITE_KEYCLOAK_CLIENT_ID=ai-platform-dashboard
|
||||
ARG VITE_KEYCLOAK_REQUIRED_ROLE=admin
|
||||
ARG VITE_STAGING_MODE=false
|
||||
|
||||
ENV VITE_KEYCLOAK_URL=$VITE_KEYCLOAK_URL \
|
||||
VITE_KEYCLOAK_REALM=$VITE_KEYCLOAK_REALM \
|
||||
VITE_KEYCLOAK_CLIENT_ID=$VITE_KEYCLOAK_CLIENT_ID \
|
||||
VITE_KEYCLOAK_REQUIRED_ROLE=$VITE_KEYCLOAK_REQUIRED_ROLE \
|
||||
VITE_STAGING_MODE=$VITE_STAGING_MODE
|
||||
|
||||
# Cache deps
|
||||
COPY web/package.json web/package-lock.json* ./
|
||||
RUN npm install --no-audit --no-fund
|
||||
# Build
|
||||
COPY web/index.html web/tsconfig.json web/vite.config.ts ./
|
||||
COPY web/src ./src
|
||||
RUN npm run build
|
||||
|
||||
# ---- Stage 2: build Python deps ----
|
||||
FROM python:3.11.12-slim AS builder
|
||||
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.10 /uv /uvx /bin/
|
||||
|
||||
ENV UV_LINK_MODE=copy \
|
||||
UV_COMPILE_BYTECODE=1 \
|
||||
UV_PROJECT_ENVIRONMENT=/app/.venv
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY pyproject.toml ./
|
||||
RUN uv venv /app/.venv && uv pip install --python /app/.venv/bin/python \
|
||||
fastapi uvicorn[standard] pydantic pydantic-settings httpx \
|
||||
"sqlalchemy[asyncio]>=2.0.36" asyncpg alembic jinja2 python-multipart
|
||||
|
||||
COPY src ./src
|
||||
|
||||
RUN uv pip install --python /app/.venv/bin/python -e .
|
||||
|
||||
# ---- Stage 3: runtime ----
|
||||
FROM python:3.11.12-slim
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libpq5 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=builder /app /app
|
||||
COPY --from=web-builder /web/dist /app/web_dist
|
||||
|
||||
ENV PATH="/app/.venv/bin:$PATH" \
|
||||
PYTHONPATH=/app/src \
|
||||
PYTHONUNBUFFERED=1
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
EXPOSE 51300
|
||||
|
||||
CMD ["uvicorn", "dashboard.api.app:app", "--host", "0.0.0.0", "--port", "51300"]
|
||||
50
ai_platform/modules/dashboard/deploy/deploy.sh
Normal file
50
ai_platform/modules/dashboard/deploy/deploy.sh
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
[[ -f .env ]] && set -a && source .env && set +a
|
||||
|
||||
check_required_var() {
|
||||
if [[ -z "${!1:-}" ]]; then
|
||||
echo "ERROR: Required variable $1 not set (check .env)"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: ./deploy.sh {up|down|logs|restart}
|
||||
|
||||
Actions:
|
||||
up Build and start dashboard + database
|
||||
down Stop containers (volumes preserved)
|
||||
logs Tail logs
|
||||
restart Restart dashboard without rebuild
|
||||
EOF
|
||||
}
|
||||
|
||||
check_required_var DASHBOARD_DB_USER
|
||||
check_required_var DASHBOARD_DB_PASSWORD
|
||||
check_required_var DASHBOARD_DB_NAME
|
||||
check_required_var DASHBOARD_EXTERNAL_URL
|
||||
|
||||
case "${1:-}" in
|
||||
up)
|
||||
docker compose --profile dashboard up -d --build
|
||||
echo "Dashboard started at ${DASHBOARD_EXTERNAL_URL}"
|
||||
;;
|
||||
down)
|
||||
docker compose --profile dashboard down
|
||||
;;
|
||||
logs)
|
||||
docker compose --profile dashboard logs -f --tail=100
|
||||
;;
|
||||
restart)
|
||||
docker compose --profile dashboard restart
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
102
ai_platform/modules/dashboard/deploy/docker-compose.yml
Normal file
102
ai_platform/modules/dashboard/deploy/docker-compose.yml
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
# Dashboard Module - Docker Compose
|
||||
#
|
||||
# Port Allocation (Dev: 51300):
|
||||
# 51300 - Dashboard API + Web UI
|
||||
# 15432 - PostgreSQL (optional host exposure for debugging)
|
||||
#
|
||||
# Naming Convention: didiAI-{module}-{service}
|
||||
|
||||
networks:
|
||||
didi-network:
|
||||
external: true # single shared network for all DIDI + AI platform stacks
|
||||
|
||||
volumes:
|
||||
dashboard_db_data:
|
||||
driver: local
|
||||
|
||||
services:
|
||||
# ==========================================================================
|
||||
# PostgreSQL
|
||||
# ==========================================================================
|
||||
dashboard-db:
|
||||
container_name: didiAI-dashboard-db
|
||||
image: postgres:16-alpine
|
||||
networks:
|
||||
- didi-network
|
||||
environment:
|
||||
- POSTGRES_USER=${DASHBOARD_DB_USER}
|
||||
- POSTGRES_PASSWORD=${DASHBOARD_DB_PASSWORD}
|
||||
- POSTGRES_DB=${DASHBOARD_DB_NAME}
|
||||
volumes:
|
||||
- dashboard_db_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "15432:5432"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${DASHBOARD_DB_USER} -d ${DASHBOARD_DB_NAME}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- dashboard
|
||||
|
||||
# ==========================================================================
|
||||
# Dashboard API + Web UI
|
||||
# ==========================================================================
|
||||
dashboard-api:
|
||||
container_name: didiAI-dashboard
|
||||
image: didiai-dashboard
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: deploy/Dockerfile
|
||||
args:
|
||||
# SPA build-time config — Keycloak settings get baked into the JS bundle.
|
||||
# Default: local Keycloak proxied through admin nginx at /auth/.
|
||||
VITE_KEYCLOAK_URL: ${VITE_KEYCLOAK_URL:-https://10.11.10.11:3001/auth}
|
||||
VITE_KEYCLOAK_REALM: ${VITE_KEYCLOAK_REALM:-didi-admins}
|
||||
VITE_KEYCLOAK_CLIENT_ID: ${VITE_KEYCLOAK_CLIENT_ID:-ai-platform-dashboard}
|
||||
VITE_KEYCLOAK_REQUIRED_ROLE: ${VITE_KEYCLOAK_REQUIRED_ROLE:-admin}
|
||||
VITE_STAGING_MODE: ${VITE_STAGING_MODE:-false}
|
||||
ports:
|
||||
- "51300:51300"
|
||||
networks:
|
||||
- didi-network
|
||||
depends_on:
|
||||
dashboard-db:
|
||||
condition: service_healthy
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- DASHBOARD_HOST=0.0.0.0
|
||||
- DASHBOARD_PORT=51300
|
||||
- DASHBOARD_DATABASE_URL=postgresql+asyncpg://${DASHBOARD_DB_USER}:${DASHBOARD_DB_PASSWORD}@didiAI-dashboard-db:5432/${DASHBOARD_DB_NAME}
|
||||
- DASHBOARD_BRAIN_URL=${DASHBOARD_BRAIN_URL:-http://didibrain-api:8090}
|
||||
# Keycloak SSO (leave empty to disable JWT auth)
|
||||
- DASHBOARD_KEYCLOAK_URL=${DASHBOARD_KEYCLOAK_URL:-}
|
||||
- DASHBOARD_KEYCLOAK_REALM=${DASHBOARD_KEYCLOAK_REALM:-didi-clients}
|
||||
- DASHBOARD_KEYCLOAK_CLIENT_ID=${DASHBOARD_KEYCLOAK_CLIENT_ID:-ai-platform-dashboard}
|
||||
- DASHBOARD_KEYCLOAK_REQUIRED_ROLE=${DASHBOARD_KEYCLOAK_REQUIRED_ROLE:-admin}
|
||||
- DASHBOARD_STAGING_MODE=${DASHBOARD_STAGING_MODE:-false}
|
||||
# OTel — traces to Jaeger via OTel Collector
|
||||
- OTEL_EXPORTER_OTLP_ENDPOINT=${OTEL_EXPORTER_OTLP_ENDPOINT:-http://didi-otel-collector:4317}
|
||||
- OTEL_SERVICE_NAME=didiAI-dashboard
|
||||
# Module health endpoint overrides — point at real upstreams
|
||||
# (raw vLLM at 10.11.10.15 instead of the unbuilt FastAPI wrapper at 10.11.10.17:14100/14200)
|
||||
- DASHBOARD_EMBEDDINGS_HEALTH_URL=${DASHBOARD_EMBEDDINGS_HEALTH_URL:-http://10.11.10.15:8200/v1/models}
|
||||
- DASHBOARD_RERANK_HEALTH_URL=${DASHBOARD_RERANK_HEALTH_URL:-http://10.11.10.15:8100/v1/models}
|
||||
# Catalog runs locally on Docker DNS; /v1/status is the aggregated reachability probe
|
||||
# (catalog does not expose /v1/info — it consumes it from other modules)
|
||||
- DASHBOARD_CATALOG_HEALTH_URL=${DASHBOARD_CATALOG_HEALTH_URL:-http://didiAI-catalog-api:11000/v1/status}
|
||||
# Gateway is nginx — only /health is auth-free and inline
|
||||
- DASHBOARD_GATEWAY_HEALTH_URL=${DASHBOARD_GATEWAY_HEALTH_URL:-http://didiAI-gateway:11000/health}
|
||||
# Brain doesn't expose /v1/info — point at /health (matches catalog rationale)
|
||||
- DASHBOARD_BRAIN_HEALTH_URL=${DASHBOARD_BRAIN_HEALTH_URL:-http://didibrain-api:8090/health}
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:51300/health')"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- dashboard
|
||||
209
ai_platform/modules/dashboard/deploy/setup-keycloak.sh
Normal file
209
ai_platform/modules/dashboard/deploy/setup-keycloak.sh
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
#!/usr/bin/env bash
|
||||
# Set up Keycloak client `ai-platform-dashboard` on sso.clossers.com realm `didi-clients`.
|
||||
# Idempotent — safe to re-run; existing client gets updated to match this config.
|
||||
#
|
||||
# Usage:
|
||||
# KC_ADMIN_USER=admin KC_ADMIN_PASS=admin123 ./setup-keycloak.sh
|
||||
#
|
||||
# Optional overrides:
|
||||
# KC_BASE_URL=https://sso.clossers.com
|
||||
# KC_REALM=didi-clients
|
||||
# KC_CLIENT_ID=ai-platform-dashboard
|
||||
# DASHBOARD_HOST=10.11.10.12 # used to build redirect URI
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Keycloak admin endpoints redirect public sso.clossers.com → internal
|
||||
# sso.clossers.local. Token issuer must match the host you call, so we use the
|
||||
# internal name end-to-end (this host has DNS for it).
|
||||
KC_BASE_URL="${KC_BASE_URL:-https://sso.clossers.local}"
|
||||
KC_REALM="${KC_REALM:-didi-clients}"
|
||||
KC_CLIENT_ID="${KC_CLIENT_ID:-ai-platform-dashboard}"
|
||||
KC_ADMIN_USER="${KC_ADMIN_USER:-admin}"
|
||||
KC_ADMIN_PASS="${KC_ADMIN_PASS:?Set KC_ADMIN_PASS}"
|
||||
DASHBOARD_HOST="${DASHBOARD_HOST:-10.11.10.12}"
|
||||
DASHBOARD_PORT="${DASHBOARD_PORT:-51300}"
|
||||
|
||||
echo "→ Authenticating to Keycloak master realm…"
|
||||
ADMIN_TOKEN=$(
|
||||
curl -ksS -X POST "$KC_BASE_URL/realms/master/protocol/openid-connect/token" \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "client_id=admin-cli&grant_type=password&username=$KC_ADMIN_USER&password=$KC_ADMIN_PASS" \
|
||||
| python3 -c "import json,sys; print(json.load(sys.stdin)['access_token'])"
|
||||
)
|
||||
[ -n "$ADMIN_TOKEN" ] || { echo "FAIL: could not get admin token"; exit 1; }
|
||||
echo " OK (token length ${#ADMIN_TOKEN})"
|
||||
|
||||
# ---------- Step 1: ensure realm role 'admin' exists ----------
|
||||
echo "→ Checking realm role 'admin' exists in realm '$KC_REALM'…"
|
||||
RAW=$(curl -ksSL -H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||
"$KC_BASE_URL/admin/realms/$KC_REALM/roles/admin")
|
||||
if echo "$RAW" | grep -q '"name"'; then
|
||||
echo " OK — role 'admin' present"
|
||||
else
|
||||
echo " NOT found — creating…"
|
||||
curl -ksSL -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name":"admin","description":"Platform admin (DIDI + AI platform)"}' \
|
||||
"$KC_BASE_URL/admin/realms/$KC_REALM/roles"
|
||||
echo " CREATED"
|
||||
fi
|
||||
|
||||
# ---------- Step 2: ensure client `ai-platform-dashboard` exists ----------
|
||||
echo "→ Checking client '$KC_CLIENT_ID' exists…"
|
||||
EXISTING=$(curl -ksSL -H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||
"$KC_BASE_URL/admin/realms/$KC_REALM/clients?clientId=$KC_CLIENT_ID")
|
||||
CLIENT_UUID=$(echo "$EXISTING" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d[0]['id'] if d else '')")
|
||||
|
||||
REDIRECTS=$(python3 -c "
|
||||
import json
|
||||
print(json.dumps([
|
||||
'http://$DASHBOARD_HOST:$DASHBOARD_PORT/admin-ai/*',
|
||||
'https://$DASHBOARD_HOST:$DASHBOARD_PORT/admin-ai/*',
|
||||
'http://localhost:$DASHBOARD_PORT/admin-ai/*',
|
||||
'http://localhost:5173/*',
|
||||
# Reverse-proxy URL (admin-dashboard nginx exposes /admin-ai/ on port 3000)
|
||||
'https://$DASHBOARD_HOST:3000/admin-ai/*',
|
||||
]))
|
||||
")
|
||||
|
||||
PAYLOAD=$(python3 -c "
|
||||
import json, os
|
||||
print(json.dumps({
|
||||
'clientId': '$KC_CLIENT_ID',
|
||||
'name': 'AI Platform Admin Dashboard',
|
||||
'description': 'Reskinned admin dashboard for the AI platform (web/llm/embeddings/rerank/audio/video/catalog/brain)',
|
||||
'rootUrl': 'http://$DASHBOARD_HOST:$DASHBOARD_PORT/admin-ai/',
|
||||
'baseUrl': '/admin-ai/',
|
||||
'enabled': True,
|
||||
'protocol': 'openid-connect',
|
||||
'publicClient': True,
|
||||
'standardFlowEnabled': True,
|
||||
'directAccessGrantsEnabled': False,
|
||||
'serviceAccountsEnabled': False,
|
||||
'frontchannelLogout': True,
|
||||
'redirectUris': $REDIRECTS,
|
||||
'webOrigins': ['+'],
|
||||
'attributes': {
|
||||
'pkce.code.challenge.method': 'S256',
|
||||
'post.logout.redirect.uris': '+',
|
||||
},
|
||||
}))
|
||||
")
|
||||
|
||||
if [ -z "$CLIENT_UUID" ]; then
|
||||
echo " NOT found — creating…"
|
||||
curl -ksSL -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$PAYLOAD" \
|
||||
"$KC_BASE_URL/admin/realms/$KC_REALM/clients"
|
||||
CLIENT_UUID=$(
|
||||
curl -ksSL -H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||
"$KC_BASE_URL/admin/realms/$KC_REALM/clients?clientId=$KC_CLIENT_ID" \
|
||||
| python3 -c "import json,sys; print(json.load(sys.stdin)[0]['id'])"
|
||||
)
|
||||
echo " CREATED uuid=$CLIENT_UUID"
|
||||
else
|
||||
echo " Found uuid=$CLIENT_UUID — updating to current spec…"
|
||||
curl -ksSL -X PUT -H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$PAYLOAD" \
|
||||
"$KC_BASE_URL/admin/realms/$KC_REALM/clients/$CLIENT_UUID"
|
||||
echo " UPDATED"
|
||||
fi
|
||||
|
||||
# ---------- Step 3: smoke test JWKS reachable ----------
|
||||
echo "→ Smoke test: JWKS endpoint reachable…"
|
||||
JWKS_KEYS=$(curl -ksS \
|
||||
"$KC_BASE_URL/realms/$KC_REALM/protocol/openid-connect/certs" \
|
||||
| python3 -c "import json,sys; print(len(json.load(sys.stdin).get('keys',[])))")
|
||||
echo " OK — JWKS returns $JWKS_KEYS keys"
|
||||
|
||||
# ---------- Step 3b: ensure admin-dashboard client also exists (DIDI side) ----------
|
||||
# This is a no-op when the client already exists with correct config; included
|
||||
# so a fresh Keycloak install gets both apps wired in one go.
|
||||
ADMIN_DASH_CLIENT="admin-dashboard"
|
||||
echo "→ Checking client '$ADMIN_DASH_CLIENT' (DIDI admin) exists…"
|
||||
EXISTING_AD=$(curl -ksSL -H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||
"$KC_BASE_URL/admin/realms/$KC_REALM/clients?clientId=$ADMIN_DASH_CLIENT")
|
||||
ADMIN_DASH_UUID=$(echo "$EXISTING_AD" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d[0]['id'] if d else '')")
|
||||
|
||||
ADMIN_DASH_REDIRECTS=$(python3 -c "
|
||||
import json
|
||||
print(json.dumps([
|
||||
'https://$DASHBOARD_HOST:3000/admin/*',
|
||||
'https://$DASHBOARD_HOST:3000/admin-backend/*',
|
||||
'http://localhost:3000/admin/*',
|
||||
]))
|
||||
")
|
||||
|
||||
ADMIN_DASH_PAYLOAD=$(python3 -c "
|
||||
import json
|
||||
print(json.dumps({
|
||||
'clientId': '$ADMIN_DASH_CLIENT',
|
||||
'name': 'DIDI Admin Dashboard',
|
||||
'description': 'Backend admin (framework config, users, moderation queue, history)',
|
||||
'rootUrl': 'https://$DASHBOARD_HOST:3000/admin/',
|
||||
'baseUrl': '/admin/',
|
||||
'enabled': True,
|
||||
'protocol': 'openid-connect',
|
||||
'publicClient': True,
|
||||
'standardFlowEnabled': True,
|
||||
'directAccessGrantsEnabled': False,
|
||||
'redirectUris': $ADMIN_DASH_REDIRECTS,
|
||||
'webOrigins': ['+'],
|
||||
'attributes': {
|
||||
'pkce.code.challenge.method': 'S256',
|
||||
'post.logout.redirect.uris': '+',
|
||||
},
|
||||
}))
|
||||
")
|
||||
|
||||
if [ -z "$ADMIN_DASH_UUID" ]; then
|
||||
echo " NOT found — creating…"
|
||||
curl -ksSL -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$ADMIN_DASH_PAYLOAD" \
|
||||
"$KC_BASE_URL/admin/realms/$KC_REALM/clients"
|
||||
ADMIN_DASH_UUID=$(
|
||||
curl -ksSL -H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||
"$KC_BASE_URL/admin/realms/$KC_REALM/clients?clientId=$ADMIN_DASH_CLIENT" \
|
||||
| python3 -c "import json,sys; print(json.load(sys.stdin)[0]['id'])"
|
||||
)
|
||||
echo " CREATED uuid=$ADMIN_DASH_UUID"
|
||||
else
|
||||
echo " Found uuid=$ADMIN_DASH_UUID — leaving config as-is (would overwrite custom redirect URIs)"
|
||||
fi
|
||||
|
||||
# ---------- Step 4: print summary ----------
|
||||
cat <<EOF
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Keycloak setup complete.
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Realm: $KC_REALM
|
||||
Client: $KC_CLIENT_ID (uuid=$CLIENT_UUID)
|
||||
Role: admin (realm)
|
||||
Issuer: $KC_BASE_URL/realms/$KC_REALM
|
||||
JWKS: $KC_BASE_URL/realms/$KC_REALM/protocol/openid-connect/certs
|
||||
|
||||
REDIRECTS:
|
||||
$(python3 -c "import json; [print(f' - {u}') for u in $REDIRECTS]")
|
||||
|
||||
NEXT STEPS:
|
||||
1. Verify YOUR user has the 'admin' realm role assigned in $KC_REALM.
|
||||
(If you can already log into the DIDI admin-dashboard, you do.)
|
||||
|
||||
2. Update dashboard/.env:
|
||||
DASHBOARD_KEYCLOAK_URL=$KC_BASE_URL
|
||||
DASHBOARD_STAGING_MODE=false
|
||||
VITE_STAGING_MODE=false
|
||||
|
||||
3. Rebuild + redeploy:
|
||||
cd /home/admin365/didi_mono/ai_platform/modules/dashboard/deploy
|
||||
docker compose --profile dashboard up -d --build dashboard-api
|
||||
|
||||
4. Open http://$DASHBOARD_HOST:$DASHBOARD_PORT/v2/
|
||||
→ Keycloak login redirect → enter creds → SPA loads with your JWT.
|
||||
|
||||
EOF
|
||||
Loading…
Add table
Add a link
Reference in a new issue