0aefadbfd8
- Fix frontend API base path (VITE_API_BASE env var, GraphPage hardcoded /api) - Add logout button to NetworkView sidebar (clears portal SSO) - Add AuditTrail component: inline change history on all entity pages - DB migration: add updated_at, last_edited_by to ports table - DB migration: add notes_last_edited_by, notes_updated_at to all entity tables - Backend: track actor on port create/update; notes editor on entity PUT - Frontend: extend types, MarkdownEditor shows last editor, port modal/list show last editor - Fix port CREATE TABLE definition to include new columns upfront - Add try/catch in handleSavePort to surface API errors in modal
434 lines
19 KiB
Bash
Executable File
434 lines
19 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Enterprise Digital Platform — Development Launcher
|
|
# Starts all services locally without Docker.
|
|
# Usage: ./start-dev.sh (start everything)
|
|
# ./start-dev.sh stop (kill all managed processes)
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PIDFILE="$ROOT/.dev-pids"
|
|
LOG_DIR="$ROOT/.dev-logs"
|
|
PORTAL_PORT=5001
|
|
DIGISERVER_PORT=5002
|
|
ITASSETS_PORT=5003
|
|
SRVMONITOR_PORT=5004
|
|
NV_BACKEND_PORT=3001
|
|
NGINX_PORT=8080
|
|
NV_DIST="$ROOT/NetworkView/frontend/dist"
|
|
|
|
# ── Colour helpers ─────────────────────────────────────────────────────────────
|
|
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'
|
|
info() { echo -e "${GREEN}[EDP]${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}[EDP]${NC} $*"; }
|
|
error() { echo -e "${RED}[EDP]${NC} $*" >&2; }
|
|
|
|
# ── Stop command ──────────────────────────────────────────────────────────────
|
|
if [[ "${1:-}" == "stop" ]]; then
|
|
if [[ -f "$PIDFILE" ]]; then
|
|
info "Stopping all services..."
|
|
while IFS= read -r pid; do
|
|
[[ -z "$pid" ]] && continue
|
|
kill "$pid" 2>/dev/null && info " killed PID $pid" || true
|
|
done < "$PIDFILE"
|
|
rm -f "$PIDFILE"
|
|
fi
|
|
# Clear named PID files used by module_manager
|
|
for _npid in "$LOG_DIR"/*.pid; do
|
|
[[ -f "$_npid" ]] && rm -f "$_npid"
|
|
done
|
|
# Stop nginx if we started it
|
|
NGINX_BIN="$(command -v nginx 2>/dev/null || echo /usr/sbin/nginx)"
|
|
_pidfile="$ROOT/.dev-logs/nginx/nginx.pid"
|
|
if [[ -f "$_pidfile" ]]; then
|
|
kill "$(cat "$_pidfile")" 2>/dev/null || true
|
|
fi
|
|
info "All services stopped."
|
|
exit 0
|
|
fi
|
|
|
|
# ── Prerequisites ─────────────────────────────────────────────────────────────
|
|
mkdir -p "$LOG_DIR"
|
|
: > "$PIDFILE"
|
|
|
|
info "Checking prerequisites..."
|
|
|
|
# nginx
|
|
NGINX_BIN="$(command -v nginx 2>/dev/null || echo /usr/sbin/nginx)"
|
|
if ! "$NGINX_BIN" -v &>/dev/null 2>&1; then
|
|
warn "nginx not found — installing via apt..."
|
|
sudo apt-get install -y -qq nginx
|
|
NGINX_BIN=/usr/sbin/nginx
|
|
fi
|
|
|
|
# python3
|
|
if ! command -v python3 &>/dev/null; then
|
|
error "python3 is required but not found. Please install it first."
|
|
exit 1
|
|
fi
|
|
|
|
# node / npm
|
|
if ! command -v node &>/dev/null; then
|
|
error "node is required but not found. Please install it first."
|
|
exit 1
|
|
fi
|
|
|
|
# ── Python venvs ──────────────────────────────────────────────────────────────
|
|
setup_venv() {
|
|
local name="$1" dir="$2"
|
|
if [[ ! -f "$dir/.venv/bin/python" ]]; then
|
|
info "Creating venv for $name..."
|
|
python3 -m venv "$dir/.venv"
|
|
"$dir/.venv/bin/pip" install -q -r "$dir/requirements.txt"
|
|
fi
|
|
}
|
|
|
|
setup_venv "portal" "$ROOT/portal"
|
|
setup_venv "digiserver" "$ROOT/digiserver-v2"
|
|
setup_venv "itassets" "$ROOT/IT_asset_management"
|
|
setup_venv "srvmonitor" "$ROOT/Server_Monitorizare_v2"
|
|
|
|
# ── Node dependencies ─────────────────────────────────────────────────────────
|
|
if [[ ! -d "$ROOT/NetworkView/backend/node_modules" ]]; then
|
|
info "Installing NetworkView backend npm packages..."
|
|
npm --prefix "$ROOT/NetworkView/backend" install --silent
|
|
fi
|
|
|
|
if [[ ! -d "$ROOT/NetworkView/frontend/node_modules" ]]; then
|
|
info "Installing NetworkView frontend npm packages..."
|
|
npm --prefix "$ROOT/NetworkView/frontend" install --silent
|
|
fi
|
|
|
|
# ── Build NetworkView frontend (only when source is newer than dist) ───────────
|
|
VITE_CFG="$ROOT/NetworkView/frontend/vite.config.ts"
|
|
if [[ ! -f "$NV_DIST/index.html" ]] \
|
|
|| find "$ROOT/NetworkView/frontend/src" -newer "$NV_DIST/index.html" -name "*.ts" -o -name "*.tsx" -o -name "*.css" 2>/dev/null | grep -q .; then
|
|
info "Building NetworkView frontend..."
|
|
VITE_BASE_PATH=/networkview/ \
|
|
npm --prefix "$ROOT/NetworkView/frontend" run build --silent
|
|
fi
|
|
|
|
# ── Generate runtime nginx config ────────────────────────────────────────────
|
|
NGINX_CONF="$ROOT/nginx/.dev-nginx.conf"
|
|
NGINX_LOGS="$ROOT/.dev-logs/nginx"
|
|
mkdir -p "$NGINX_LOGS"
|
|
|
|
info "Writing nginx config → $NGINX_CONF"
|
|
cat > "$NGINX_CONF" <<NGINXEOF
|
|
user $(id -un) $(id -gn);
|
|
pid $LOG_DIR/nginx.pid;
|
|
error_log $NGINX_LOGS/error.log warn;
|
|
|
|
events { worker_connections 1024; }
|
|
|
|
http {
|
|
include /etc/nginx/mime.types;
|
|
default_type application/octet-stream;
|
|
sendfile on;
|
|
keepalive_timeout 65;
|
|
|
|
access_log $NGINX_LOGS/access.log;
|
|
|
|
limit_req_zone \$binary_remote_addr zone=login:10m rate=10r/m;
|
|
|
|
upstream portal_upstream { server 127.0.0.1:${PORTAL_PORT}; }
|
|
upstream digiserver_upstream { server 127.0.0.1:${DIGISERVER_PORT}; }
|
|
upstream itassets_upstream { server 127.0.0.1:${ITASSETS_PORT}; }
|
|
upstream srvmonitor_upstream { server 127.0.0.1:${SRVMONITOR_PORT}; }
|
|
upstream nv_backend_upstream { server 127.0.0.1:${NV_BACKEND_PORT}; }
|
|
|
|
server {
|
|
listen ${NGINX_PORT};
|
|
server_name _;
|
|
|
|
location = /portal-verify {
|
|
internal;
|
|
proxy_pass http://portal_upstream/api/verify-token;
|
|
proxy_pass_request_body off;
|
|
proxy_set_header Content-Length "";
|
|
proxy_set_header Cookie \$http_cookie;
|
|
proxy_set_header X-Original-URI \$request_uri;
|
|
proxy_set_header X-Real-IP \$remote_addr;
|
|
}
|
|
|
|
location = /health {
|
|
proxy_pass http://portal_upstream/health;
|
|
proxy_set_header Host \$host;
|
|
}
|
|
|
|
location / {
|
|
limit_req zone=login burst=20 nodelay;
|
|
proxy_pass http://portal_upstream;
|
|
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;
|
|
proxy_read_timeout 60s;
|
|
}
|
|
|
|
# DigiServer — player API (no portal auth)
|
|
location /digiserver/api/ {
|
|
proxy_pass http://digiserver_upstream/api/;
|
|
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;
|
|
proxy_set_header X-Script-Name /digiserver;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade \$http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
client_max_body_size 256M;
|
|
proxy_connect_timeout 300s;
|
|
proxy_send_timeout 300s;
|
|
proxy_read_timeout 300s;
|
|
}
|
|
|
|
# DigiServer — media downloads (no portal auth)
|
|
location /digiserver/static/uploads/ {
|
|
proxy_pass http://digiserver_upstream/static/uploads/;
|
|
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;
|
|
proxy_set_header X-Script-Name /digiserver;
|
|
expires 60d;
|
|
add_header Cache-Control "public";
|
|
}
|
|
|
|
# DigiServer — admin UI (portal auth required)
|
|
location /digiserver/ {
|
|
auth_request /portal-verify;
|
|
auth_request_set \$auth_user_id \$upstream_http_x_auth_user_id;
|
|
auth_request_set \$auth_username \$upstream_http_x_auth_username;
|
|
auth_request_set \$auth_role \$upstream_http_x_auth_role;
|
|
|
|
proxy_pass http://digiserver_upstream/;
|
|
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;
|
|
proxy_set_header X-Script-Name /digiserver;
|
|
proxy_set_header X-Auth-User-Id \$auth_user_id;
|
|
proxy_set_header X-Auth-Username \$auth_username;
|
|
proxy_set_header X-Auth-Role \$auth_role;
|
|
proxy_read_timeout 300s;
|
|
proxy_send_timeout 300s;
|
|
proxy_connect_timeout 300s;
|
|
client_max_body_size 2048M;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade \$http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
}
|
|
|
|
# IT Asset Management (portal auth required)
|
|
location /itassets/ {
|
|
auth_request /portal-verify;
|
|
auth_request_set \$auth_user_id \$upstream_http_x_auth_user_id;
|
|
auth_request_set \$auth_username \$upstream_http_x_auth_username;
|
|
auth_request_set \$auth_role \$upstream_http_x_auth_role;
|
|
|
|
proxy_pass http://itassets_upstream/;
|
|
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;
|
|
proxy_set_header X-Script-Name /itassets;
|
|
proxy_set_header X-Auth-User-Id \$auth_user_id;
|
|
proxy_set_header X-Auth-Username \$auth_username;
|
|
proxy_set_header X-Auth-Role \$auth_role;
|
|
proxy_read_timeout 120s;
|
|
client_max_body_size 50M;
|
|
}
|
|
|
|
# Server Monitor — device API (no portal auth — Raspberry Pi clients push directly)
|
|
location /srvmonitor/api/ {
|
|
proxy_pass http://srvmonitor_upstream/api/;
|
|
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;
|
|
proxy_set_header X-Script-Name /srvmonitor;
|
|
client_max_body_size 50M;
|
|
}
|
|
|
|
# Server Monitor — web UI (portal auth required)
|
|
location /srvmonitor/ {
|
|
auth_request /portal-verify;
|
|
auth_request_set \$auth_user_id \$upstream_http_x_auth_user_id;
|
|
auth_request_set \$auth_username \$upstream_http_x_auth_username;
|
|
auth_request_set \$auth_role \$upstream_http_x_auth_role;
|
|
|
|
proxy_pass http://srvmonitor_upstream/;
|
|
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;
|
|
proxy_set_header X-Script-Name /srvmonitor;
|
|
proxy_set_header X-Auth-User-Id \$auth_user_id;
|
|
proxy_set_header X-Auth-Username \$auth_username;
|
|
proxy_set_header X-Auth-Role \$auth_role;
|
|
proxy_read_timeout 120s;
|
|
client_max_body_size 50M;
|
|
}
|
|
|
|
# NetworkView API (portal auth required)
|
|
location /networkview/api/ {
|
|
auth_request /portal-verify;
|
|
auth_request_set \$auth_user_id \$upstream_http_x_auth_user_id;
|
|
auth_request_set \$auth_username \$upstream_http_x_auth_username;
|
|
|
|
proxy_pass http://nv_backend_upstream/api/;
|
|
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;
|
|
proxy_set_header X-User-Id \$auth_user_id;
|
|
proxy_set_header X-Username \$auth_username;
|
|
}
|
|
|
|
# NetworkView SPA — served from pre-built dist/
|
|
location /networkview/ {
|
|
auth_request /portal-verify;
|
|
auth_request_set \$auth_user_id \$upstream_http_x_auth_user_id;
|
|
auth_request_set \$auth_username \$upstream_http_x_auth_username;
|
|
|
|
alias ${NV_DIST}/;
|
|
try_files \$uri \$uri/ @nv_index;
|
|
}
|
|
|
|
location @nv_index {
|
|
auth_request /portal-verify;
|
|
root ${NV_DIST};
|
|
try_files /index.html =404;
|
|
}
|
|
|
|
error_page 401 = @login_redirect;
|
|
location @login_redirect {
|
|
return 302 /login?next=\$request_uri;
|
|
}
|
|
|
|
error_page 403 = @access_denied;
|
|
location @access_denied {
|
|
return 302 /access-denied;
|
|
}
|
|
}
|
|
}
|
|
NGINXEOF
|
|
|
|
"$NGINX_BIN" -t -c "$NGINX_CONF"
|
|
|
|
# ── Start services ────────────────────────────────────────────────────────────
|
|
start_bg() {
|
|
local name="$1"; shift
|
|
info "Starting $name..."
|
|
"$@" >> "$LOG_DIR/${name}.log" 2>&1 &
|
|
local pid=$!
|
|
echo $pid >> "$PIDFILE"
|
|
echo $pid > "$LOG_DIR/${name}.pid" # named PID for module_manager
|
|
info " $name PID=$pid — logs: $LOG_DIR/${name}.log"
|
|
}
|
|
|
|
# Check .dev-module-state.json — returns 0 (true) if enabled, 1 (false) if disabled
|
|
module_enabled() {
|
|
local app_id="$1"
|
|
local state_file="$ROOT/.dev-module-state.json"
|
|
[[ ! -f "$state_file" ]] && return 0
|
|
python3 -c "
|
|
import json, sys
|
|
try:
|
|
d = json.load(open('$state_file'))
|
|
sys.exit(0 if d.get('$app_id', True) else 1)
|
|
except Exception:
|
|
sys.exit(0)
|
|
"
|
|
}
|
|
|
|
# Portal (always starts — it manages the other modules)
|
|
start_bg "portal" \
|
|
env FLASK_ENV=development \
|
|
DATA_DIR="$ROOT/portal/data" \
|
|
PORT=$PORTAL_PORT \
|
|
"$ROOT/portal/.venv/bin/python" "$ROOT/portal/run.py"
|
|
|
|
# DigiServer
|
|
if module_enabled "digiserver"; then
|
|
mkdir -p "$ROOT/digiserver-v2/instance" "$ROOT/digiserver-v2/app/static/uploads"
|
|
start_bg "digiserver" \
|
|
env FLASK_ENV=development \
|
|
ADMIN_USERNAME=admin \
|
|
ADMIN_PASSWORD=admin123 \
|
|
PORTAL_JWT_SECRET=change-this-jwt-secret-in-production \
|
|
PORTAL_LOGOUT_URL="http://localhost:${NGINX_PORT}/logout" \
|
|
"$ROOT/digiserver-v2/.venv/bin/gunicorn" \
|
|
--bind "127.0.0.1:$DIGISERVER_PORT" \
|
|
--workers 2 \
|
|
--timeout 120 \
|
|
--chdir "$ROOT/digiserver-v2" \
|
|
wsgi:application
|
|
else
|
|
warn "digiserver is disabled — skipping"
|
|
fi
|
|
|
|
# IT Asset Management
|
|
if module_enabled "itassets"; then
|
|
mkdir -p "$ROOT/IT_asset_management/data"
|
|
start_bg "itassets" \
|
|
env FLASK_ENV=development \
|
|
PORT=$ITASSETS_PORT \
|
|
SQLALCHEMY_DATABASE_URI="sqlite:///$ROOT/IT_asset_management/data/itassets.db" \
|
|
PORTAL_JWT_SECRET=change-this-jwt-secret-in-production \
|
|
PORTAL_LOGIN_URL="http://localhost:${NGINX_PORT}/login" \
|
|
PORTAL_LOGOUT_URL="http://localhost:${NGINX_PORT}/logout" \
|
|
FLASK_APP=run.py \
|
|
"$ROOT/IT_asset_management/.venv/bin/python" "$ROOT/IT_asset_management/run.py"
|
|
else
|
|
warn "itassets is disabled — skipping"
|
|
fi
|
|
|
|
# NetworkView backend
|
|
if module_enabled "srvmonitor"; then
|
|
mkdir -p "$ROOT/Server_Monitorizare_v2/data"
|
|
start_bg "srvmonitor" \
|
|
env PORT=$SRVMONITOR_PORT \
|
|
FLASK_ENV=development \
|
|
PORTAL_LOGIN_URL="http://localhost:${NGINX_PORT}/login" \
|
|
"$ROOT/Server_Monitorizare_v2/.venv/bin/python" "$ROOT/Server_Monitorizare_v2/main.py"
|
|
else
|
|
warn "srvmonitor is disabled — skipping"
|
|
fi
|
|
|
|
if module_enabled "networkview"; then
|
|
mkdir -p "$ROOT/NetworkView/data"
|
|
start_bg "networkview-backend" \
|
|
env PORT=$NV_BACKEND_PORT \
|
|
PORTAL_JWT_SECRET=change-this-jwt-secret-in-production \
|
|
NODE_ENV=development \
|
|
DB_PATH="$ROOT/NetworkView/data/networkview.db" \
|
|
node "$ROOT/NetworkView/backend/src/index.js"
|
|
else
|
|
warn "networkview is disabled — skipping"
|
|
fi
|
|
|
|
# ── Start nginx ────────────────────────────────────────────────────────────────
|
|
info "Starting nginx on port $NGINX_PORT..."
|
|
"$NGINX_BIN" -c "$NGINX_CONF"
|
|
|
|
# ── Summary ────────────────────────────────────────────────────────────────────
|
|
echo ""
|
|
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo -e "${GREEN} Enterprise Digital Platform is running!${NC}"
|
|
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo ""
|
|
echo -e " Portal (umbrella): http://localhost:${NGINX_PORT}/"
|
|
echo -e " DigiServer: http://localhost:${NGINX_PORT}/digiserver/"
|
|
echo -e " IT Assets: http://localhost:${NGINX_PORT}/itassets/"
|
|
echo -e " Server Monitor: http://localhost:${NGINX_PORT}/srvmonitor/"
|
|
echo -e " NetworkView: http://localhost:${NGINX_PORT}/networkview/"
|
|
echo ""
|
|
echo -e " Login: admin / admin123"
|
|
echo ""
|
|
echo -e " Logs: $LOG_DIR/"
|
|
echo -e " Stop: ./start-dev.sh stop"
|
|
echo ""
|