mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-03-28 17:42:45 +01:00
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:
94
app/main.py
94
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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user