From c376ecff30503da8eb886c442971ec4e5040f9c4 Mon Sep 17 00:00:00 2001 From: MarekWo Date: Fri, 9 Jan 2026 13:18:56 +0100 Subject: [PATCH] fix: Proxy console WebSocket through main app for HTTPS compatibility Browser blocks mixed content (HTTPS page -> HTTP WebSocket on port 5001). Solution: Route WebSocket through main Flask app which goes through existing HTTPS reverse proxy. - Add Flask-SocketIO to main mc-webui app - WebSocket handler proxies commands to bridge via HTTP - Remove port 5001 external exposure (no longer needed) - Remove duplicate title from console header Co-Authored-By: Claude Opus 4.5 --- app/main.py | 94 +++++++++++++++++++++++++++++++++++++- app/routes/views.py | 11 +---- app/static/js/console.js | 12 ++--- app/templates/console.html | 17 +------ docker-compose.yml | 2 - requirements.txt | 7 +++ 6 files changed, 109 insertions(+), 34 deletions(-) diff --git a/app/main.py b/app/main.py index cb0696f..3229f74 100644 --- a/app/main.py +++ b/app/main.py @@ -3,7 +3,9 @@ mc-webui - Flask application entry point """ import logging +import requests from flask import Flask +from flask_socketio import SocketIO, emit from app.config import config from app.routes.views import views_bp from app.routes.api import api_bp @@ -17,6 +19,9 @@ logging.basicConfig( logger = logging.getLogger(__name__) +# Initialize SocketIO globally +socketio = SocketIO() + def create_app(): """Create and configure Flask application""" @@ -30,6 +35,9 @@ def create_app(): app.register_blueprint(views_bp) app.register_blueprint(api_bp) + # Initialize SocketIO with the app + socketio.init_app(app, cors_allowed_origins="*", async_mode='gevent') + # Initialize archive scheduler if enabled if config.MC_ARCHIVE_ENABLED: schedule_daily_archiving() @@ -44,9 +52,93 @@ def create_app(): return app +# ============================================================ +# WebSocket handlers for Console +# ============================================================ + +@socketio.on('connect', namespace='/console') +def handle_console_connect(): + """Handle console WebSocket connection""" + logger.info("Console WebSocket client connected") + emit('console_status', {'message': 'Connected to mc-webui console proxy'}) + + +@socketio.on('disconnect', namespace='/console') +def handle_console_disconnect(): + """Handle console WebSocket disconnection""" + logger.info("Console WebSocket client disconnected") + + +@socketio.on('send_command', namespace='/console') +def handle_send_command(data): + """Handle command from console client - proxy to bridge via HTTP""" + command = data.get('command', '').strip() + + if not command: + emit('command_response', { + 'success': False, + 'error': 'Empty command' + }) + return + + logger.info(f"Console command received: {command}") + + # Execute command via bridge HTTP API + def execute_and_respond(): + try: + bridge_url = f"http://{config.MC_BRIDGE_HOST}:{config.MC_BRIDGE_PORT}/cli" + response = requests.post( + bridge_url, + json={'command': command}, + timeout=30 + ) + + if response.status_code == 200: + result = response.json() + output = result.get('output', '') + # Handle list output (join with newlines) + if isinstance(output, list): + output = '\n'.join(output) + emit('command_response', { + 'success': True, + 'command': command, + 'output': output + }) + else: + emit('command_response', { + 'success': False, + 'command': command, + 'error': f'Bridge returned status {response.status_code}' + }) + + except requests.exceptions.Timeout: + emit('command_response', { + 'success': False, + 'command': command, + 'error': 'Command timed out' + }) + except requests.exceptions.ConnectionError: + emit('command_response', { + 'success': False, + 'command': command, + 'error': 'Cannot connect to meshcore-bridge' + }) + except Exception as e: + logger.error(f"Console command error: {e}") + emit('command_response', { + 'success': False, + 'command': command, + 'error': str(e) + }) + + # Run in background to not block + socketio.start_background_task(execute_and_respond) + + if __name__ == '__main__': app = create_app() - app.run( + socketio.run( + app, host=config.FLASK_HOST, port=config.FLASK_PORT, debug=config.FLASK_DEBUG diff --git a/app/routes/views.py b/app/routes/views.py index d06bc72..15f9927 100644 --- a/app/routes/views.py +++ b/app/routes/views.py @@ -77,18 +77,11 @@ def console(): """ Interactive meshcli console - chat-style command interface. - Connects via WebSocket to meshcore-bridge for real-time command execution. + WebSocket connection is handled by the main Flask app and proxied to bridge. """ - # Build WebSocket URL for meshcore-bridge - # Browser connects directly to bridge on port 5001 - # Use the same hostname the user is accessing but with port 5001 - host = request.host.split(':')[0] # Get hostname without port - bridge_ws_url = f"http://{host}:5001" - return render_template( 'console.html', - device_name=config.MC_DEVICE_NAME, - bridge_ws_url=bridge_ws_url + device_name=config.MC_DEVICE_NAME ) diff --git a/app/static/js/console.js b/app/static/js/console.js index d8aab6a..8d3596a 100644 --- a/app/static/js/console.js +++ b/app/static/js/console.js @@ -20,20 +20,18 @@ document.addEventListener('DOMContentLoaded', function() { }); /** - * Connect to WebSocket server on meshcore-bridge + * Connect to WebSocket server (proxied through main Flask app) */ function connectWebSocket() { updateStatus('connecting'); - // Get WebSocket URL - bridge runs on port 5001 - // Use same hostname as current page but different port - const bridgeUrl = window.MC_CONFIG?.bridgeWsUrl || - `${window.location.protocol}//${window.location.hostname}:5001`; + // Connect to same origin - WebSocket is proxied through main Flask app + const wsUrl = window.location.origin; - console.log('Connecting to WebSocket:', bridgeUrl); + console.log('Connecting to WebSocket:', wsUrl); try { - socket = io(bridgeUrl + '/console', { + socket = io(wsUrl + '/console', { transports: ['websocket', 'polling'], reconnection: true, reconnectionAttempts: Infinity, diff --git a/app/templates/console.html b/app/templates/console.html index d3076c3..b6d54b6 100644 --- a/app/templates/console.html +++ b/app/templates/console.html @@ -180,14 +180,9 @@
- +
-
-
- meshcli Console -
- {{ device_name }} -
+ {{ device_name }}
Connecting... @@ -226,13 +221,5 @@ - - diff --git a/docker-compose.yml b/docker-compose.yml index 4e374af..80a18e6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,8 +8,6 @@ services: restart: unless-stopped devices: - "${MC_SERIAL_PORT}:${MC_SERIAL_PORT}" - ports: - - "5001:5001" # Expose for WebSocket console access volumes: - "${MC_CONFIG_DIR}:/root/.config/meshcore:rw" environment: diff --git a/requirements.txt b/requirements.txt index c8d5df7..54deac8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,3 +22,10 @@ Pillow==10.1.0 # HTTP Client for MeshCore Bridge communication requests==2.31.0 + +# WebSocket support for console +flask-socketio==5.3.6 +python-socketio==5.10.0 +python-engineio==4.8.1 +gevent==23.9.1 +gevent-websocket==0.10.1