fixed network issue with multiple managed rooms
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -13,6 +13,8 @@ venv/
|
|||||||
gen/
|
gen/
|
||||||
|
|
||||||
# Room configurations (separate repo - contains credentials and room-specific data)
|
# Room configurations (separate repo - contains credentials and room-specific data)
|
||||||
# Keep cfg/standalone/ as sample, ignore actual rooms
|
# Keep cfg/standalone/ and cfg/sample/ as templates, ignore actual rooms
|
||||||
cfg/amar/
|
cfg/amar/
|
||||||
cfg/dlt/
|
cfg/dlt/
|
||||||
|
# Add new rooms here as they are created
|
||||||
|
# cfg/<room>/
|
||||||
|
|||||||
6
cfg/.gitignore
vendored
6
cfg/.gitignore
vendored
@@ -1,13 +1,15 @@
|
|||||||
# Environment files with credentials (use .env.example as template)
|
# Environment files with credentials (use .env.example as template)
|
||||||
**/.env
|
**/.env
|
||||||
|
!sample/**/.env
|
||||||
|
|
||||||
# Database dumps (sensitive data)
|
# Database dumps (sensitive data)
|
||||||
*/dumps/*.sql
|
**/dumps/*.sql
|
||||||
|
|
||||||
# Python
|
# Python
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
*.pyo
|
*.pyo
|
||||||
|
|
||||||
# Standalone is kept in main soleprint repo as sample
|
# These are kept in main soleprint repo as templates
|
||||||
standalone/
|
standalone/
|
||||||
|
sample/
|
||||||
|
|||||||
15
cfg/sample/config.json
Normal file
15
cfg/sample/config.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"managed": {
|
||||||
|
"name": "sample",
|
||||||
|
"repos": {
|
||||||
|
"frontend": "/path/to/your/frontend/repo",
|
||||||
|
"backend": "/path/to/your/backend/repo"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"framework": {
|
||||||
|
"name": "soleprint"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
}
|
||||||
28
cfg/sample/ctrl/start.sh
Executable file
28
cfg/sample/ctrl/start.sh
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Start all sample services
|
||||||
|
# Usage: ./ctrl/start.sh [-d]
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||||
|
|
||||||
|
DETACH=""
|
||||||
|
if [[ "$1" == "-d" ]]; then
|
||||||
|
DETACH="-d"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== Starting sample services ==="
|
||||||
|
|
||||||
|
# Start soleprint
|
||||||
|
echo "Starting soleprint..."
|
||||||
|
cd "$ROOT_DIR/soleprint"
|
||||||
|
docker compose up $DETACH &
|
||||||
|
|
||||||
|
# Start sample app
|
||||||
|
if [[ -f "$ROOT_DIR/sample/docker-compose.yml" ]]; then
|
||||||
|
echo "Starting sample app..."
|
||||||
|
cd "$ROOT_DIR/sample"
|
||||||
|
docker compose up $DETACH &
|
||||||
|
fi
|
||||||
|
|
||||||
|
wait
|
||||||
|
echo "=== All services started ==="
|
||||||
22
cfg/sample/ctrl/stop.sh
Executable file
22
cfg/sample/ctrl/stop.sh
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Stop all sample services
|
||||||
|
# Usage: ./ctrl/stop.sh
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||||
|
|
||||||
|
echo "=== Stopping sample services ==="
|
||||||
|
|
||||||
|
# Stop sample app
|
||||||
|
if [[ -f "$ROOT_DIR/sample/docker-compose.yml" ]]; then
|
||||||
|
echo "Stopping sample app..."
|
||||||
|
cd "$ROOT_DIR/sample"
|
||||||
|
docker compose down
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stop soleprint
|
||||||
|
echo "Stopping soleprint..."
|
||||||
|
cd "$ROOT_DIR/soleprint"
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
echo "=== All services stopped ==="
|
||||||
15
cfg/sample/sample/.env
Normal file
15
cfg/sample/sample/.env
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# Sample Managed App - Environment Configuration
|
||||||
|
# =============================================================================
|
||||||
|
# Copy this to cfg/<your-room>/<app-name>/.env and customize
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# DEPLOYMENT
|
||||||
|
# =============================================================================
|
||||||
|
DEPLOYMENT_NAME=sample
|
||||||
|
NETWORK_NAME=sample_network
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PORTS
|
||||||
|
# =============================================================================
|
||||||
|
FRONTEND_PORT=3020
|
||||||
19
cfg/sample/sample/docker-compose.yml
Normal file
19
cfg/sample/sample/docker-compose.yml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Sample Mock Frontend
|
||||||
|
# Simple nginx serving static HTML
|
||||||
|
#
|
||||||
|
# For a real app, customize this with your actual services
|
||||||
|
|
||||||
|
services:
|
||||||
|
frontend:
|
||||||
|
image: nginx:alpine
|
||||||
|
container_name: ${DEPLOYMENT_NAME}_frontend
|
||||||
|
volumes:
|
||||||
|
- ./index.html:/usr/share/nginx/html/index.html:ro
|
||||||
|
ports:
|
||||||
|
- "${FRONTEND_PORT}:80"
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
name: ${NETWORK_NAME}
|
||||||
35
cfg/sample/sample/index.html
Normal file
35
cfg/sample/sample/index.html
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Sample App</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
h1 { color: #333; }
|
||||||
|
p { color: #666; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Sample App</h1>
|
||||||
|
<p>This is a sample managed room for Soleprint.</p>
|
||||||
|
<p>Replace this with your actual frontend.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
40
cfg/sample/soleprint/.env
Normal file
40
cfg/sample/soleprint/.env
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# SOLEPRINT - Sample Room Configuration
|
||||||
|
# =============================================================================
|
||||||
|
# Copy this to cfg/<your-room>/soleprint/.env and customize
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# DEPLOYMENT
|
||||||
|
# =============================================================================
|
||||||
|
DEPLOYMENT_NAME=sample_spr
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# NETWORK (unique per room to allow concurrent operation)
|
||||||
|
# =============================================================================
|
||||||
|
NETWORK_NAME=sample_network
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PORTS (choose unique ports for each room)
|
||||||
|
# =============================================================================
|
||||||
|
SOLEPRINT_PORT=12020
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# GOOGLE OAUTH (optional - for auth)
|
||||||
|
# =============================================================================
|
||||||
|
GOOGLE_CLIENT_ID=
|
||||||
|
GOOGLE_CLIENT_SECRET=
|
||||||
|
GOOGLE_REDIRECT_URI=http://sample.spr.local.ar/spr/artery/google/oauth/callback
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# AUTH
|
||||||
|
# =============================================================================
|
||||||
|
AUTH_SESSION_SECRET=change-this-in-production
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# DATABASE (optional - if your app uses a database)
|
||||||
|
# =============================================================================
|
||||||
|
DB_HOST=sample_db
|
||||||
|
DB_PORT=5432
|
||||||
|
DB_NAME=sampledb
|
||||||
|
DB_USER=postgres
|
||||||
|
DB_PASSWORD=localdev123
|
||||||
36
cfg/sample/soleprint/docker-compose.yml
Normal file
36
cfg/sample/soleprint/docker-compose.yml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Soleprint Services - Docker Compose
|
||||||
|
#
|
||||||
|
# Runs soleprint hub as a single service
|
||||||
|
# Artery, atlas, station are accessed via path-based routing
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# cd gen/<room>/soleprint && docker compose up -d
|
||||||
|
|
||||||
|
name: ${DEPLOYMENT_NAME}
|
||||||
|
|
||||||
|
services:
|
||||||
|
soleprint:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: ${DEPLOYMENT_NAME}
|
||||||
|
user: "${UID:-1000}:${GID:-1000}"
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
ports:
|
||||||
|
- "${SOLEPRINT_PORT}:8000"
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
# For single-port mode, all subsystems are internal routes
|
||||||
|
- ARTERY_EXTERNAL_URL=/artery
|
||||||
|
- ATLAS_EXTERNAL_URL=/atlas
|
||||||
|
- STATION_EXTERNAL_URL=/station
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
# Use run.py for single-port bare-metal mode
|
||||||
|
command: uvicorn run:app --host 0.0.0.0 --port 8000 --reload
|
||||||
|
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
name: ${NETWORK_NAME}
|
||||||
@@ -5,12 +5,15 @@
|
|||||||
# Usage:
|
# Usage:
|
||||||
# cd gen/standalone && docker compose up -d
|
# cd gen/standalone && docker compose up -d
|
||||||
|
|
||||||
|
name: soleprint_standalone
|
||||||
|
|
||||||
services:
|
services:
|
||||||
soleprint:
|
soleprint:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
container_name: soleprint
|
container_name: soleprint
|
||||||
|
user: "${UID:-1000}:${GID:-1000}"
|
||||||
volumes:
|
volumes:
|
||||||
- .:/app
|
- .:/app
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
181
docs/architecture/05-sidebar-injection.md
Normal file
181
docs/architecture/05-sidebar-injection.md
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
# Sidebar Injection Architecture
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The soleprint sidebar is injected into managed app pages using a hybrid nginx + JavaScript approach. This allows any frontend framework (React, Next.js, static HTML) to receive the sidebar without code modifications.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Browser Request │
|
||||||
|
│ http://room.spr.local.ar/ │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Nginx │
|
||||||
|
│ │
|
||||||
|
│ 1. Routes /spr/* → soleprint:PORT (sidebar assets + API) │
|
||||||
|
│ 2. Routes /* → frontend:PORT (app pages) │
|
||||||
|
│ 3. Injects CSS+JS into HTML responses via sub_filter │
|
||||||
|
│ │
|
||||||
|
│ sub_filter '</head>' │
|
||||||
|
│ '<link rel="stylesheet" href="/spr/sidebar.css"> │
|
||||||
|
│ <script src="/spr/sidebar.js" defer></script></head>'; │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Browser Renders │
|
||||||
|
│ │
|
||||||
|
│ 1. Page loads with injected CSS (sidebar styles ready) │
|
||||||
|
│ 2. sidebar.js executes (deferred, after DOM ready) │
|
||||||
|
│ 3. JS fetches /spr/api/sidebar/config (room name, auth, etc) │
|
||||||
|
│ 4. JS creates sidebar DOM elements and injects into page │
|
||||||
|
│ 5. Sidebar appears on left side, pushes content with margin │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Design Decisions
|
||||||
|
|
||||||
|
### Why nginx sub_filter instead of modifying app code?
|
||||||
|
|
||||||
|
- **Framework agnostic**: Works with any frontend (Next.js, React, Vue, static HTML)
|
||||||
|
- **No app changes needed**: The managed app doesn't need to know about soleprint
|
||||||
|
- **Easy to disable**: Just access `room.local.ar` instead of `room.spr.local.ar`
|
||||||
|
|
||||||
|
### Why inject into `</head>` instead of `</body>`?
|
||||||
|
|
||||||
|
Next.js and other streaming SSR frameworks may not include `</body>` in the initial HTML response. The `</head>` tag is always present, so we inject both CSS and JS there using `defer` to ensure JS runs after DOM is ready.
|
||||||
|
|
||||||
|
### Why JavaScript injection instead of iframe?
|
||||||
|
|
||||||
|
- **No iframe isolation issues**: Sidebar can interact with page if needed
|
||||||
|
- **Better UX**: No double scrollbars, native feel
|
||||||
|
- **Simpler CSS**: Just push content with `margin-left`
|
||||||
|
|
||||||
|
## Nginx Configuration
|
||||||
|
|
||||||
|
### For Local Development (system nginx)
|
||||||
|
|
||||||
|
Each room needs entries in `/etc/nginx/sites-enabled/`:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
# room.spr.local.ar - app with sidebar
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name room.spr.local.ar;
|
||||||
|
|
||||||
|
# Soleprint assets and API
|
||||||
|
location /spr/ {
|
||||||
|
proxy_pass http://127.0.0.1:SOLEPRINT_PORT/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Backend API (if applicable)
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://127.0.0.1:BACKEND_PORT/api/;
|
||||||
|
# ... headers
|
||||||
|
}
|
||||||
|
|
||||||
|
# Frontend with sidebar injection
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:FRONTEND_PORT;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header Accept-Encoding ""; # Required for sub_filter
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
|
||||||
|
# Inject sidebar
|
||||||
|
sub_filter '</head>' '<link rel="stylesheet" href="/spr/sidebar.css"><script src="/spr/sidebar.js" defer></script></head>';
|
||||||
|
sub_filter_once off;
|
||||||
|
sub_filter_types text/html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# room.local.ar - app without sidebar (direct access)
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name room.local.ar;
|
||||||
|
# ... same as above but without sub_filter
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### For Docker/AWS (nginx container)
|
||||||
|
|
||||||
|
The nginx config lives in `cfg/<room>/soleprint/nginx/local.conf` and uses docker service names instead of localhost ports:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
location /spr/ {
|
||||||
|
proxy_pass http://soleprint:8000/spr/;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://frontend:3000;
|
||||||
|
# ... sub_filter same as above
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Port Allocation
|
||||||
|
|
||||||
|
Each room should use unique ports to allow concurrent operation:
|
||||||
|
|
||||||
|
| Room | Soleprint | Frontend | Backend |
|
||||||
|
|--------|-----------|----------|---------|
|
||||||
|
| amar | 12000 | 3000 | 8001 |
|
||||||
|
| dlt | 12010 | 3010 | - |
|
||||||
|
| sample | 12020 | 3020 | 8020 |
|
||||||
|
|
||||||
|
## Sidebar Assets
|
||||||
|
|
||||||
|
The sidebar consists of two files served by soleprint:
|
||||||
|
|
||||||
|
- `/sidebar.css` - Styles for the sidebar (dark theme, positioning)
|
||||||
|
- `/sidebar.js` - Self-contained JS that fetches config and renders sidebar
|
||||||
|
|
||||||
|
Source location: `soleprint/station/tools/sbwrapper/`
|
||||||
|
|
||||||
|
## Sidebar Config API
|
||||||
|
|
||||||
|
The sidebar JS fetches configuration from `/spr/api/sidebar/config`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"room": "amar",
|
||||||
|
"soleprint_base": "/spr",
|
||||||
|
"auth_enabled": true,
|
||||||
|
"tools": {
|
||||||
|
"artery": "/spr/artery",
|
||||||
|
"atlas": "/spr/atlas",
|
||||||
|
"station": "/spr/station"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"login_url": "/spr/artery/google/oauth/login",
|
||||||
|
"logout_url": "/spr/artery/google/oauth/logout"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Sidebar not appearing
|
||||||
|
|
||||||
|
1. Check if soleprint is running: `curl http://room.spr.local.ar/spr/sidebar.js`
|
||||||
|
2. Check browser console for `[Soleprint]` messages
|
||||||
|
3. Verify nginx has `Accept-Encoding ""` set (required for sub_filter)
|
||||||
|
4. Hard refresh (Ctrl+Shift+R) to clear cached HTML
|
||||||
|
|
||||||
|
### Wrong room config showing
|
||||||
|
|
||||||
|
Each room needs its own docker network (`room_network`) to isolate services. Check `NETWORK_NAME` in `.env` files and ensure containers are on correct networks.
|
||||||
|
|
||||||
|
### sub_filter not working
|
||||||
|
|
||||||
|
- Ensure `proxy_set_header Accept-Encoding ""` is set
|
||||||
|
- Check that response is `text/html` (sub_filter_types)
|
||||||
|
- Streaming SSR may not have `</body>`, use `</head>` injection instead
|
||||||
Reference in New Issue
Block a user