diff --git a/.env.example b/.env.example index 0fc4cd1..78b1eaa 100644 --- a/.env.example +++ b/.env.example @@ -1,12 +1,93 @@ # SLS Management UI Environment Variables -# Base URL for the API server -# This will be used by the React app to make API calls -APP_URL=http://localhost:8080 - -# Ports -SRT_PLAYER_PORT=4000 -SRT_SENDER_PORT=4001 -SLS_STATS_PORT=8080 -SRTLA_PORT=5000 +# ============================================================================ +# Backend API Configuration +# ============================================================================ + +# Base URL for the API server (Optional) +# Leave empty for automatic detection based on access method: +# - Direct IP access: Uses IP + SLS_STATS_PORT (e.g., http://192.168.1.100:8080) +# - FQDN access: Uses current domain (e.g., https://srt.example.com) +# Set explicitly only if you need to override automatic detection +# Examples: +# APP_URL=http://192.168.1.100:8080 # Direct IP access +# APP_URL=https://api.example.com # Custom API domain +# APP_URL= # Auto-detection (recommended) +APP_URL= + +# ============================================================================ +# Service Ports Configuration +# ============================================================================ + +# SRT Live Server ports +SLS_STATS_PORT=8080 # HTTP API and statistics (required) +SRT_PLAYER_PORT=4000 # SRT player/receiver port (UDP) +SRT_SENDER_PORT=4001 # SRT publisher/sender port (UDP) + +# SRTLA (SRT Link Aggregation) port (optional) +# Leave empty if not using SRTLA +SRTLA_PORT=5000 # SRTLA aggregation port (UDP) + +# Frontend UI port (for docker-compose) +SLS_MGNT_PORT=3000 # Management UI HTTP port + +# ============================================================================ +# Nginx Reverse Proxy Configuration Example +# ============================================================================ +# +# For HTTPS domain access (e.g., https://srt.example.com), configure nginx: +# +# server { +# listen 443 ssl http2; +# server_name srt.example.com; +# +# # SSL certificates +# ssl_certificate /path/to/ssl/cert.pem; +# ssl_certificate_key /path/to/ssl/key.pem; +# +# # Frontend UI (Management Interface) +# location / { +# proxy_pass http://localhost:3000; +# 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 endpoints +# location /api/ { +# proxy_pass http://localhost:8080/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; +# } +# +# # Statistics endpoints +# location /stats/ { +# proxy_pass http://localhost:8080/stats/; +# 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; +# } +# } +# +# Note: SRT/SRTLA UDP ports (4000, 4001, 5000) cannot be proxied through HTTP. +# These ports must be directly accessible for SRT streaming protocols. +# +# ============================================================================ +# Port Summary for Firewall Configuration +# ============================================================================ +# +# TCP Ports (HTTP/HTTPS): +# 3000 - Management UI (or 443 if using nginx SSL) +# 8080 - SLS API & Statistics +# +# UDP Ports (SRT Streaming): +# 4000 - SRT Player/Receiver +# 4001 - SRT Publisher/Sender +# 5000 - SRTLA (optional) +# +# ============================================================================ diff --git a/html/run.js b/html/run.js index 9119ad6..5ee37dc 100755 --- a/html/run.js +++ b/html/run.js @@ -52,12 +52,12 @@ files.forEach(filepath => { let newContent = content .toString() - .replaceAll('{{BASE_URL}}', process.env.REACT_APP_BASE_URL) + .replaceAll('{{BASE_URL}}', process.env.REACT_APP_BASE_URL || '') // Replace new port placeholders - .replaceAll('{{SRT_PLAYER_PORT}}', process.env.REACT_APP_SRT_PLAYER_PORT) - .replaceAll('{{SRT_SENDER_PORT}}', process.env.REACT_APP_SRT_SENDER_PORT) - .replaceAll('{{SLS_STATS_PORT}}', process.env.REACT_APP_SLS_STATS_PORT) - .replaceAll('{{SRTLA_PORT}}', process.env.REACT_APP_SRTLA_PORT); + .replaceAll('{{SRT_PLAYER_PORT}}', process.env.REACT_APP_SRT_PLAYER_PORT || '') + .replaceAll('{{SRT_SENDER_PORT}}', process.env.REACT_APP_SRT_SENDER_PORT || '') + .replaceAll('{{SLS_STATS_PORT}}', process.env.REACT_APP_SLS_STATS_PORT || '') + .replaceAll('{{SRTLA_PORT}}', process.env.REACT_APP_SRTLA_PORT || ''); fs.writeFileSync(filepath, newContent); }); diff --git a/html/src/config.ts b/html/src/config.ts index 3a5de5a..0128008 100644 --- a/html/src/config.ts +++ b/html/src/config.ts @@ -1,23 +1,86 @@ -// Configuration with runtime replacement support +// Configuration with runtime replacement support and dynamic backend detection + +// Environment variables mapping for compile-time access +const ENV_VARS = { + NODE_ENV: process.env.NODE_ENV || 'production', + REACT_APP_BASE_URL: process.env.REACT_APP_BASE_URL || '{{BASE_URL}}', + REACT_APP_SRT_PLAYER_PORT: process.env.REACT_APP_SRT_PLAYER_PORT || '{{SRT_PLAYER_PORT}}', + REACT_APP_SRT_SENDER_PORT: process.env.REACT_APP_SRT_SENDER_PORT || '{{SRT_SENDER_PORT}}', + REACT_APP_SLS_STATS_PORT: process.env.REACT_APP_SLS_STATS_PORT || '{{SLS_STATS_PORT}}', + REACT_APP_SRTLA_PORT: process.env.REACT_APP_SRTLA_PORT || '{{SRTLA_PORT}}' +}; + +// Safely access environment variables +const getEnvVar = (key: keyof typeof ENV_VARS, fallback: string = '') => { + return ENV_VARS[key] || fallback; +}; const config = (() => { - // In development: Use environment variable if available - // In production: Use placeholder that will be replaced by run.js - const API_ENDPOINT = process.env.REACT_APP_BASE_URL || '{{BASE_URL}}'; + // Helper function to detect if current access is via IP or FQDN + const getCurrentHostInfo = () => { + const hostname = window.location.hostname; + const port = window.location.port; + const protocol = window.location.protocol; + + // Check if hostname is an IP address (IPv4) + const isIP = /^(\d{1,3}\.){3}\d{1,3}$/.test(hostname); + + return { + hostname, + port, + protocol, + isIP, + isLocalhost: hostname === 'localhost' || hostname === '127.0.0.1' + }; + }; + + // Get dynamic API endpoint based on access method + const getDynamicApiEndpoint = () => { + const hostInfo = getCurrentHostInfo(); + + // In development, use environment variable if available + if (getEnvVar('NODE_ENV') === 'development') { + const devUrl = getEnvVar('REACT_APP_BASE_URL', 'http://localhost:8080'); + return devUrl; + } + + // Check for explicitly configured base URL first + const configuredUrl = getEnvVar('REACT_APP_BASE_URL'); + if (configuredUrl && !configuredUrl.includes('{{')) { + return configuredUrl; + } + + // Dynamic endpoint generation based on access method + let finalUrl; + if (hostInfo.isIP || hostInfo.isLocalhost) { + // Direct IP/localhost access: use same host with stats port + const statsPort = getEnvVar('REACT_APP_SLS_STATS_PORT') || '8080'; + const resolvedStatsPort = statsPort.includes('{{') ? '8080' : statsPort; + finalUrl = `${hostInfo.protocol}//${hostInfo.hostname}:${resolvedStatsPort}`; + } else { + // FQDN access: assume nginx proxy setup with direct proxy + finalUrl = `${hostInfo.protocol}//${hostInfo.hostname}${hostInfo.port ? `:${hostInfo.port}` : ''}`; + } + + return finalUrl; + }; // SRT/SRTLA ports configuration - const SRT_PLAYER_PORT = process.env.REACT_APP_SRT_PLAYER_PORT || '{{SRT_PLAYER_PORT}}'; - const SRT_SENDER_PORT = process.env.REACT_APP_SRT_SENDER_PORT || '{{SRT_SENDER_PORT}}'; - const SLS_STATS_PORT = process.env.REACT_APP_SLS_STATS_PORT || '{{SLS_STATS_PORT}}'; + const SRT_PLAYER_PORT = getEnvVar('REACT_APP_SRT_PLAYER_PORT'); + const SRT_SENDER_PORT = getEnvVar('REACT_APP_SRT_SENDER_PORT'); + const SLS_STATS_PORT = getEnvVar('REACT_APP_SLS_STATS_PORT'); // SRTLA_PORT is optional - will be empty if not configured - const SRTLA_PORT = process.env.REACT_APP_SRTLA_PORT || '{{SRTLA_PORT}}'; + const SRTLA_PORT = getEnvVar('REACT_APP_SRTLA_PORT'); return { - apiEndpoint: API_ENDPOINT, + apiEndpoint: getDynamicApiEndpoint(), srtPlayerPort: SRT_PLAYER_PORT, srtSenderPort: SRT_SENDER_PORT, slsStatsPort: SLS_STATS_PORT, - srtlaPort: SRTLA_PORT + srtlaPort: SRTLA_PORT, + // Add helper methods for debugging + getHostInfo: getCurrentHostInfo, + getDynamicEndpoint: getDynamicApiEndpoint }; })(); diff --git a/html/src/react-app-env.d.ts b/html/src/react-app-env.d.ts new file mode 100644 index 0000000..4d11a3b --- /dev/null +++ b/html/src/react-app-env.d.ts @@ -0,0 +1,12 @@ +/// + +declare namespace NodeJS { + interface ProcessEnv { + NODE_ENV: 'development' | 'production' | 'test'; + REACT_APP_BASE_URL?: string; + REACT_APP_SRT_PLAYER_PORT?: string; + REACT_APP_SRT_SENDER_PORT?: string; + REACT_APP_SLS_STATS_PORT?: string; + REACT_APP_SRTLA_PORT?: string; + } +} diff --git a/html/src/utils/url-generator.ts b/html/src/utils/url-generator.ts index 0b9cf3a..6e566b8 100644 --- a/html/src/utils/url-generator.ts +++ b/html/src/utils/url-generator.ts @@ -11,6 +11,17 @@ const getServerHostname = (): string => { } }; +// Check if we're accessing via FQDN (through nginx proxy) +const isAccessingViaFqdn = (): boolean => { + const hostname = window.location.hostname; + // If hostname is an IP address or localhost, we're accessing directly + return !( + hostname === 'localhost' || + hostname === '127.0.0.1' || + /^(\d{1,3}\.){3}\d{1,3}$/.test(hostname) + ); +}; + // Check if a port is configured (not empty and not a placeholder) const isPortConfigured = (port: string): boolean => { return !!(port && port !== '' && !port.includes('{{')); @@ -40,6 +51,14 @@ export const generateSrtlaPublisherUrl = (publisherId: string): string => { // Generate Stats URL export const generateStatsUrl = (playerId: string): string => { const hostname = getServerHostname(); + + // If accessing via FQDN (nginx proxy), use the standard HTTP port without explicit port number + if (isAccessingViaFqdn()) { + const protocol = window.location.protocol; // Use same protocol as current page + return `${protocol}//${hostname}/stats/${playerId}?legacy=1`; + } + + // For direct IP access, include the port const port = config.slsStatsPort; return `http://${hostname}:${port}/stats/${playerId}?legacy=1`; };