mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-05-26 05:53:33 +00:00
Compare commits
1 Commits
fix/log-vi
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea95f37d73 |
@@ -185,13 +185,19 @@ class StateReconciliation:
|
|||||||
message=f"Reconciliation failed: {str(e)}"
|
message=f"Reconciliation failed: {str(e)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Top-level config keys that are NOT plugins
|
# Top-level config keys that are NOT plugins.
|
||||||
|
# Includes both config.json structural keys and config_secrets.json top-level
|
||||||
|
# keys (load_config() deep-merges secrets in, so secrets keys appear here too).
|
||||||
_SYSTEM_CONFIG_KEYS = frozenset({
|
_SYSTEM_CONFIG_KEYS = frozenset({
|
||||||
'web_display_autostart', 'timezone', 'location', 'display',
|
'web_display_autostart', 'timezone', 'location', 'display',
|
||||||
'plugin_system', 'vegas_scroll_speed', 'vegas_separator_width',
|
'plugin_system', 'vegas_scroll_speed', 'vegas_separator_width',
|
||||||
'vegas_target_fps', 'vegas_buffer_ahead', 'vegas_plugin_order',
|
'vegas_target_fps', 'vegas_buffer_ahead', 'vegas_plugin_order',
|
||||||
'vegas_excluded_plugins', 'vegas_scroll_enabled', 'logging',
|
'vegas_excluded_plugins', 'vegas_scroll_enabled', 'logging',
|
||||||
'dim_schedule', 'network', 'system', 'schedule',
|
'dim_schedule', 'network', 'system', 'schedule',
|
||||||
|
# Multi-display sync config (config.json structural key)
|
||||||
|
'sync',
|
||||||
|
# Secrets file top-level keys (merged in by load_config)
|
||||||
|
'github', 'youtube',
|
||||||
})
|
})
|
||||||
|
|
||||||
def _get_config_state(self) -> Dict[str, Dict[str, Any]]:
|
def _get_config_state(self) -> Dict[str, Dict[str, Any]]:
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import queue
|
import queue
|
||||||
import shutil
|
|
||||||
import sys
|
import sys
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
@@ -25,9 +24,6 @@ from src.plugin_system.state_manager import PluginStateManager
|
|||||||
from src.plugin_system.operation_history import OperationHistory
|
from src.plugin_system.operation_history import OperationHistory
|
||||||
from src.plugin_system.health_monitor import PluginHealthMonitor
|
from src.plugin_system.health_monitor import PluginHealthMonitor
|
||||||
|
|
||||||
_JOURNALCTL = shutil.which('journalctl')
|
|
||||||
_SYSTEMCTL = shutil.which('systemctl')
|
|
||||||
|
|
||||||
# Create Flask app
|
# Create Flask app
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.secret_key = os.urandom(24)
|
app.secret_key = os.urandom(24)
|
||||||
@@ -496,13 +492,12 @@ def system_status_generator():
|
|||||||
# Check if display service is running (cached to avoid per-client subprocess forks)
|
# Check if display service is running (cached to avoid per-client subprocess forks)
|
||||||
now = time.time()
|
now = time.time()
|
||||||
if (now - _ledmatrix_service_cache['timestamp']) >= _LEDMATRIX_SERVICE_CACHE_TTL:
|
if (now - _ledmatrix_service_cache['timestamp']) >= _LEDMATRIX_SERVICE_CACHE_TTL:
|
||||||
if _SYSTEMCTL:
|
|
||||||
try:
|
try:
|
||||||
result = subprocess.run([_SYSTEMCTL, 'is-active', 'ledmatrix'],
|
result = subprocess.run(['systemctl', 'is-active', 'ledmatrix'],
|
||||||
capture_output=True, text=True, timeout=2)
|
capture_output=True, text=True, timeout=2)
|
||||||
_ledmatrix_service_cache['active'] = result.stdout.strip() == 'active'
|
_ledmatrix_service_cache['active'] = result.stdout.strip() == 'active'
|
||||||
except (subprocess.SubprocessError, OSError) as e:
|
except (subprocess.SubprocessError, OSError):
|
||||||
app.logger.warning("systemctl status check failed: %s", e)
|
pass
|
||||||
_ledmatrix_service_cache['timestamp'] = now
|
_ledmatrix_service_cache['timestamp'] = now
|
||||||
service_active = _ledmatrix_service_cache['active']
|
service_active = _ledmatrix_service_cache['active']
|
||||||
|
|
||||||
@@ -594,13 +589,8 @@ def logs_generator():
|
|||||||
# Get recent logs from journalctl (simplified version)
|
# Get recent logs from journalctl (simplified version)
|
||||||
# Note: User should be in systemd-journal group to read logs without sudo
|
# Note: User should be in systemd-journal group to read logs without sudo
|
||||||
try:
|
try:
|
||||||
if not _JOURNALCTL:
|
|
||||||
yield {'timestamp': time.time(), 'logs': 'journalctl not found; cannot read logs'}
|
|
||||||
time.sleep(60)
|
|
||||||
continue
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[_JOURNALCTL, '-u', 'ledmatrix.service', '-u', 'ledmatrix-web.service',
|
['journalctl', '-u', 'ledmatrix.service', '-n', '50', '--no-pager'],
|
||||||
'-n', '50', '--no-pager', '--output=short-iso'],
|
|
||||||
capture_output=True, text=True, timeout=5
|
capture_output=True, text=True, timeout=5
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -616,7 +606,7 @@ def logs_generator():
|
|||||||
# No logs available
|
# No logs available
|
||||||
logs_data = {
|
logs_data = {
|
||||||
'timestamp': time.time(),
|
'timestamp': time.time(),
|
||||||
'logs': 'No logs available from ledmatrix or ledmatrix-web service'
|
'logs': 'No logs available from ledmatrix service'
|
||||||
}
|
}
|
||||||
yield logs_data
|
yield logs_data
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import os
|
|||||||
import re
|
import re
|
||||||
import stat
|
import stat
|
||||||
import sys
|
import sys
|
||||||
import shutil
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
@@ -26,9 +25,6 @@ from src.web_interface.validators import (
|
|||||||
)
|
)
|
||||||
from src.error_aggregator import get_error_aggregator
|
from src.error_aggregator import get_error_aggregator
|
||||||
|
|
||||||
_SUDO = shutil.which('sudo')
|
|
||||||
_JOURNALCTL = shutil.which('journalctl')
|
|
||||||
|
|
||||||
# Will be initialized when blueprint is registered
|
# Will be initialized when blueprint is registered
|
||||||
config_manager = None
|
config_manager = None
|
||||||
plugin_manager = None
|
plugin_manager = None
|
||||||
@@ -1460,41 +1456,31 @@ def execute_system_action():
|
|||||||
if mode:
|
if mode:
|
||||||
# For on-demand modes, we would need to integrate with the display controller
|
# For on-demand modes, we would need to integrate with the display controller
|
||||||
# For now, just start the display service
|
# For now, just start the display service
|
||||||
try:
|
|
||||||
result = subprocess.run(['sudo', 'systemctl', 'start', 'ledmatrix'],
|
result = subprocess.run(['sudo', 'systemctl', 'start', 'ledmatrix'],
|
||||||
capture_output=True, text=True, timeout=10)
|
capture_output=True, text=True)
|
||||||
except subprocess.TimeoutExpired as e:
|
|
||||||
logger.error("start_display (%s) timed out: %s", mode, e)
|
|
||||||
return jsonify({'status': 'error', 'message': 'Command timed out', 'returncode': -1, 'stderr': 'timeout'})
|
|
||||||
logger.info("start_display (%s) returned code %d", mode, result.returncode)
|
logger.info("start_display (%s) returned code %d", mode, result.returncode)
|
||||||
if result.returncode != 0 and result.stderr:
|
return jsonify({
|
||||||
logger.error("start_display (%s) stderr: %s", mode, result.stderr.strip())
|
|
||||||
resp = {
|
|
||||||
'status': 'success' if result.returncode == 0 else 'error',
|
'status': 'success' if result.returncode == 0 else 'error',
|
||||||
'message': 'Display started' if result.returncode == 0 else 'Failed to start display',
|
'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:
|
else:
|
||||||
result = subprocess.run(['sudo', 'systemctl', 'start', 'ledmatrix'],
|
result = subprocess.run(['sudo', 'systemctl', 'start', 'ledmatrix'],
|
||||||
capture_output=True, text=True, timeout=10)
|
capture_output=True, text=True)
|
||||||
elif action == 'stop_display':
|
elif action == 'stop_display':
|
||||||
result = subprocess.run(['sudo', 'systemctl', 'stop', 'ledmatrix'],
|
result = subprocess.run(['sudo', 'systemctl', 'stop', 'ledmatrix'],
|
||||||
capture_output=True, text=True, timeout=10)
|
capture_output=True, text=True)
|
||||||
elif action == 'enable_autostart':
|
elif action == 'enable_autostart':
|
||||||
result = subprocess.run(['sudo', 'systemctl', 'enable', 'ledmatrix'],
|
result = subprocess.run(['sudo', 'systemctl', 'enable', 'ledmatrix'],
|
||||||
capture_output=True, text=True, timeout=10)
|
capture_output=True, text=True)
|
||||||
elif action == 'disable_autostart':
|
elif action == 'disable_autostart':
|
||||||
result = subprocess.run(['sudo', 'systemctl', 'disable', 'ledmatrix'],
|
result = subprocess.run(['sudo', 'systemctl', 'disable', 'ledmatrix'],
|
||||||
capture_output=True, text=True, timeout=10)
|
capture_output=True, text=True)
|
||||||
elif action == 'reboot_system':
|
elif action == 'reboot_system':
|
||||||
result = subprocess.run(['sudo', 'reboot'],
|
result = subprocess.run(['sudo', 'reboot'],
|
||||||
capture_output=True, text=True, timeout=10)
|
capture_output=True, text=True)
|
||||||
elif action == 'shutdown_system':
|
elif action == 'shutdown_system':
|
||||||
result = subprocess.run(['sudo', 'poweroff'],
|
result = subprocess.run(['sudo', 'poweroff'],
|
||||||
capture_output=True, text=True, timeout=10)
|
capture_output=True, text=True)
|
||||||
elif action == 'git_pull':
|
elif action == 'git_pull':
|
||||||
# Use PROJECT_ROOT instead of hardcoded path
|
# Use PROJECT_ROOT instead of hardcoded path
|
||||||
project_dir = str(PROJECT_ROOT)
|
project_dir = str(PROJECT_ROOT)
|
||||||
@@ -1569,29 +1555,20 @@ def execute_system_action():
|
|||||||
})
|
})
|
||||||
elif action == 'restart_display_service':
|
elif action == 'restart_display_service':
|
||||||
result = subprocess.run(['sudo', 'systemctl', 'restart', 'ledmatrix'],
|
result = subprocess.run(['sudo', 'systemctl', 'restart', 'ledmatrix'],
|
||||||
capture_output=True, text=True, timeout=10)
|
capture_output=True, text=True)
|
||||||
elif action == 'restart_web_service':
|
elif action == 'restart_web_service':
|
||||||
# Try to restart the web service (assuming it's ledmatrix-web.service)
|
# Try to restart the web service (assuming it's ledmatrix-web.service)
|
||||||
result = subprocess.run(['sudo', 'systemctl', 'restart', 'ledmatrix-web'],
|
result = subprocess.run(['sudo', 'systemctl', 'restart', 'ledmatrix-web'],
|
||||||
capture_output=True, text=True, timeout=10)
|
capture_output=True, text=True)
|
||||||
else:
|
else:
|
||||||
return jsonify({'status': 'error', 'message': 'Unknown action'}), 400
|
return jsonify({'status': 'error', 'message': 'Unknown action'}), 400
|
||||||
|
|
||||||
logger.info("system action '%s' returncode=%d", action, result.returncode)
|
logger.info("system action '%s' returncode=%d", action, result.returncode)
|
||||||
if result.returncode != 0 and result.stderr:
|
return jsonify({
|
||||||
logger.error("system action '%s' stderr: %s", action, result.stderr.strip())
|
|
||||||
resp = {
|
|
||||||
'status': 'success' if result.returncode == 0 else 'error',
|
'status': 'success' if result.returncode == 0 else 'error',
|
||||||
'message': 'Action completed' if result.returncode == 0 else 'Action failed; check logs for details',
|
'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 subprocess.TimeoutExpired as e:
|
|
||||||
logger.error("system action '%s' timed out: %s", action, e)
|
|
||||||
return jsonify({'status': 'error', 'message': 'Command timed out', 'returncode': -1, 'stderr': 'timeout'})
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("execute_system_action failed: %s", e, exc_info=True)
|
logger.error("execute_system_action failed: %s", e, exc_info=True)
|
||||||
return jsonify({'status': 'error', 'message': 'Action failed; see logs for details'}), 500
|
return jsonify({'status': 'error', 'message': 'Action failed; see logs for details'}), 500
|
||||||
@@ -6448,14 +6425,9 @@ def list_plugin_assets():
|
|||||||
def get_logs():
|
def get_logs():
|
||||||
"""Get system logs from journalctl"""
|
"""Get system logs from journalctl"""
|
||||||
try:
|
try:
|
||||||
if not _JOURNALCTL:
|
|
||||||
return jsonify({'status': 'error', 'message': 'journalctl not found on this system'}), 503
|
|
||||||
# Get recent logs from journalctl
|
# Get recent logs from journalctl
|
||||||
_cmd = ([_SUDO, _JOURNALCTL] if _SUDO else [_JOURNALCTL]) + [
|
|
||||||
'-u', 'ledmatrix.service', '-u', 'ledmatrix-web.service',
|
|
||||||
'-n', '100', '--no-pager', '--output=short-iso']
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
_cmd,
|
['sudo', 'journalctl', '-u', 'ledmatrix.service', '-n', '100', '--no-pager'],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
timeout=5
|
timeout=5
|
||||||
@@ -6466,7 +6438,7 @@ def get_logs():
|
|||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'success',
|
'status': 'success',
|
||||||
'data': {
|
'data': {
|
||||||
'logs': logs_text if logs_text else 'No logs available from ledmatrix or ledmatrix-web service'
|
'logs': logs_text if logs_text else 'No logs available from ledmatrix service'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user