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 <noreply@anthropic.com>
This commit is contained in:
MarekWo
2026-01-09 13:18:56 +01:00
parent a5f7fd59e6
commit c376ecff30
6 changed files with 109 additions and 34 deletions

View File

@@ -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

View File

@@ -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
)

View File

@@ -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,

View File

@@ -180,14 +180,9 @@
</head>
<body>
<div class="console-container">
<!-- Header -->
<!-- Header with status indicator -->
<div class="console-header d-flex justify-content-between align-items-center">
<div>
<h6 class="mb-0 text-white">
<i class="bi bi-terminal"></i> meshcli Console
</h6>
<small class="text-muted">{{ device_name }}</small>
</div>
<small class="text-muted">{{ device_name }}</small>
<div class="d-flex align-items-center gap-2">
<span class="status-dot" id="statusDot"></span>
<small class="text-muted" id="statusText">Connecting...</small>
@@ -226,13 +221,5 @@
<script src="{{ url_for('static', filename='vendor/bootstrap/js/bootstrap.bundle.min.js') }}"></script>
<!-- Console JS -->
<script src="{{ url_for('static', filename='js/console.js') }}"></script>
<script>
// Pass configuration from Flask to JavaScript
window.MC_CONFIG = {
bridgeWsUrl: "{{ bridge_ws_url }}",
deviceName: "{{ device_name }}"
};
</script>
</body>
</html>

View File

@@ -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:

View File

@@ -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