Split architecture: Trainer=backend, Demo=dashboard
Browse filesDockerfile
- Replaces hard-coded uvicorn CMD with /bin/sh /app/start.sh
- Sets ownership correctly before switching to non-root user
start.sh (new)
- Reads BACKEND_URL env var β writes it into dashboard/js/config.js
- Reads SERVE_DASHBOARD_ONLY env var:
0 / unset β full stack via dashboard_server.py (Trainer Space)
1 β static-only via serve_demo.py (Demo Space)
serve_demo.py (new)
- Minimal FastAPI + StaticFiles server for the Demo space
- Serves dashboard/ on port 7860 with CORS enabled
dashboard/js/config.js (new)
- Sets window.CYBERSOC_BACKEND_URL = '' by default
- Overwritten at container startup by start.sh when BACKEND_URL is set
dashboard/js/api.js
- Reads window.CYBERSOC_BACKEND_URL set by config.js
- _wsUrl() and _httpBase() use the override URL when set,
fall back to same-origin auto-detection when empty
dashboard/index.html
- Loads js/config.js before api.js so the override is available
HF Space setup:
CyberSOC-trainer β no extra env vars (full backend mode)
Demo β SERVE_DASHBOARD_ONLY=1
BACKEND_URL=https://ajay00747-cybersoc-trainer.hf.space
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Dockerfile +14 -10
- dashboard/index.html +1 -0
- dashboard/js/api.js +15 -0
- dashboard/js/config.js +12 -0
- serve_demo.py +43 -0
- start.sh +37 -0
|
@@ -1,19 +1,23 @@
|
|
| 1 |
FROM python:3.10-slim
|
| 2 |
|
| 3 |
-
# Create
|
| 4 |
RUN useradd -m -u 1000 user
|
| 5 |
-
USER user
|
| 6 |
-
ENV PATH="/home/user/.local/bin:$PATH"
|
| 7 |
|
| 8 |
WORKDIR /app
|
| 9 |
|
| 10 |
-
#
|
| 11 |
-
COPY
|
| 12 |
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
| 13 |
|
| 14 |
-
# Copy
|
| 15 |
-
COPY
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
-
#
|
| 18 |
-
#
|
| 19 |
-
|
|
|
|
|
|
|
|
|
| 1 |
FROM python:3.10-slim
|
| 2 |
|
| 3 |
+
# Create non-root user
|
| 4 |
RUN useradd -m -u 1000 user
|
|
|
|
|
|
|
| 5 |
|
| 6 |
WORKDIR /app
|
| 7 |
|
| 8 |
+
# Install dependencies as root so pip cache is shared
|
| 9 |
+
COPY ./requirements.txt requirements.txt
|
| 10 |
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
| 11 |
|
| 12 |
+
# Copy project files and set ownership
|
| 13 |
+
COPY . /app
|
| 14 |
+
RUN chmod +x /app/start.sh && chown -R user:user /app
|
| 15 |
+
|
| 16 |
+
USER user
|
| 17 |
+
ENV PATH="/home/user/.local/bin:$PATH"
|
| 18 |
|
| 19 |
+
# start.sh reads BACKEND_URL and SERVE_DASHBOARD_ONLY env vars:
|
| 20 |
+
# Trainer Space β SERVE_DASHBOARD_ONLY unset β full backend (dashboard_server.py)
|
| 21 |
+
# Demo Space β SERVE_DASHBOARD_ONLY=1 β static dashboard only (serve_demo.py)
|
| 22 |
+
# BACKEND_URL=https://ajay00747-cybersoc-trainer.hf.space
|
| 23 |
+
CMD ["/bin/sh", "/app/start.sh"]
|
|
@@ -290,6 +290,7 @@
|
|
| 290 |
</main><!-- /main-grid -->
|
| 291 |
|
| 292 |
<!-- Scripts (order matters) -->
|
|
|
|
| 293 |
<script src="js/animations.js"></script>
|
| 294 |
<script src="js/api.js"></script>
|
| 295 |
<script src="js/graphs.js"></script>
|
|
|
|
| 290 |
</main><!-- /main-grid -->
|
| 291 |
|
| 292 |
<!-- Scripts (order matters) -->
|
| 293 |
+
<script src="js/config.js"></script>
|
| 294 |
<script src="js/animations.js"></script>
|
| 295 |
<script src="js/api.js"></script>
|
| 296 |
<script src="js/graphs.js"></script>
|
|
@@ -49,8 +49,22 @@ const API = (() => {
|
|
| 49 |
}
|
| 50 |
})();
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
// ββ WebSocket URL ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 53 |
function _wsUrl() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
if (typeof window === 'undefined') {
|
| 55 |
return `ws://localhost:8000/ws/${_sessionId}`;
|
| 56 |
}
|
|
@@ -63,6 +77,7 @@ const API = (() => {
|
|
| 63 |
|
| 64 |
// HTTP base URL β used only by checkConnection() which pings /health over HTTP
|
| 65 |
function _httpBase() {
|
|
|
|
| 66 |
if (typeof window === 'undefined') return 'http://localhost:8000';
|
| 67 |
const { protocol, hostname, port } = window.location;
|
| 68 |
if (protocol === 'file:') return 'http://localhost:8000';
|
|
|
|
| 49 |
}
|
| 50 |
})();
|
| 51 |
|
| 52 |
+
// ββ External backend override (set by dashboard/js/config.js in Demo mode) β
|
| 53 |
+
// config.js sets window.CYBERSOC_BACKEND_URL to the trainer Space URL.
|
| 54 |
+
// Empty string β auto-detect from page origin (default for full-stack mode).
|
| 55 |
+
const _backendOverride = (
|
| 56 |
+
typeof window !== 'undefined' &&
|
| 57 |
+
typeof window.CYBERSOC_BACKEND_URL === 'string' &&
|
| 58 |
+
window.CYBERSOC_BACKEND_URL.trim()
|
| 59 |
+
) ? window.CYBERSOC_BACKEND_URL.trim().replace(/\/$/, '') : '';
|
| 60 |
+
|
| 61 |
// ββ WebSocket URL ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 62 |
function _wsUrl() {
|
| 63 |
+
if (_backendOverride) {
|
| 64 |
+
const wsProto = _backendOverride.startsWith('https') ? 'wss:' : 'ws:';
|
| 65 |
+
const host = _backendOverride.replace(/^https?:\/\//, '');
|
| 66 |
+
return `${wsProto}//${host}/ws/${_sessionId}`;
|
| 67 |
+
}
|
| 68 |
if (typeof window === 'undefined') {
|
| 69 |
return `ws://localhost:8000/ws/${_sessionId}`;
|
| 70 |
}
|
|
|
|
| 77 |
|
| 78 |
// HTTP base URL β used only by checkConnection() which pings /health over HTTP
|
| 79 |
function _httpBase() {
|
| 80 |
+
if (_backendOverride) return _backendOverride;
|
| 81 |
if (typeof window === 'undefined') return 'http://localhost:8000';
|
| 82 |
const { protocol, hostname, port } = window.location;
|
| 83 |
if (protocol === 'file:') return 'http://localhost:8000';
|
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// CyberSOC Backend URL Configuration
|
| 2 |
+
// -------------------------------------------------------------
|
| 3 |
+
// Leave as empty string when the dashboard and backend are on
|
| 4 |
+
// the same origin (default for the CyberSOC-upgraded space).
|
| 5 |
+
//
|
| 6 |
+
// Set to the trainer Space URL for standalone Dashboard mode:
|
| 7 |
+
// window.CYBERSOC_BACKEND_URL = 'https://ajay00747-cybersoc-trainer.hf.space';
|
| 8 |
+
//
|
| 9 |
+
// In production this file is overwritten at container startup by
|
| 10 |
+
// start.sh using the BACKEND_URL environment variable.
|
| 11 |
+
// -------------------------------------------------------------
|
| 12 |
+
window.CYBERSOC_BACKEND_URL = '';
|
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Minimal static file server for the CyberSOC Demo HF Space.
|
| 3 |
+
|
| 4 |
+
Serves the dashboard/ directory on port 7860.
|
| 5 |
+
The BACKEND_URL environment variable is injected into
|
| 6 |
+
dashboard/js/config.js at startup by start.sh before this
|
| 7 |
+
process is launched.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import os
|
| 11 |
+
import uvicorn
|
| 12 |
+
from fastapi import FastAPI
|
| 13 |
+
from fastapi.responses import RedirectResponse
|
| 14 |
+
from fastapi.staticfiles import StaticFiles
|
| 15 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 16 |
+
|
| 17 |
+
app = FastAPI(title="CyberSOC Demo Dashboard")
|
| 18 |
+
|
| 19 |
+
app.add_middleware(
|
| 20 |
+
CORSMiddleware,
|
| 21 |
+
allow_origins=["*"],
|
| 22 |
+
allow_methods=["*"],
|
| 23 |
+
allow_headers=["*"],
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
@app.get("/")
|
| 28 |
+
def root():
|
| 29 |
+
return RedirectResponse(url="/index.html")
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
@app.get("/health")
|
| 33 |
+
def health():
|
| 34 |
+
return {"status": "ok", "service": "cybersoc-demo"}
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
# Serve everything in the dashboard/ folder
|
| 38 |
+
_dashboard_dir = os.path.join(os.path.dirname(__file__), "dashboard")
|
| 39 |
+
app.mount("/", StaticFiles(directory=_dashboard_dir, html=True), name="static")
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
if __name__ == "__main__":
|
| 43 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/sh
|
| 2 |
+
# CyberSOC container startup script
|
| 3 |
+
#
|
| 4 |
+
# Environment variables (set in HF Space settings):
|
| 5 |
+
# BACKEND_URL β URL of the CyberSOC trainer Space, e.g.
|
| 6 |
+
# https://ajay00747-cybersoc-trainer.hf.space
|
| 7 |
+
# Leave unset for same-origin (full-stack mode).
|
| 8 |
+
# SERVE_DASHBOARD_ONLY β Set to "1" for the Demo space (static files only).
|
| 9 |
+
# Leave unset or "0" for the Trainer space (full API).
|
| 10 |
+
#
|
| 11 |
+
# Demo Space settings:
|
| 12 |
+
# BACKEND_URL = https://ajay00747-cybersoc-trainer.hf.space
|
| 13 |
+
# SERVE_DASHBOARD_ONLY = 1
|
| 14 |
+
#
|
| 15 |
+
# Trainer Space settings:
|
| 16 |
+
# (no extra env vars required)
|
| 17 |
+
|
| 18 |
+
set -e
|
| 19 |
+
|
| 20 |
+
# ββ Inject backend URL into config.js ββββββββββββββββββββββββββββββββββββββββ
|
| 21 |
+
CONFIG_JS="/app/dashboard/js/config.js"
|
| 22 |
+
if [ -n "${BACKEND_URL}" ]; then
|
| 23 |
+
printf "window.CYBERSOC_BACKEND_URL = '%s';\n" "${BACKEND_URL}" > "${CONFIG_JS}"
|
| 24 |
+
echo "[startup] Demo mode β backend URL: ${BACKEND_URL}"
|
| 25 |
+
else
|
| 26 |
+
printf "window.CYBERSOC_BACKEND_URL = '';\n" > "${CONFIG_JS}"
|
| 27 |
+
echo "[startup] Full-stack mode β backend on same origin"
|
| 28 |
+
fi
|
| 29 |
+
|
| 30 |
+
# ββ Launch the correct server βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 31 |
+
if [ "${SERVE_DASHBOARD_ONLY:-0}" = "1" ]; then
|
| 32 |
+
echo "[startup] Serving dashboard only (serve_demo.py) on port 7860"
|
| 33 |
+
exec python /app/serve_demo.py
|
| 34 |
+
else
|
| 35 |
+
echo "[startup] Serving full stack (dashboard_server.py) on port 7860"
|
| 36 |
+
exec uvicorn dashboard_server:app --host 0.0.0.0 --port 7860
|
| 37 |
+
fi
|