diff --git a/app/main.py b/app/main.py index f4e0e54..93afe4c 100644 --- a/app/main.py +++ b/app/main.py @@ -3,6 +3,7 @@ mc-webui - Flask application entry point """ import logging +import re import shlex import requests from flask import Flask, request as flask_request @@ -12,6 +13,14 @@ from app.routes.views import views_bp from app.routes.api import api_bp from app.archiver.manager import schedule_daily_archiving +# Commands that require longer timeout (in seconds) +SLOW_COMMANDS = { + 'node_discover': 15, + 'recv': 60, + 'send': 15, + 'send_msg': 15, +} + # Configure logging logging.basicConfig( level=logging.DEBUG if config.FLASK_DEBUG else logging.INFO, @@ -53,6 +62,65 @@ def create_app(): return app +# ============================================================ +# Console output helpers +# ============================================================ + +def clean_console_output(output: str, command: str) -> str: + """ + Clean meshcli console output by removing: + - Prompt lines (e.g., "MarWoj|*" or "DeviceName|*[N]") + - Echoed command line + - Leading/trailing whitespace + """ + if not output: + return output + + lines = output.split('\n') + cleaned_lines = [] + + # Pattern to match prompt: |*[optional] or |* + # Examples: "MarWoj|*", "MarWoj|*[3]", "Device Name|*" + prompt_pattern = re.compile(r'^.+\|\*(\[\d+\])?\s*$') + + # Pattern to match prompt followed by command echo + # Example: "MarWoj|* contacts" or "MarWoj|*[3] get_radio" + prompt_cmd_pattern = re.compile(r'^.+\|\*(\[\d+\])?\s+\S+') + + for line in lines: + stripped = line.rstrip() + + # Skip empty lines at start + if not cleaned_lines and not stripped: + continue + + # Skip lines that are just the prompt + if prompt_pattern.match(stripped): + continue + + # Skip lines that are prompt + echoed command + if prompt_cmd_pattern.match(stripped): + # Check if this line is just echoing the command + # Extract the part after the prompt + match = re.match(r'^.+\|\*(\[\d+\])?\s+(.+)$', stripped) + if match: + echoed_part = match.group(2).strip() + if echoed_part == command.strip(): + continue + + cleaned_lines.append(line) + + # Remove leading empty lines + while cleaned_lines and not cleaned_lines[0].strip(): + cleaned_lines.pop(0) + + # Remove trailing empty lines + while cleaned_lines and not cleaned_lines[-1].strip(): + cleaned_lines.pop() + + return '\n'.join(cleaned_lines) + + # ============================================================ # WebSocket handlers for Console # ============================================================ @@ -93,18 +161,24 @@ def handle_send_command(data): except ValueError: args = command.split() + # Determine timeout based on command + base_command = args[0] if args else '' + cmd_timeout = SLOW_COMMANDS.get(base_command, 10) + def execute_and_respond(): try: response = requests.post( config.MC_BRIDGE_URL, - json={'args': args, 'timeout': 30}, - timeout=35 + json={'args': args, 'timeout': cmd_timeout}, + timeout=cmd_timeout + 5 # HTTP timeout slightly longer ) if response.status_code == 200: result = response.json() if result.get('success'): - output = result.get('stdout', '').strip() + raw_output = result.get('stdout', '') + # Clean output: remove prompts and echoed commands + output = clean_console_output(raw_output, command) if not output: output = '(no output)' socketio.emit('command_response', {