From 7685586508b65489e4bf18ad978a68cf1c37aec4 Mon Sep 17 00:00:00 2001 From: Chuck <33324927+ChuckBuilds@users.noreply.github.com> Date: Fri, 12 Sep 2025 18:08:46 -0400 Subject: [PATCH] web ui updates for leaderboard and odds manager timeout if APi call limit is hit --- config/config.json | 1 + src/odds_ticker_manager.py | 108 ++++++++++++- templates/index_v2.html | 307 +++++++++++++++++++++++++++++++++++++ 3 files changed, 408 insertions(+), 8 deletions(-) diff --git a/config/config.json b/config/config.json index c0d1a1f1..41e5af37 100644 --- a/config/config.json +++ b/config/config.json @@ -135,6 +135,7 @@ "games_per_favorite_team": 1, "max_games_per_league": 5, "show_odds_only": false, + "fetch_odds": false, "sort_order": "soonest", "enabled_leagues": [ "nfl", diff --git a/src/odds_ticker_manager.py b/src/odds_ticker_manager.py index 81027cfe..b84869f6 100644 --- a/src/odds_ticker_manager.py +++ b/src/odds_ticker_manager.py @@ -84,6 +84,7 @@ class OddsTickerManager: self.games_per_favorite_team = self.odds_ticker_config.get('games_per_favorite_team', 1) self.max_games_per_league = self.odds_ticker_config.get('max_games_per_league', 5) self.show_odds_only = self.odds_ticker_config.get('show_odds_only', False) + self.fetch_odds = self.odds_ticker_config.get('fetch_odds', True) # New option to disable odds fetching self.sort_order = self.odds_ticker_config.get('sort_order', 'soonest') self.enabled_leagues = self.odds_ticker_config.get('enabled_leagues', ['nfl', 'nba', 'mlb']) self.update_interval = self.odds_ticker_config.get('update_interval', 3600) @@ -506,12 +507,49 @@ class OddsTickerManager: logger.debug(f"Game {game_id} starts in {time_until_game}. Setting odds update interval to {update_interval_seconds}s.") - odds_data = self.odds_manager.get_odds( - sport=sport, - league=league, - event_id=game_id, - update_interval_seconds=update_interval_seconds - ) + # Fetch odds with timeout protection to prevent freezing (if enabled) + if self.fetch_odds: + try: + import threading + import queue + + result_queue = queue.Queue() + + def fetch_odds(): + try: + odds_result = self.odds_manager.get_odds( + sport=sport, + league=league, + event_id=game_id, + update_interval_seconds=update_interval_seconds + ) + result_queue.put(('success', odds_result)) + except Exception as e: + result_queue.put(('error', e)) + + # Start odds fetch in a separate thread + odds_thread = threading.Thread(target=fetch_odds) + odds_thread.daemon = True + odds_thread.start() + + # Wait for result with 3-second timeout + try: + result_type, result_data = result_queue.get(timeout=3) + if result_type == 'success': + odds_data = result_data + else: + logger.warning(f"Odds fetch failed for game {game_id}: {result_data}") + odds_data = None + except queue.Empty: + logger.warning(f"Odds fetch timed out for game {game_id}") + odds_data = None + + except Exception as e: + logger.warning(f"Odds fetch failed for game {game_id}: {e}") + odds_data = None + else: + # Odds fetching is disabled + odds_data = None has_odds = False if odds_data and not odds_data.get('no_odds'): @@ -1518,7 +1556,34 @@ class OddsTickerManager: logger.debug(f"Number of games in data at start of display method: {len(self.games_data)}") if not self.games_data: logger.warning("Odds ticker has no games data. Attempting to update...") - self.update() + try: + import threading + import queue + + update_queue = queue.Queue() + + def perform_update(): + try: + self.update() + update_queue.put(('success', None)) + except Exception as e: + update_queue.put(('error', e)) + + # Start update in a separate thread with 10-second timeout + update_thread = threading.Thread(target=perform_update) + update_thread.daemon = True + update_thread.start() + + try: + result_type, result_data = update_queue.get(timeout=10) + if result_type == 'error': + logger.error(f"Update failed: {result_data}") + except queue.Empty: + logger.warning("Update timed out after 10 seconds, using fallback") + + except Exception as e: + logger.error(f"Error during update: {e}") + if not self.games_data: logger.warning("Still no games data after update. Displaying fallback message.") self._display_fallback_message() @@ -1526,7 +1591,34 @@ class OddsTickerManager: if self.ticker_image is None: logger.warning("Ticker image is not available. Attempting to create it.") - self._create_ticker_image() + try: + import threading + import queue + + image_queue = queue.Queue() + + def create_image(): + try: + self._create_ticker_image() + image_queue.put(('success', None)) + except Exception as e: + image_queue.put(('error', e)) + + # Start image creation in a separate thread with 5-second timeout + image_thread = threading.Thread(target=create_image) + image_thread.daemon = True + image_thread.start() + + try: + result_type, result_data = image_queue.get(timeout=5) + if result_type == 'error': + logger.error(f"Image creation failed: {result_data}") + except queue.Empty: + logger.warning("Image creation timed out after 5 seconds") + + except Exception as e: + logger.error(f"Error during image creation: {e}") + if self.ticker_image is None: logger.error("Failed to create ticker image.") self._display_fallback_message() diff --git a/templates/index_v2.html b/templates/index_v2.html index 1b2c71c5..92a62766 100644 --- a/templates/index_v2.html +++ b/templates/index_v2.html @@ -818,12 +818,18 @@ + + @@ -1408,6 +1414,147 @@ + +
+
+
+

Leaderboard Configuration

+
+ + +
+
+
+
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ +
+
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +

Enabled Sports

+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+ +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+ + +
+
+
+
@@ -1448,6 +1595,84 @@
+ +
+
+
+

Of The Day Configuration

+
+ +
+
+
+
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
Comma-separated list of category keys in display order
+
+ +

Categories

+
+
Word of the Day
+
+ +
+
+ + +
+
+ + +
+
+ +
+
Slovenian Word of the Day
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+
@@ -2689,6 +2914,88 @@ }); })(); + // Leaderboard form submit + (function augmentLeaderboardForm(){ + const form = document.getElementById('leaderboard-form'); + form.addEventListener('submit', async function(e){ + e.preventDefault(); + const payload = { + leaderboard: { + enabled: document.getElementById('leaderboard_enabled').checked, + update_interval: parseInt(document.getElementById('leaderboard_update_interval').value), + scroll_speed: parseFloat(document.getElementById('leaderboard_scroll_speed').value), + scroll_delay: parseFloat(document.getElementById('leaderboard_scroll_delay').value), + display_duration: parseInt(document.getElementById('leaderboard_display_duration').value), + loop: document.getElementById('leaderboard_loop').checked, + request_timeout: parseInt(document.getElementById('leaderboard_request_timeout').value), + dynamic_duration: document.getElementById('leaderboard_dynamic_duration').checked, + min_duration: parseInt(document.getElementById('leaderboard_min_duration').value), + max_duration: parseInt(document.getElementById('leaderboard_max_duration').value), + duration_buffer: parseFloat(document.getElementById('leaderboard_duration_buffer').value), + enabled_sports: { + nfl: { + enabled: document.getElementById('leaderboard_nfl_enabled').checked, + top_teams: parseInt(document.getElementById('leaderboard_nfl_top_teams').value) + }, + nba: { + enabled: document.getElementById('leaderboard_nba_enabled').checked, + top_teams: parseInt(document.getElementById('leaderboard_nba_top_teams').value) + }, + mlb: { + enabled: document.getElementById('leaderboard_mlb_enabled').checked, + top_teams: parseInt(document.getElementById('leaderboard_mlb_top_teams').value) + }, + ncaa_fb: { + enabled: document.getElementById('leaderboard_ncaa_fb_enabled').checked, + top_teams: parseInt(document.getElementById('leaderboard_ncaa_fb_top_teams').value), + show_ranking: document.getElementById('leaderboard_ncaa_fb_show_ranking').checked + }, + nhl: { + enabled: document.getElementById('leaderboard_nhl_enabled').checked, + top_teams: parseInt(document.getElementById('leaderboard_nhl_top_teams').value) + }, + ncaam_basketball: { + enabled: document.getElementById('leaderboard_ncaam_basketball_enabled').checked, + top_teams: parseInt(document.getElementById('leaderboard_ncaam_basketball_top_teams').value) + } + } + } + }; + await saveConfigJson(payload); + }); + })(); + + // Of The Day form submit + (function augmentOfTheDayForm(){ + const form = document.getElementById('of_the_day-form'); + form.addEventListener('submit', async function(e){ + e.preventDefault(); + const categoryOrder = document.getElementById('of_the_day_category_order').value.split(',').map(s => s.trim()).filter(Boolean); + const payload = { + of_the_day: { + enabled: document.getElementById('of_the_day_enabled').checked, + update_interval: parseInt(document.getElementById('of_the_day_update_interval').value), + display_rotate_interval: parseInt(document.getElementById('of_the_day_display_rotate_interval').value), + subtitle_rotate_interval: parseInt(document.getElementById('of_the_day_subtitle_rotate_interval').value), + category_order: categoryOrder, + categories: { + word_of_the_day: { + enabled: document.getElementById('of_the_day_word_enabled').checked, + data_file: document.getElementById('of_the_day_word_data_file').value, + display_name: document.getElementById('of_the_day_word_display_name').value + }, + slovenian_word_of_the_day: { + enabled: document.getElementById('of_the_day_slovenian_enabled').checked, + data_file: document.getElementById('of_the_day_slovenian_data_file').value, + display_name: document.getElementById('of_the_day_slovenian_display_name').value + } + } + } + }; + await saveConfigJson(payload); + }); + })(); + // Text form submit (function augmentTextForm(){ const form = document.getElementById('text-form');