diff --git a/web_interface/app.py b/web_interface/app.py index 97287119..bfb29bf7 100644 --- a/web_interface/app.py +++ b/web_interface/app.py @@ -3,6 +3,7 @@ import json import logging import os import queue +import shutil import sys import subprocess import threading @@ -24,6 +25,9 @@ from src.plugin_system.state_manager import PluginStateManager from src.plugin_system.operation_history import OperationHistory from src.plugin_system.health_monitor import PluginHealthMonitor +_JOURNALCTL = shutil.which('journalctl') +_SYSTEMCTL = shutil.which('systemctl') + # Create Flask app app = Flask(__name__) app.secret_key = os.urandom(24) @@ -492,12 +496,13 @@ def system_status_generator(): # Check if display service is running (cached to avoid per-client subprocess forks) now = time.time() if (now - _ledmatrix_service_cache['timestamp']) >= _LEDMATRIX_SERVICE_CACHE_TTL: - try: - result = subprocess.run(['systemctl', 'is-active', 'ledmatrix'], - capture_output=True, text=True, timeout=2) - _ledmatrix_service_cache['active'] = result.stdout.strip() == 'active' - except (subprocess.SubprocessError, OSError): - pass + if _SYSTEMCTL: + try: + result = subprocess.run([_SYSTEMCTL, 'is-active', 'ledmatrix'], + capture_output=True, text=True, timeout=2) + _ledmatrix_service_cache['active'] = result.stdout.strip() == 'active' + except (subprocess.SubprocessError, OSError): + pass _ledmatrix_service_cache['timestamp'] = now service_active = _ledmatrix_service_cache['active'] @@ -589,8 +594,13 @@ def logs_generator(): # Get recent logs from journalctl (simplified version) # Note: User should be in systemd-journal group to read logs without sudo try: + if not _JOURNALCTL: + yield {'timestamp': time.time(), 'logs': 'journalctl not found; cannot read logs'} + time.sleep(60) + continue result = subprocess.run( - ['journalctl', '-u', 'ledmatrix.service', '-n', '50', '--no-pager'], + [_JOURNALCTL, '-u', 'ledmatrix.service', '-u', 'ledmatrix-web.service', + '-n', '50', '--no-pager', '--output=short-iso'], capture_output=True, text=True, timeout=5 ) @@ -606,7 +616,7 @@ def logs_generator(): # No logs available logs_data = { 'timestamp': time.time(), - 'logs': 'No logs available from ledmatrix service' + 'logs': 'No logs available from ledmatrix or ledmatrix-web service' } yield logs_data else: diff --git a/web_interface/blueprints/api_v3.py b/web_interface/blueprints/api_v3.py index d434afc2..e2f5d1fc 100644 --- a/web_interface/blueprints/api_v3.py +++ b/web_interface/blueprints/api_v3.py @@ -4,6 +4,7 @@ import os import re import stat import sys +import shutil import subprocess import tempfile import time @@ -25,6 +26,9 @@ from src.web_interface.validators import ( ) from src.error_aggregator import get_error_aggregator +_SUDO = shutil.which('sudo') +_JOURNALCTL = shutil.which('journalctl') + # Will be initialized when blueprint is registered config_manager = None plugin_manager = None @@ -1459,10 +1463,16 @@ def execute_system_action(): result = subprocess.run(['sudo', 'systemctl', 'start', 'ledmatrix'], capture_output=True, text=True) logger.info("start_display (%s) returned code %d", mode, result.returncode) - return jsonify({ + if result.returncode != 0 and result.stderr: + logger.error("start_display (%s) stderr: %s", mode, result.stderr.strip()) + resp = { 'status': 'success' if result.returncode == 0 else 'error', 'message': 'Display started' if result.returncode == 0 else 'Failed to start display', - }) + } + if result.returncode != 0: + resp['returncode'] = result.returncode + resp['stderr'] = result.stderr.strip() + return jsonify(resp) else: result = subprocess.run(['sudo', 'systemctl', 'start', 'ledmatrix'], capture_output=True, text=True) @@ -1564,10 +1574,16 @@ def execute_system_action(): return jsonify({'status': 'error', 'message': 'Unknown action'}), 400 logger.info("system action '%s' returncode=%d", action, result.returncode) - return jsonify({ + if result.returncode != 0 and result.stderr: + logger.error("system action '%s' stderr: %s", action, result.stderr.strip()) + resp = { 'status': 'success' if result.returncode == 0 else 'error', 'message': 'Action completed' if result.returncode == 0 else 'Action failed; check logs for details', - }) + } + if result.returncode != 0: + resp['returncode'] = result.returncode + resp['stderr'] = result.stderr.strip() + return jsonify(resp) except Exception as e: logger.error("execute_system_action failed: %s", e, exc_info=True) @@ -6425,9 +6441,12 @@ def list_plugin_assets(): def get_logs(): """Get system logs from journalctl""" try: + if not _SUDO or not _JOURNALCTL: + return jsonify({'status': 'error', 'message': 'sudo or journalctl not found on this system'}), 503 # Get recent logs from journalctl result = subprocess.run( - ['sudo', 'journalctl', '-u', 'ledmatrix.service', '-n', '100', '--no-pager'], + [_SUDO, _JOURNALCTL, '-u', 'ledmatrix.service', '-u', 'ledmatrix-web.service', + '-n', '100', '--no-pager', '--output=short-iso'], capture_output=True, text=True, timeout=5 @@ -6438,7 +6457,7 @@ def get_logs(): return jsonify({ 'status': 'success', 'data': { - 'logs': logs_text if logs_text else 'No logs available from ledmatrix service' + 'logs': logs_text if logs_text else 'No logs available from ledmatrix or ledmatrix-web service' } }) else: