mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-28 03:23:01 +00:00
Compare commits
6 Commits
76c5bf5781
...
28a374485f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28a374485f | ||
|
|
fa92bfbdd8 | ||
|
|
f3e7c639ba | ||
|
|
f718305886 | ||
|
|
f0dc094cd6 | ||
|
|
178dfb0c2a |
@@ -329,7 +329,7 @@ class Baseball(SportsCore):
|
|||||||
return
|
return
|
||||||
|
|
||||||
series_summary = game.get("series_summary", "")
|
series_summary = game.get("series_summary", "")
|
||||||
font = ImageFont.truetype("assets/fonts/4x6-font.ttf", 6)
|
font = self.fonts.get('detail', ImageFont.load_default())
|
||||||
bbox = draw_overlay.textbbox((0, 0), series_summary, font=self.fonts['time'])
|
bbox = draw_overlay.textbbox((0, 0), series_summary, font=self.fonts['time'])
|
||||||
height = bbox[3] - bbox[1]
|
height = bbox[3] - bbox[1]
|
||||||
shots_y = (self.display_height - height) // 2
|
shots_y = (self.display_height - height) // 2
|
||||||
|
|||||||
@@ -201,14 +201,7 @@ class BasketballLive(Basketball, SportsLive):
|
|||||||
|
|
||||||
# Draw records or rankings if enabled
|
# Draw records or rankings if enabled
|
||||||
if self.show_records or self.show_ranking:
|
if self.show_records or self.show_ranking:
|
||||||
try:
|
record_font = self.fonts.get('detail', ImageFont.load_default())
|
||||||
record_font = ImageFont.truetype("assets/fonts/4x6-font.ttf", 6)
|
|
||||||
self.logger.debug(f"Loaded 6px record font successfully")
|
|
||||||
except IOError:
|
|
||||||
record_font = ImageFont.load_default()
|
|
||||||
self.logger.warning(
|
|
||||||
f"Failed to load 6px font, using default font (size: {record_font.size})"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get team abbreviations
|
# Get team abbreviations
|
||||||
away_abbr = game.get("away_abbr", "")
|
away_abbr = game.get("away_abbr", "")
|
||||||
|
|||||||
@@ -308,13 +308,8 @@ class FootballLive(Football, SportsLive):
|
|||||||
|
|
||||||
# Draw records or rankings if enabled
|
# Draw records or rankings if enabled
|
||||||
if self.show_records or self.show_ranking:
|
if self.show_records or self.show_ranking:
|
||||||
try:
|
record_font = self.fonts.get('detail', ImageFont.load_default())
|
||||||
record_font = ImageFont.truetype("assets/fonts/4x6-font.ttf", 6)
|
|
||||||
self.logger.debug(f"Loaded 6px record font successfully")
|
|
||||||
except IOError:
|
|
||||||
record_font = ImageFont.load_default()
|
|
||||||
self.logger.warning(f"Failed to load 6px font, using default font (size: {record_font.size})")
|
|
||||||
|
|
||||||
# Get team abbreviations
|
# Get team abbreviations
|
||||||
away_abbr = game.get('away_abbr', '')
|
away_abbr = game.get('away_abbr', '')
|
||||||
home_abbr = game.get('home_abbr', '')
|
home_abbr = game.get('home_abbr', '')
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ class HockeyLive(Hockey, SportsLive):
|
|||||||
|
|
||||||
# Shots on Goal
|
# Shots on Goal
|
||||||
if self.show_shots_on_goal:
|
if self.show_shots_on_goal:
|
||||||
shots_font = ImageFont.truetype("assets/fonts/4x6-font.ttf", 6)
|
shots_font = self.fonts.get('detail', ImageFont.load_default())
|
||||||
home_shots = str(game.get("home_shots", "0"))
|
home_shots = str(game.get("home_shots", "0"))
|
||||||
away_shots = str(game.get("away_shots", "0"))
|
away_shots = str(game.get("away_shots", "0"))
|
||||||
shots_text = f"{away_shots} SHOTS {home_shots}"
|
shots_text = f"{away_shots} SHOTS {home_shots}"
|
||||||
@@ -276,14 +276,7 @@ class HockeyLive(Hockey, SportsLive):
|
|||||||
|
|
||||||
# Draw records or rankings if enabled
|
# Draw records or rankings if enabled
|
||||||
if self.show_records or self.show_ranking:
|
if self.show_records or self.show_ranking:
|
||||||
try:
|
record_font = self.fonts.get('detail', ImageFont.load_default())
|
||||||
record_font = ImageFont.truetype("assets/fonts/4x6-font.ttf", 6)
|
|
||||||
self.logger.debug(f"Loaded 6px record font successfully")
|
|
||||||
except IOError:
|
|
||||||
record_font = ImageFont.load_default()
|
|
||||||
self.logger.warning(
|
|
||||||
f"Failed to load 6px font, using default font (size: {record_font.size})"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get team abbreviations
|
# Get team abbreviations
|
||||||
away_abbr = game.get("away_abbr", "")
|
away_abbr = game.get("away_abbr", "")
|
||||||
|
|||||||
@@ -863,13 +863,8 @@ class SportsUpcoming(SportsCore):
|
|||||||
|
|
||||||
# Draw records or rankings if enabled
|
# Draw records or rankings if enabled
|
||||||
if self.show_records or self.show_ranking:
|
if self.show_records or self.show_ranking:
|
||||||
try:
|
record_font = self.fonts.get('detail', ImageFont.load_default())
|
||||||
record_font = ImageFont.truetype("assets/fonts/4x6-font.ttf", 6)
|
|
||||||
self.logger.debug(f"Loaded 6px record font successfully")
|
|
||||||
except IOError:
|
|
||||||
record_font = ImageFont.load_default()
|
|
||||||
self.logger.warning(f"Failed to load 6px font, using default font (size: {record_font.size})")
|
|
||||||
|
|
||||||
# Get team abbreviations
|
# Get team abbreviations
|
||||||
away_abbr = game.get('away_abbr', '')
|
away_abbr = game.get('away_abbr', '')
|
||||||
home_abbr = game.get('home_abbr', '')
|
home_abbr = game.get('home_abbr', '')
|
||||||
@@ -1172,13 +1167,8 @@ class SportsRecent(SportsCore):
|
|||||||
|
|
||||||
# Draw records or rankings if enabled
|
# Draw records or rankings if enabled
|
||||||
if self.show_records or self.show_ranking:
|
if self.show_records or self.show_ranking:
|
||||||
try:
|
record_font = self.fonts.get('detail', ImageFont.load_default())
|
||||||
record_font = ImageFont.truetype("assets/fonts/4x6-font.ttf", 6)
|
|
||||||
self.logger.debug(f"Loaded 6px record font successfully")
|
|
||||||
except IOError:
|
|
||||||
record_font = ImageFont.load_default()
|
|
||||||
self.logger.warning(f"Failed to load 6px font, using default font (size: {record_font.size})")
|
|
||||||
|
|
||||||
# Get team abbreviations
|
# Get team abbreviations
|
||||||
away_abbr = game.get('away_abbr', '')
|
away_abbr = game.get('away_abbr', '')
|
||||||
home_abbr = game.get('home_abbr', '')
|
home_abbr = game.get('home_abbr', '')
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ class LayoutManager:
|
|||||||
# Format the text
|
# Format the text
|
||||||
try:
|
try:
|
||||||
text = format_str.format(value=value)
|
text = format_str.format(value=value)
|
||||||
except:
|
except (ValueError, TypeError, KeyError, IndexError):
|
||||||
text = str(value)
|
text = str(value)
|
||||||
|
|
||||||
self.display_manager.draw_text(text, x, y, color)
|
self.display_manager.draw_text(text, x, y, color)
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ class LogoDownloader:
|
|||||||
logger.error(f"Downloaded file for {team_abbreviation} is not a valid image or conversion failed: {e}")
|
logger.error(f"Downloaded file for {team_abbreviation} is not a valid image or conversion failed: {e}")
|
||||||
try:
|
try:
|
||||||
os.remove(filepath) # Remove invalid file
|
os.remove(filepath) # Remove invalid file
|
||||||
except:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -642,10 +642,10 @@ class LogoDownloader:
|
|||||||
# Try to load a font, fallback to default
|
# Try to load a font, fallback to default
|
||||||
try:
|
try:
|
||||||
font = ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 12)
|
font = ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 12)
|
||||||
except:
|
except (OSError, IOError):
|
||||||
try:
|
try:
|
||||||
font = ImageFont.load_default()
|
font = ImageFont.load_default()
|
||||||
except:
|
except (OSError, IOError):
|
||||||
font = None
|
font = None
|
||||||
|
|
||||||
# Draw team abbreviation
|
# Draw team abbreviation
|
||||||
|
|||||||
1
test/__init__.py
Normal file
1
test/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Test package
|
||||||
@@ -23,6 +23,7 @@ if str(project_root) not in sys.path:
|
|||||||
sys.path.insert(0, str(project_root))
|
sys.path.insert(0, str(project_root))
|
||||||
|
|
||||||
from src.config_manager import ConfigManager
|
from src.config_manager import ConfigManager
|
||||||
|
from src.exceptions import ConfigError
|
||||||
from src.plugin_system.schema_manager import SchemaManager
|
from src.plugin_system.schema_manager import SchemaManager
|
||||||
|
|
||||||
|
|
||||||
@@ -30,35 +31,31 @@ class TestInvalidJson:
|
|||||||
"""Test handling of invalid JSON in config files."""
|
"""Test handling of invalid JSON in config files."""
|
||||||
|
|
||||||
def test_invalid_json_syntax(self, tmp_path):
|
def test_invalid_json_syntax(self, tmp_path):
|
||||||
"""Config with invalid JSON syntax should be handled gracefully."""
|
"""Config with invalid JSON syntax should raise ConfigError."""
|
||||||
config_file = tmp_path / "config.json"
|
config_file = tmp_path / "config.json"
|
||||||
config_file.write_text("{ invalid json }")
|
config_file.write_text("{ invalid json }")
|
||||||
|
|
||||||
with patch.object(ConfigManager, '_get_config_path', return_value=str(config_file)):
|
config_manager = ConfigManager(config_path=str(config_file))
|
||||||
config_manager = ConfigManager(config_dir=str(tmp_path))
|
with pytest.raises(ConfigError):
|
||||||
# Should not raise, should return empty or default config
|
config_manager.load_config()
|
||||||
config = config_manager.load_config()
|
|
||||||
assert isinstance(config, dict)
|
|
||||||
|
|
||||||
def test_truncated_json(self, tmp_path):
|
def test_truncated_json(self, tmp_path):
|
||||||
"""Config with truncated JSON should be handled gracefully."""
|
"""Config with truncated JSON should raise ConfigError."""
|
||||||
config_file = tmp_path / "config.json"
|
config_file = tmp_path / "config.json"
|
||||||
config_file.write_text('{"plugin": {"enabled": true') # Missing closing braces
|
config_file.write_text('{"plugin": {"enabled": true') # Missing closing braces
|
||||||
|
|
||||||
with patch.object(ConfigManager, '_get_config_path', return_value=str(config_file)):
|
config_manager = ConfigManager(config_path=str(config_file))
|
||||||
config_manager = ConfigManager(config_dir=str(tmp_path))
|
with pytest.raises(ConfigError):
|
||||||
config = config_manager.load_config()
|
config_manager.load_config()
|
||||||
assert isinstance(config, dict)
|
|
||||||
|
|
||||||
def test_empty_config_file(self, tmp_path):
|
def test_empty_config_file(self, tmp_path):
|
||||||
"""Empty config file should be handled gracefully."""
|
"""Empty config file should raise ConfigError."""
|
||||||
config_file = tmp_path / "config.json"
|
config_file = tmp_path / "config.json"
|
||||||
config_file.write_text("")
|
config_file.write_text("")
|
||||||
|
|
||||||
with patch.object(ConfigManager, '_get_config_path', return_value=str(config_file)):
|
config_manager = ConfigManager(config_path=str(config_file))
|
||||||
config_manager = ConfigManager(config_dir=str(tmp_path))
|
with pytest.raises(ConfigError):
|
||||||
config = config_manager.load_config()
|
config_manager.load_config()
|
||||||
assert isinstance(config, dict)
|
|
||||||
|
|
||||||
|
|
||||||
class TestTypeValidation:
|
class TestTypeValidation:
|
||||||
|
|||||||
@@ -209,49 +209,47 @@ class TestDisplayControllerSchedule:
|
|||||||
def test_schedule_disabled(self, test_display_controller):
|
def test_schedule_disabled(self, test_display_controller):
|
||||||
"""Test when schedule is disabled."""
|
"""Test when schedule is disabled."""
|
||||||
controller = test_display_controller
|
controller = test_display_controller
|
||||||
controller.config = {"schedule": {"enabled": False}}
|
schedule_config = {"schedule": {"enabled": False}}
|
||||||
|
with patch.object(controller.config_service, 'get_config', return_value=schedule_config):
|
||||||
controller._check_schedule()
|
controller._check_schedule()
|
||||||
assert controller.is_display_active is True
|
assert controller.is_display_active is True
|
||||||
|
|
||||||
def test_active_hours(self, test_display_controller):
|
def test_active_hours(self, test_display_controller):
|
||||||
"""Test active hours check."""
|
"""Test active hours check."""
|
||||||
controller = test_display_controller
|
controller = test_display_controller
|
||||||
# Mock datetime to be within active hours
|
|
||||||
with patch('src.display_controller.datetime') as mock_datetime:
|
with patch('src.display_controller.datetime') as mock_datetime:
|
||||||
mock_datetime.now.return_value.strftime.return_value.lower.return_value = "monday"
|
mock_datetime.now.return_value.strftime.return_value.lower.return_value = "monday"
|
||||||
mock_datetime.now.return_value.time.return_value = datetime.strptime("12:00", "%H:%M").time()
|
mock_datetime.now.return_value.time.return_value = datetime.strptime("12:00", "%H:%M").time()
|
||||||
mock_datetime.strptime = datetime.strptime
|
mock_datetime.strptime = datetime.strptime
|
||||||
|
|
||||||
controller.config = {
|
schedule_config = {
|
||||||
"schedule": {
|
"schedule": {
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
"start_time": "09:00",
|
"start_time": "09:00",
|
||||||
"end_time": "17:00"
|
"end_time": "17:00"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
with patch.object(controller.config_service, 'get_config', return_value=schedule_config):
|
||||||
controller._check_schedule()
|
controller._check_schedule()
|
||||||
assert controller.is_display_active is True
|
assert controller.is_display_active is True
|
||||||
|
|
||||||
def test_inactive_hours(self, test_display_controller):
|
def test_inactive_hours(self, test_display_controller):
|
||||||
"""Test inactive hours check."""
|
"""Test inactive hours check."""
|
||||||
controller = test_display_controller
|
controller = test_display_controller
|
||||||
# Mock datetime to be outside active hours
|
|
||||||
with patch('src.display_controller.datetime') as mock_datetime:
|
with patch('src.display_controller.datetime') as mock_datetime:
|
||||||
mock_datetime.now.return_value.strftime.return_value.lower.return_value = "monday"
|
mock_datetime.now.return_value.strftime.return_value.lower.return_value = "monday"
|
||||||
mock_datetime.now.return_value.time.return_value = datetime.strptime("20:00", "%H:%M").time()
|
mock_datetime.now.return_value.time.return_value = datetime.strptime("20:00", "%H:%M").time()
|
||||||
mock_datetime.strptime = datetime.strptime
|
mock_datetime.strptime = datetime.strptime
|
||||||
|
|
||||||
controller.config = {
|
schedule_config = {
|
||||||
"schedule": {
|
"schedule": {
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
"start_time": "09:00",
|
"start_time": "09:00",
|
||||||
"end_time": "17:00"
|
"end_time": "17:00"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
with patch.object(controller.config_service, 'get_config', return_value=schedule_config):
|
||||||
controller._check_schedule()
|
controller._check_schedule()
|
||||||
assert controller.is_display_active is False
|
assert controller.is_display_active is False
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|||||||
@@ -29,7 +29,13 @@ def mock_config_manager():
|
|||||||
}
|
}
|
||||||
mock.get_config_path.return_value = 'config/config.json'
|
mock.get_config_path.return_value = 'config/config.json'
|
||||||
mock.get_secrets_path.return_value = 'config/config_secrets.json'
|
mock.get_secrets_path.return_value = 'config/config_secrets.json'
|
||||||
mock.get_raw_file_content.return_value = {'weather': {'api_key': 'test'}}
|
mock_config = {
|
||||||
|
'display': {'brightness': 50},
|
||||||
|
'plugins': {},
|
||||||
|
'timezone': 'UTC'
|
||||||
|
}
|
||||||
|
mock.load_config.return_value = mock_config
|
||||||
|
mock.get_raw_file_content.return_value = mock_config
|
||||||
mock.save_config_atomic.return_value = MagicMock(
|
mock.save_config_atomic.return_value = MagicMock(
|
||||||
status=MagicMock(value='success'),
|
status=MagicMock(value='success'),
|
||||||
message=None
|
message=None
|
||||||
@@ -385,17 +391,21 @@ class TestPluginsAPI:
|
|||||||
|
|
||||||
def test_get_plugin_config(self, client, mock_config_manager):
|
def test_get_plugin_config(self, client, mock_config_manager):
|
||||||
"""Test getting plugin configuration."""
|
"""Test getting plugin configuration."""
|
||||||
|
# Plugin configs live at top-level keys (not under 'plugins')
|
||||||
mock_config_manager.load_config.return_value = {
|
mock_config_manager.load_config.return_value = {
|
||||||
'plugins': {
|
'weather': {
|
||||||
'weather': {
|
'enabled': True,
|
||||||
'enabled': True,
|
'api_key': 'test_key'
|
||||||
'api_key': 'test_key'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Ensure schema manager returns serializable values
|
||||||
|
from web_interface.blueprints.api_v3 import api_v3
|
||||||
|
api_v3.schema_manager.generate_default_config.return_value = {'enabled': False}
|
||||||
|
api_v3.schema_manager.merge_with_defaults.side_effect = lambda config, defaults: {**defaults, **config}
|
||||||
|
|
||||||
response = client.get('/api/v3/plugins/config?plugin_id=weather')
|
response = client.get('/api/v3/plugins/config?plugin_id=weather')
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
data = json.loads(response.data)
|
data = json.loads(response.data)
|
||||||
assert 'enabled' in data or 'config' in data or 'data' in data
|
assert 'enabled' in data or 'config' in data or 'data' in data
|
||||||
|
|||||||
@@ -442,7 +442,7 @@ def system_status_generator():
|
|||||||
try:
|
try:
|
||||||
with open('/sys/class/thermal/thermal_zone0/temp', 'r') as f:
|
with open('/sys/class/thermal/thermal_zone0/temp', 'r') as f:
|
||||||
cpu_temp = round(float(f.read()) / 1000.0, 1)
|
cpu_temp = round(float(f.read()) / 1000.0, 1)
|
||||||
except:
|
except (OSError, ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -456,7 +456,7 @@ def system_status_generator():
|
|||||||
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)
|
||||||
service_active = result.stdout.strip() == 'active'
|
service_active = result.stdout.strip() == 'active'
|
||||||
except:
|
except (subprocess.SubprocessError, OSError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
status = {
|
status = {
|
||||||
@@ -492,7 +492,7 @@ def display_preview_generator():
|
|||||||
parallel = main_config.get('display', {}).get('hardware', {}).get('parallel', 1)
|
parallel = main_config.get('display', {}).get('hardware', {}).get('parallel', 1)
|
||||||
width = cols * chain_length
|
width = cols * chain_length
|
||||||
height = rows * parallel
|
height = rows * parallel
|
||||||
except:
|
except (KeyError, TypeError, ValueError):
|
||||||
width = 128
|
width = 128
|
||||||
height = 64
|
height = 64
|
||||||
|
|
||||||
|
|||||||
@@ -406,14 +406,11 @@ def save_schedule_config():
|
|||||||
|
|
||||||
return success_response(message='Schedule configuration saved successfully')
|
return success_response(message='Schedule configuration saved successfully')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import logging
|
logger.exception("[ScheduleConfig] Failed to save schedule configuration")
|
||||||
import traceback
|
|
||||||
error_msg = f"Error saving schedule config: {str(e)}\n{traceback.format_exc()}"
|
|
||||||
logging.error(error_msg)
|
|
||||||
return error_response(
|
return error_response(
|
||||||
ErrorCode.CONFIG_SAVE_FAILED,
|
ErrorCode.CONFIG_SAVE_FAILED,
|
||||||
f"Error saving schedule configuration: {str(e)}",
|
"Error saving schedule configuration",
|
||||||
details=traceback.format_exc(),
|
details="Internal server error - check server logs",
|
||||||
status_code=500
|
status_code=500
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -627,14 +624,11 @@ def save_dim_schedule_config():
|
|||||||
|
|
||||||
return success_response(message='Dim schedule configuration saved successfully')
|
return success_response(message='Dim schedule configuration saved successfully')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import logging
|
logger.exception("[DimScheduleConfig] Failed to save dim schedule configuration")
|
||||||
import traceback
|
|
||||||
error_msg = f"Error saving dim schedule config: {str(e)}\n{traceback.format_exc()}"
|
|
||||||
logging.error(error_msg)
|
|
||||||
return error_response(
|
return error_response(
|
||||||
ErrorCode.CONFIG_SAVE_FAILED,
|
ErrorCode.CONFIG_SAVE_FAILED,
|
||||||
f"Error saving dim schedule configuration: {str(e)}",
|
"Error saving dim schedule configuration",
|
||||||
details=traceback.format_exc(),
|
details="Internal server error - check server logs",
|
||||||
status_code=500
|
status_code=500
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -978,14 +972,11 @@ def save_main_config():
|
|||||||
|
|
||||||
return success_response(message='Configuration saved successfully')
|
return success_response(message='Configuration saved successfully')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import logging
|
logger.exception("[Config] Failed to save configuration")
|
||||||
import traceback
|
|
||||||
error_msg = f"Error saving config: {str(e)}\n{traceback.format_exc()}"
|
|
||||||
logging.error(error_msg)
|
|
||||||
return error_response(
|
return error_response(
|
||||||
ErrorCode.CONFIG_SAVE_FAILED,
|
ErrorCode.CONFIG_SAVE_FAILED,
|
||||||
f"Error saving configuration: {e}",
|
"Error saving configuration",
|
||||||
details=traceback.format_exc(),
|
details="Internal server error - check server logs",
|
||||||
status_code=500
|
status_code=500
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1021,32 +1012,20 @@ def save_raw_main_config():
|
|||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
return jsonify({'status': 'error', 'message': f'Invalid JSON: {str(e)}'}), 400
|
return jsonify({'status': 'error', 'message': f'Invalid JSON: {str(e)}'}), 400
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import logging
|
|
||||||
import traceback
|
|
||||||
from src.exceptions import ConfigError
|
from src.exceptions import ConfigError
|
||||||
|
logger.exception("[RawConfig] Failed to save raw main config")
|
||||||
# Log the full error for debugging
|
|
||||||
error_msg = f"Error saving raw main config: {str(e)}\n{traceback.format_exc()}"
|
|
||||||
logging.error(error_msg)
|
|
||||||
|
|
||||||
# Extract more specific error message if it's a ConfigError
|
|
||||||
if isinstance(e, ConfigError):
|
if isinstance(e, ConfigError):
|
||||||
error_message = str(e)
|
|
||||||
if hasattr(e, 'config_path') and e.config_path:
|
|
||||||
error_message = f"{error_message} (config_path: {e.config_path})"
|
|
||||||
return error_response(
|
return error_response(
|
||||||
ErrorCode.CONFIG_SAVE_FAILED,
|
ErrorCode.CONFIG_SAVE_FAILED,
|
||||||
error_message,
|
"Error saving raw main configuration",
|
||||||
details=traceback.format_exc(),
|
details="Internal server error - check server logs",
|
||||||
context={'config_path': e.config_path} if hasattr(e, 'config_path') and e.config_path else None,
|
|
||||||
status_code=500
|
status_code=500
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
error_message = str(e) if str(e) else "An unexpected error occurred while saving the configuration"
|
|
||||||
return error_response(
|
return error_response(
|
||||||
ErrorCode.UNKNOWN_ERROR,
|
ErrorCode.UNKNOWN_ERROR,
|
||||||
error_message,
|
"An unexpected error occurred while saving the configuration",
|
||||||
details=traceback.format_exc(),
|
details="Internal server error - check server logs",
|
||||||
status_code=500
|
status_code=500
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1072,24 +1051,22 @@ def save_raw_secrets_config():
|
|||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
return jsonify({'status': 'error', 'message': f'Invalid JSON: {str(e)}'}), 400
|
return jsonify({'status': 'error', 'message': f'Invalid JSON: {str(e)}'}), 400
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import logging
|
|
||||||
import traceback
|
|
||||||
from src.exceptions import ConfigError
|
from src.exceptions import ConfigError
|
||||||
|
logger.exception("[RawSecrets] Failed to save raw secrets config")
|
||||||
# Log the full error for debugging
|
|
||||||
error_msg = f"Error saving raw secrets config: {str(e)}\n{traceback.format_exc()}"
|
|
||||||
logging.error(error_msg)
|
|
||||||
|
|
||||||
# Extract more specific error message if it's a ConfigError
|
|
||||||
if isinstance(e, ConfigError):
|
if isinstance(e, ConfigError):
|
||||||
# ConfigError has a message attribute and may have context
|
return error_response(
|
||||||
error_message = str(e)
|
ErrorCode.CONFIG_SAVE_FAILED,
|
||||||
if hasattr(e, 'config_path') and e.config_path:
|
"Error saving raw secrets configuration",
|
||||||
error_message = f"{error_message} (config_path: {e.config_path})"
|
details="Internal server error - check server logs",
|
||||||
|
status_code=500
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
error_message = str(e) if str(e) else "An unexpected error occurred while saving the configuration"
|
return error_response(
|
||||||
|
ErrorCode.UNKNOWN_ERROR,
|
||||||
return jsonify({'status': 'error', 'message': error_message}), 500
|
"An unexpected error occurred while saving the configuration",
|
||||||
|
details="Internal server error - check server logs",
|
||||||
|
status_code=500
|
||||||
|
)
|
||||||
|
|
||||||
@api_v3.route('/system/status', methods=['GET'])
|
@api_v3.route('/system/status', methods=['GET'])
|
||||||
def get_system_status():
|
def get_system_status():
|
||||||
@@ -1470,10 +1447,8 @@ def execute_system_action():
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
import traceback
|
||||||
error_details = traceback.format_exc()
|
logger.exception("[SystemAction] Unexpected error")
|
||||||
print(f"Error in execute_system_action: {str(e)}")
|
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||||
print(error_details)
|
|
||||||
return jsonify({'status': 'error', 'message': str(e), 'details': error_details}), 500
|
|
||||||
|
|
||||||
@api_v3.route('/display/current', methods=['GET'])
|
@api_v3.route('/display/current', methods=['GET'])
|
||||||
def get_display_current():
|
def get_display_current():
|
||||||
@@ -1882,10 +1857,8 @@ def get_installed_plugins():
|
|||||||
return jsonify({'status': 'success', 'data': {'plugins': plugins}})
|
return jsonify({'status': 'success', 'data': {'plugins': plugins}})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
import traceback
|
||||||
error_details = traceback.format_exc()
|
logger.exception("[Plugins] Error listing installed plugins")
|
||||||
print(f"Error in get_installed_plugins: {str(e)}")
|
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||||
print(error_details)
|
|
||||||
return jsonify({'status': 'error', 'message': str(e), 'details': error_details}), 500
|
|
||||||
|
|
||||||
@api_v3.route('/plugins/health', methods=['GET'])
|
@api_v3.route('/plugins/health', methods=['GET'])
|
||||||
def get_plugin_health():
|
def get_plugin_health():
|
||||||
@@ -6068,8 +6041,8 @@ def upload_plugin_asset():
|
|||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
logger.exception("[API] Unexpected error")
|
||||||
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
|
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||||
|
|
||||||
@api_v3.route('/plugins/of-the-day/json/upload', methods=['POST'])
|
@api_v3.route('/plugins/of-the-day/json/upload', methods=['POST'])
|
||||||
def upload_of_the_day_json():
|
def upload_of_the_day_json():
|
||||||
@@ -6218,8 +6191,8 @@ def upload_of_the_day_json():
|
|||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
logger.exception("[API] Unexpected error")
|
||||||
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
|
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||||
|
|
||||||
@api_v3.route('/plugins/of-the-day/json/delete', methods=['POST'])
|
@api_v3.route('/plugins/of-the-day/json/delete', methods=['POST'])
|
||||||
def delete_of_the_day_json():
|
def delete_of_the_day_json():
|
||||||
@@ -6265,8 +6238,8 @@ def delete_of_the_day_json():
|
|||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
logger.exception("[API] Unexpected error")
|
||||||
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
|
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||||
|
|
||||||
@api_v3.route('/plugins/<plugin_id>/static/<path:file_path>', methods=['GET'])
|
@api_v3.route('/plugins/<plugin_id>/static/<path:file_path>', methods=['GET'])
|
||||||
def serve_plugin_static(plugin_id, file_path):
|
def serve_plugin_static(plugin_id, file_path):
|
||||||
@@ -6286,7 +6259,9 @@ def serve_plugin_static(plugin_id, file_path):
|
|||||||
requested_file = (plugin_dir / file_path).resolve()
|
requested_file = (plugin_dir / file_path).resolve()
|
||||||
|
|
||||||
# Security check: ensure file is within plugin directory
|
# Security check: ensure file is within plugin directory
|
||||||
if not str(requested_file).startswith(str(plugin_dir)):
|
try:
|
||||||
|
requested_file.relative_to(plugin_dir)
|
||||||
|
except ValueError:
|
||||||
return jsonify({'status': 'error', 'message': 'Invalid file path'}), 403
|
return jsonify({'status': 'error', 'message': 'Invalid file path'}), 403
|
||||||
|
|
||||||
# Check if file exists
|
# Check if file exists
|
||||||
@@ -6311,8 +6286,8 @@ def serve_plugin_static(plugin_id, file_path):
|
|||||||
return Response(content, mimetype=content_type)
|
return Response(content, mimetype=content_type)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
logger.exception("[API] Unexpected error")
|
||||||
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
|
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||||
|
|
||||||
|
|
||||||
@api_v3.route('/plugins/calendar/upload-credentials', methods=['POST'])
|
@api_v3.route('/plugins/calendar/upload-credentials', methods=['POST'])
|
||||||
@@ -6483,8 +6458,8 @@ def delete_plugin_asset():
|
|||||||
return jsonify({'status': 'success', 'message': 'Image deleted successfully'})
|
return jsonify({'status': 'success', 'message': 'Image deleted successfully'})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
logger.exception("[API] Unexpected error")
|
||||||
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
|
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||||
|
|
||||||
@api_v3.route('/plugins/assets/list', methods=['GET'])
|
@api_v3.route('/plugins/assets/list', methods=['GET'])
|
||||||
def list_plugin_assets():
|
def list_plugin_assets():
|
||||||
@@ -6511,8 +6486,8 @@ def list_plugin_assets():
|
|||||||
return jsonify({'status': 'success', 'data': {'assets': assets}})
|
return jsonify({'status': 'success', 'data': {'assets': assets}})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
logger.exception("[API] Unexpected error")
|
||||||
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
|
return jsonify({'status': 'error', 'message': 'Internal server error - check server logs'}), 500
|
||||||
|
|
||||||
@api_v3.route('/logs', methods=['GET'])
|
@api_v3.route('/logs', methods=['GET'])
|
||||||
def get_logs():
|
def get_logs():
|
||||||
|
|||||||
@@ -329,7 +329,7 @@ const PluginAPI = {
|
|||||||
* @returns {Promise<Array>} List of available plugins
|
* @returns {Promise<Array>} List of available plugins
|
||||||
*/
|
*/
|
||||||
async getPluginStore() {
|
async getPluginStore() {
|
||||||
const response = await this.request('/plugins/store');
|
const response = await this.request('/plugins/store/list');
|
||||||
return response.data || [];
|
return response.data || [];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user