Finally fixtures.html working

parent beeec67c
Collecting flask-cors
Using cached flask_cors-6.0.1-py3-none-any.whl.metadata (5.3 kB)
Requirement already satisfied: flask>=0.9 in ./venv/lib/python3.13/site-packages (from flask-cors) (3.1.2)
Requirement already satisfied: Werkzeug>=0.7 in ./venv/lib/python3.13/site-packages (from flask-cors) (3.1.3)
Requirement already satisfied: blinker>=1.9.0 in ./venv/lib/python3.13/site-packages (from flask>=0.9->flask-cors) (1.9.0)
Requirement already satisfied: click>=8.1.3 in ./venv/lib/python3.13/site-packages (from flask>=0.9->flask-cors) (8.3.1)
Requirement already satisfied: itsdangerous>=2.2.0 in ./venv/lib/python3.13/site-packages (from flask>=0.9->flask-cors) (2.2.0)
Requirement already satisfied: jinja2>=3.1.2 in ./venv/lib/python3.13/site-packages (from flask>=0.9->flask-cors) (3.1.6)
Requirement already satisfied: markupsafe>=2.1.1 in ./venv/lib/python3.13/site-packages (from flask>=0.9->flask-cors) (3.0.3)
Using cached flask_cors-6.0.1-py3-none-any.whl (13 kB)
Installing collected packages: flask-cors
Successfully installed flask-cors-6.0.1
../assets/
\ No newline at end of file
...@@ -135,6 +135,10 @@ class MbetterClientApplication: ...@@ -135,6 +135,10 @@ class MbetterClientApplication:
# Preserve command line Qt overlay setting # Preserve command line Qt overlay setting
stored_settings.qt.use_native_overlay = self.settings.qt.use_native_overlay stored_settings.qt.use_native_overlay = self.settings.qt.use_native_overlay
# Preserve command line debug settings
stored_settings.debug_overlay = self.settings.debug_overlay
stored_settings.debug_player = self.settings.debug_player
# Preserve command line SSL settings # Preserve command line SSL settings
stored_settings.web.enable_ssl = self.settings.web.enable_ssl stored_settings.web.enable_ssl = self.settings.web.enable_ssl
stored_settings.web.ssl_cert_path = self.settings.web.ssl_cert_path stored_settings.web.ssl_cert_path = self.settings.web.ssl_cert_path
......
...@@ -234,8 +234,6 @@ class MessageBus: ...@@ -234,8 +234,6 @@ class MessageBus:
if component_name != message.sender: # Don't send to sender if component_name != message.sender: # Don't send to sender
if self._deliver_to_queue(queue, message): if self._deliver_to_queue(queue, message):
success_count += 1 success_count += 1
logger.debug(f"Broadcast message delivered to {success_count} components")
else: else:
# Send to specific recipient # Send to specific recipient
if message.recipient in self._queues: if message.recipient in self._queues:
......
...@@ -51,12 +51,15 @@ class OverlayWebChannel(QObject): ...@@ -51,12 +51,15 @@ class OverlayWebChannel(QObject):
dataUpdated = pyqtSignal(dict) dataUpdated = pyqtSignal(dict)
positionChanged = pyqtSignal(float, float) # position, duration in seconds positionChanged = pyqtSignal(float, float) # position, duration in seconds
videoInfoChanged = pyqtSignal(dict) videoInfoChanged = pyqtSignal(dict)
fixtureDataUpdated = pyqtSignal(dict) # Signal for fixture data updates
# Signal to receive console messages from JavaScript
consoleMessage = pyqtSignal(str, str, int, str) # level, message, line, source
def __init__(self): def __init__(self, db_manager=None):
super().__init__() super().__init__()
self.mutex = QMutex() self.mutex = QMutex()
self.overlay_data = {} self.overlay_data = {}
self.db_manager = db_manager
logger.info("OverlayWebChannel initialized") logger.info("OverlayWebChannel initialized")
@pyqtSlot(str) @pyqtSlot(str)
...@@ -178,42 +181,125 @@ class OverlayWebChannel(QObject): ...@@ -178,42 +181,125 @@ class OverlayWebChannel(QObject):
else: else:
logger.debug("Video info contained only null/undefined values, skipping") logger.debug("Video info contained only null/undefined values, skipping")
def send_fixture_data_update(self, data: Dict[str, Any]): @pyqtSlot(str)
"""Send fixture data update to JavaScript (thread-safe)""" def log(self, message: str):
# Validate data before emitting """Receive console.log messages from JavaScript"""
if not data: logger.info(f"[JS CONSOLE.LOG] {message}")
logger.warning("send_fixture_data_update called with null/empty data, skipping") print(f"[JS CONSOLE.LOG] {message}")
return
@pyqtSlot(result=str)
def getFixtureData(self) -> str:
"""Provide fixture data to JavaScript via WebChannel"""
try:
# Get fixture data from the games thread via message bus
fixture_data = self._get_fixture_data_from_games_thread()
if fixture_data:
logger.debug(f"Providing fixture data to JavaScript: {len(fixture_data)} matches")
return json.dumps(fixture_data)
else:
logger.debug("No fixture data available")
return json.dumps([])
except Exception as e:
logger.error(f"Failed to get fixture data: {e}")
return json.dumps([])
def _get_fixture_data_from_games_thread(self) -> Optional[List[Dict[str, Any]]]:
"""Get fixture data from the games thread"""
try:
# Import here to avoid circular imports
from ..core.message_bus import MessageBus, Message, MessageType
# Get the message bus from the parent player
if hasattr(self, 'parent') and self.parent():
player_window = self.parent()
if hasattr(player_window, '_message_bus') and player_window._message_bus:
message_bus = player_window._message_bus
# Send a request for fixture data to games_thread
request_message = Message(
type=MessageType.CUSTOM,
sender="qt_player",
recipient="games_thread",
data={
"request": "get_active_fixture_matches",
"limit": 5
}
)
# For synchronous response, we need to implement a callback mechanism
# For now, return mock data or get from database directly
return self._get_fixture_data_from_database()
logger.debug(f"OverlayWebChannel sending fixture data: {data}") # Fallback: get data directly from database
self.fixtureDataUpdated.emit(data) return self._get_fixture_data_from_database()
except Exception as e:
logger.error(f"Failed to get fixture data from games thread: {e}")
return None
@pyqtSlot() def _get_fixture_data_from_database(self) -> Optional[List[Dict[str, Any]]]:
def trigger_fixtures_data_load(self): """Get fixture data directly from database"""
"""Slot to manually trigger fixtures data loading from JavaScript"""
try: try:
logger.info("Manual trigger of fixtures data load from JavaScript") from ..database.models import MatchModel, MatchOutcomeModel
from datetime import datetime
# Find the PlayerWindow and trigger fixtures data load
# This method is called from JavaScript, so we need to find the PlayerWindow # Use the database manager passed to this channel
overlay_view = None if not self.db_manager:
logger.error("Database manager not initialized")
# Try to find the overlay view that contains this channel return None
# We need to traverse up to find the PlayerWindow
current_obj = self.parent()
while current_obj:
if hasattr(current_obj, '_find_player_window'):
player_window = current_obj._find_player_window()
if player_window and hasattr(player_window, '_send_fixtures_to_overlay'):
logger.debug("Found PlayerWindow, triggering fixtures data load")
player_window._send_fixtures_to_overlay()
return
current_obj = current_obj.parent()
logger.warning("Could not find PlayerWindow to trigger fixtures data load") session = self.db_manager.get_session()
try:
# Get today's date
today = datetime.now().date()
# Get active matches for today (non-terminal states)
active_matches = session.query(MatchModel).filter(
MatchModel.start_time.isnot(None),
MatchModel.start_time >= datetime.combine(today, datetime.min.time()),
MatchModel.start_time < datetime.combine(today, datetime.max.time()),
MatchModel.status.notin_(['done', 'cancelled', 'failed', 'paused']),
MatchModel.active_status == True
).order_by(MatchModel.start_time.asc()).limit(5).all()
if not active_matches:
logger.debug("No active matches found")
return []
fixture_data = []
for match in active_matches:
# Get outcomes for this match
outcomes = session.query(MatchOutcomeModel).filter(
MatchOutcomeModel.match_id == match.id
).all()
outcome_dict = {}
for outcome in outcomes:
outcome_dict[outcome.column_name] = outcome.float_value
match_data = {
'id': match.id,
'match_number': match.match_number,
'fighter1_township': match.fighter1_township,
'fighter2_township': match.fighter2_township,
'venue_kampala_township': match.venue_kampala_township,
'start_time': match.start_time.isoformat() if match.start_time else None,
'outcomes': outcome_dict
}
fixture_data.append(match_data)
logger.debug(f"Retrieved {len(fixture_data)} matches from database")
return fixture_data
finally:
session.close()
except Exception as e: except Exception as e:
logger.error(f"Failed to trigger fixtures data load from JavaScript: {e}") logger.error(f"Failed to get fixture data from database: {e}")
return None
class VideoProcessingWorker(QRunnable): class VideoProcessingWorker(QRunnable):
...@@ -272,9 +358,12 @@ class VideoProcessingWorker(QRunnable): ...@@ -272,9 +358,12 @@ class VideoProcessingWorker(QRunnable):
class OverlayWebView(QWebEngineView): class OverlayWebView(QWebEngineView):
"""Custom QWebEngineView for video overlays with transparent background""" """Custom QWebEngineView for video overlays with transparent background"""
def __init__(self, parent=None, debug_overlay=False): def __init__(self, parent=None, debug_overlay=False, web_server_url=None, db_manager=None):
super().__init__(parent) super().__init__(parent)
logger.info(f"DEBUG: OverlayWebView.__init__ called with debug_overlay={debug_overlay}")
self.debug_overlay = debug_overlay self.debug_overlay = debug_overlay
self.web_server_url = web_server_url or "http://127.0.0.1:5001"
self.db_manager = db_manager
self.web_channel = None self.web_channel = None
self.overlay_channel = None self.overlay_channel = None
self.current_template = "default.html" self.current_template = "default.html"
...@@ -335,16 +424,129 @@ class OverlayWebView(QWebEngineView): ...@@ -335,16 +424,129 @@ class OverlayWebView(QWebEngineView):
""") """)
logger.debug(f"OverlayWebView setup completed - Mesa: {is_mesa}, transparency configured") logger.debug(f"OverlayWebView setup completed - Mesa: {is_mesa}, transparency configured")
# Setup WebChannel # Setup WebChannel
self.web_channel = QWebChannel() self.web_channel = QWebChannel()
self.overlay_channel = OverlayWebChannel() self.overlay_channel = OverlayWebChannel(db_manager=self.db_manager)
self.web_channel.registerObject("overlay", self.overlay_channel) self.web_channel.registerObject("overlay", self.overlay_channel)
page.setWebChannel(self.web_channel) page.setWebChannel(self.web_channel)
# Console override moved to _enable_debug_console to ensure it runs after page load
# Note: Console message capturing via WebChannel is now handled by JavaScript overrides
# The javaScriptConsoleMessage signal connection was removed as it was failing
# Load default template # Load default template
self.load_template(self.current_template) self.load_template(self.current_template)
# Show debug console if debug mode is enabled
if self.debug_overlay:
# Connect to load finished signal to enable debug console
logger.info("DEBUG: Connecting loadFinished signal to _enable_debug_console")
self.page().loadFinished.connect(self._enable_debug_console)
def _enable_debug_console(self, ok=None):
"""Enable debug console and ensure JavaScript overrides are active"""
try:
if ok is not None and not ok:
logger.warning("Page failed to load, cannot enable debug console")
return
logger.info("DEBUG: _enable_debug_console called")
logger.info("Enabling debug console for JavaScript logging")
# Enable Qt WebEngine developer tools
page = self.page()
if page:
# Try to connect to Qt WebEngine console messages
try:
page.javaScriptConsoleMessage.connect(self._on_javaScript_console_message)
logger.info("Connected to Qt WebEngine console messages")
except Exception as e:
logger.warning(f"Could not connect to Qt WebEngine console messages: {e}")
# Override console.log in JavaScript to send messages to Python
page.runJavaScript("""
(function() {
if (typeof window.qtConsoleOverride === 'undefined') {
window.qtConsoleOverride = true;
var originalLog = console.log;
var originalError = console.error;
var originalWarn = console.warn;
var originalInfo = console.info;
// Override console methods - WebChannel should be ready
console.log = function(...args) {
var message = args.map(String).join(' ');
if (window.overlay && window.overlay.log) {
window.overlay.log('[LOG] ' + message);
}
originalLog.apply(console, args);
};
console.error = function(...args) {
var message = args.map(String).join(' ');
if (window.overlay && window.overlay.log) {
window.overlay.log('[ERROR] ' + message);
}
originalError.apply(console, args);
};
console.warn = function(...args) {
var message = args.map(String).join(' ');
if (window.overlay && window.overlay.log) {
window.overlay.log('[WARN] ' + message);
}
originalWarn.apply(console, args);
};
console.info = function(...args) {
var message = args.map(String).join(' ');
if (window.overlay && window.overlay.log) {
window.overlay.log('[INFO] ' + message);
}
originalInfo.apply(console, args);
};
console.debug = function(...args) {
var message = args.map(String).join(' ');
if (window.overlay && window.overlay.log) {
window.overlay.log('[DEBUG] ' + message);
}
originalLog.apply(console, args);
};
// Test the override
console.log('JavaScript console override active - Qt WebEngine debug console enabled');
}
})();
""")
logger.info("Debug console enabled - JavaScript console override injected")
except Exception as e:
logger.error(f"Failed to enable debug console: {e}")
def _on_javaScript_console_message(self, level, message, line, source):
"""Handle Qt WebEngine console messages"""
try:
logger.info(f"DEBUG: JavaScript console message received: {level} - {message}")
level_name = {
0: 'INFO',
1: 'WARNING',
2: 'ERROR'
}.get(level, 'UNKNOWN')
logger.info(f"[QtWebEngine {level_name}] {source}:{line} - {message}")
# Also send to our WebChannel if available
if hasattr(self, 'overlay_channel') and self.overlay_channel:
self.overlay_channel.log(f"[QtWebEngine {level_name}] {source}:{line} - {message}")
except Exception as e:
logger.error(f"Failed to handle JavaScript console message: {e}")
def _setup_custom_scheme(self): def _setup_custom_scheme(self):
"""Setup custom URL scheme handler for overlay resources""" """Setup custom URL scheme handler for overlay resources"""
try: try:
...@@ -375,6 +577,7 @@ class OverlayWebView(QWebEngineView): ...@@ -375,6 +577,7 @@ class OverlayWebView(QWebEngineView):
def load_template(self, template_name: str): def load_template(self, template_name: str):
"""Load a specific template file, prioritizing uploaded templates""" """Load a specific template file, prioritizing uploaded templates"""
try: try:
logger.debug(f"Loading overlay template: {template_name}")
if self.debug_overlay: if self.debug_overlay:
logger.debug(f"GREEN SCREEN DEBUG: Starting template load - {template_name}") logger.debug(f"GREEN SCREEN DEBUG: Starting template load - {template_name}")
logger.debug(f"GREEN SCREEN DEBUG: Current page URL before load: {self.url().toString()}") logger.debug(f"GREEN SCREEN DEBUG: Current page URL before load: {self.url().toString()}")
...@@ -456,10 +659,21 @@ class OverlayWebView(QWebEngineView): ...@@ -456,10 +659,21 @@ class OverlayWebView(QWebEngineView):
from PyQt6.QtCore import QTimer from PyQt6.QtCore import QTimer
QTimer.singleShot(100, lambda: self._ensure_overlay_visibility_post_load(was_visible)) QTimer.singleShot(100, lambda: self._ensure_overlay_visibility_post_load(was_visible))
# If fixtures template was loaded, trigger fixtures data loading # If fixtures template was loaded, the template handles its own data fetching via JavaScript
if template_name == "fixtures.html" or template_name == "fixtures": if template_name == "fixtures.html" or template_name == "fixtures":
logger.info("Fixtures template loaded, scheduling fixtures data load") logger.info("Fixtures template loaded - template handles its own data fetching via JavaScript API calls")
QTimer.singleShot(500, lambda: self._trigger_fixtures_data_load()) # Send webServerBaseUrl to the fixtures template for API calls
logger.info(f"Sending webServerBaseUrl to fixtures template: {self.web_server_url}")
data_to_send = {'webServerBaseUrl': self.web_server_url}
if self.debug_overlay:
data_to_send['debugMode'] = True
logger.info("Debug mode enabled for fixtures template")
self.update_overlay_data(data_to_send)
# Ensure console override is active after template load
if self.debug_overlay:
self._enable_debug_console()
QTimer.singleShot(200, lambda: self._ensure_console_override())
if self.debug_overlay: if self.debug_overlay:
logger.debug(f"GREEN SCREEN DEBUG: Template load initiated - {template_path}") logger.debug(f"GREEN SCREEN DEBUG: Template load initiated - {template_path}")
...@@ -661,50 +875,53 @@ class OverlayWebView(QWebEngineView): ...@@ -661,50 +875,53 @@ class OverlayWebView(QWebEngineView):
if self.overlay_channel: if self.overlay_channel:
self.overlay_channel.send_video_info(info) self.overlay_channel.send_video_info(info)
def _trigger_fixtures_data_load(self): def _ensure_console_override(self):
"""Trigger fixtures data loading when fixtures template is loaded""" """Ensure console override is active in the current page"""
try: try:
logger.info("Triggering fixtures data load for fixtures template") if hasattr(self, 'page') and self.page():
self.page().runJavaScript("""
# Find the PlayerWindow by traversing up the parent hierarchy if (typeof window.overlay !== 'undefined' && window.overlay && window.overlay.log) {
player_window = self._find_player_window() if (!console.originalLog) {
if player_window and hasattr(player_window, '_send_fixtures_to_overlay'): console.originalLog = console.log;
logger.debug("Calling PlayerWindow's _send_fixtures_to_overlay method") console.originalError = console.error;
player_window._send_fixtures_to_overlay() console.originalWarn = console.warn;
else: console.originalInfo = console.info;
logger.warning("Could not find PlayerWindow with _send_fixtures_to_overlay method")
console.log = function(...args) {
var message = args.map(String).join(' ');
window.overlay.log('[LOG] ' + message);
console.originalLog.apply(console, args);
};
console.error = function(...args) {
var message = args.map(String).join(' ');
window.overlay.log('[ERROR] ' + message);
console.originalError.apply(console, args);
};
console.warn = function(...args) {
var message = args.map(String).join(' ');
window.overlay.log('[WARN] ' + message);
console.originalWarn.apply(console, args);
};
console.info = function(...args) {
var message = args.map(String).join(' ');
window.overlay.log('[INFO] ' + message);
console.originalInfo.apply(console, args);
};
}
}
""")
logger.debug("Console override ensured after template load")
except Exception as e: except Exception as e:
logger.error(f"Failed to trigger fixtures data load: {e}") logger.debug(f"Failed to ensure console override: {e}")
# Removed _on_javaScript_console_message method as console capturing now uses WebChannel
def _find_player_window(self):
"""Find the PlayerWindow by traversing up the parent hierarchy"""
try:
current = self.parent()
while current:
if hasattr(current, '_send_fixtures_to_overlay'):
logger.debug("Found PlayerWindow in parent hierarchy")
return current
current = current.parent()
logger.debug("PlayerWindow not found in parent hierarchy")
return None
except Exception as e:
logger.error(f"Error finding PlayerWindow: {e}")
return None
def update_fixture_data(self, fixture_data: Dict[str, Any]):
"""Update fixture data"""
if self.overlay_channel:
self.overlay_channel.send_fixture_data(fixture_data)
def _on_fixture_data_updated(self, fixture_data: Dict[str, Any]):
"""Handle fixture data update signal and call JavaScript function"""
try:
# Call the JavaScript updateFixtureData function
js_code = f"updateFixtureData({json.dumps(fixture_data)});"
self.page().runJavaScript(js_code)
logger.debug("Called JavaScript updateFixtureData function")
except Exception as e:
logger.error(f"Failed to call JavaScript updateFixtureData: {e}")
class NativeOverlayWidget(QWidget): class NativeOverlayWidget(QWidget):
...@@ -1088,25 +1305,56 @@ class PlayerWindow(QMainWindow): ...@@ -1088,25 +1305,56 @@ class PlayerWindow(QMainWindow):
def _get_web_server_base_url(self) -> str: def _get_web_server_base_url(self) -> str:
"""Get the web server base URL for API requests""" """Get the web server base URL for API requests"""
try: try:
# Default web server configuration - matches main.py defaults # Try to get the URL from the parent QtVideoPlayer component
host = "127.0.0.1" # Default host
port = 5001 # Default port
# Try to get web server settings if available
if hasattr(self, '_message_bus') and self._message_bus: if hasattr(self, '_message_bus') and self._message_bus:
# Check if we can get web server info from message bus or settings # Look for the qt_player component in the message bus
# For now, use defaults since we don't have direct access to web settings qt_player_queue = self._message_bus._queues.get('qt_player')
pass if qt_player_queue and hasattr(qt_player_queue, 'component'):
qt_player = qt_player_queue.component
# Construct base URL if hasattr(qt_player, 'web_dashboard_url') and qt_player.web_dashboard_url:
base_url = f"http://{host}:{port}" logger.debug(f"Web server base URL from QtVideoPlayer: {qt_player.web_dashboard_url}")
logger.debug(f"Web server base URL determined as: {base_url}") return qt_player.web_dashboard_url
# Fallback to defaults if not available
base_url = "http://127.0.0.1:5001"
logger.debug(f"Web server base URL using fallback: {base_url}")
return base_url return base_url
except Exception as e: except Exception as e:
logger.error(f"Failed to determine web server base URL: {e}") logger.error(f"Failed to determine web server base URL: {e}")
# Return default fallback # Return default fallback
return "http://127.0.0.1:5001" return "http://127.0.0.1:5001"
def _get_database_manager(self):
"""Get database manager from message bus"""
try:
if hasattr(self, '_message_bus') and self._message_bus:
# Try to get db_manager from web_dashboard component
try:
web_dashboard_queue = self._message_bus._queues.get('web_dashboard')
if web_dashboard_queue and hasattr(web_dashboard_queue, 'component'):
component = web_dashboard_queue.component
if hasattr(component, 'db_manager'):
logger.debug("PlayerWindow: Got db_manager from web_dashboard component")
return component.db_manager
except Exception as e:
logger.debug(f"PlayerWindow: Could not get db_manager from message bus: {e}")
# Fallback: create database manager directly
from ..config.settings import get_user_data_dir
from ..database.manager import DatabaseManager
db_path = get_user_data_dir() / "mbetterclient.db"
logger.debug(f"PlayerWindow: Creating database manager directly: {db_path}")
db_manager = DatabaseManager(str(db_path))
if db_manager.initialize():
return db_manager
else:
logger.warning("PlayerWindow: Failed to initialize database manager")
return None
except Exception as e:
logger.error(f"PlayerWindow: Failed to get database manager: {e}")
return None
def setup_ui(self): def setup_ui(self):
"""Setup enhanced window UI""" """Setup enhanced window UI"""
...@@ -1181,9 +1429,12 @@ class PlayerWindow(QMainWindow): ...@@ -1181,9 +1429,12 @@ class PlayerWindow(QMainWindow):
self.window_overlay = NativeOverlayWidget(self.overlay_window) self.window_overlay = NativeOverlayWidget(self.overlay_window)
logger.debug("PlayerWindow: Created NativeOverlayWidget overlay as separate window") logger.debug("PlayerWindow: Created NativeOverlayWidget overlay as separate window")
else: else:
# Pass debug_overlay setting to OverlayWebView # Pass debug_overlay setting and web server URL to OverlayWebView
debug_overlay = getattr(self, 'debug_overlay', False) debug_overlay = getattr(self, 'debug_overlay', False)
self.window_overlay = OverlayWebView(self.overlay_window, debug_overlay=debug_overlay) web_server_url = self._get_web_server_base_url()
# Get database manager from message bus
db_manager = self._get_database_manager()
self.window_overlay = OverlayWebView(self.overlay_window, debug_overlay=debug_overlay, web_server_url=web_server_url, db_manager=db_manager)
logger.debug("PlayerWindow: Created QWebEngineView overlay as separate window") logger.debug("PlayerWindow: Created QWebEngineView overlay as separate window")
# Layout for overlay window # Layout for overlay window
...@@ -1358,11 +1609,6 @@ class PlayerWindow(QMainWindow): ...@@ -1358,11 +1609,6 @@ class PlayerWindow(QMainWindow):
self.overlay_timer.timeout.connect(self.update_overlay_periodically) self.overlay_timer.timeout.connect(self.update_overlay_periodically)
self.overlay_timer.start(1000) # Update every second self.overlay_timer.start(1000) # Update every second
# Fixtures data refresh timer
self.fixtures_timer = QTimer()
self.fixtures_timer.timeout.connect(self._refresh_fixtures_data)
self.fixtures_timer.start(30000) # Refresh fixtures every 30 seconds
# Template rotation timer (initially disabled) # Template rotation timer (initially disabled)
self.template_rotation_timer = QTimer() self.template_rotation_timer = QTimer()
self.template_rotation_timer.timeout.connect(self._rotate_template) self.template_rotation_timer.timeout.connect(self._rotate_template)
...@@ -1840,132 +2086,10 @@ class PlayerWindow(QMainWindow): ...@@ -1840,132 +2086,10 @@ class PlayerWindow(QMainWindow):
except Exception as e: except Exception as e:
logger.error(f"Periodic overlay update failed: {e}") logger.error(f"Periodic overlay update failed: {e}")
def _refresh_fixtures_data(self):
"""Refresh fixtures data and send to overlay (called by timer)"""
try:
logger.debug("Refreshing fixtures data...")
self._send_fixtures_to_overlay()
except Exception as e:
logger.error(f"Failed to refresh fixtures data: {e}")
def _send_fixtures_to_overlay(self):
"""Send fixtures data to the overlay via WebChannel"""
try:
# Fetch fixtures data
fixtures_data = self._fetch_fixtures_data()
# Send data to overlay
if hasattr(self, 'window_overlay') and self.window_overlay:
if fixtures_data:
logger.info(f"Sending fixtures data to overlay: {len(fixtures_data.get('matches', []))} matches")
# Send via WebChannel signal - primary method
if hasattr(self.window_overlay, 'overlay_channel') and self.window_overlay.overlay_channel:
self.window_overlay.overlay_channel.send_fixture_data_update(fixtures_data)
logger.debug("Sent fixtures data via WebChannel signal")
# Also try calling JavaScript functions directly as fallback
try:
if hasattr(self.window_overlay, 'page'):
page = self.window_overlay.page()
if page:
# Try updateFixtureData function
page.runJavaScript(f"updateFixtureData({json.dumps(fixtures_data)});")
# Also try updateFixtures function for compatibility
page.runJavaScript(f"updateFixtures({json.dumps(fixtures_data)});")
logger.debug("Sent fixtures data via JavaScript function calls")
except Exception as js_error:
logger.debug(f"JavaScript call failed: {js_error}")
else:
logger.warning("No fixtures data available to send to overlay")
# Send empty data to trigger fallback
empty_data = {'matches': []}
if hasattr(self.window_overlay, 'overlay_channel') and self.window_overlay.overlay_channel:
self.window_overlay.overlay_channel.send_fixture_data_update(empty_data)
else:
logger.warning("No overlay available to send fixtures data")
except Exception as e:
logger.error(f"Failed to send fixtures data to overlay: {e}")
def _fetch_fixture_data(self):
"""Fetch fixture data using priority order: WebSocket -> API -> QWebChannel"""
try:
logger.info("Fetching fixture data using priority order: WebSocket -> API -> QWebChannel")
# Priority 1: Try WebSocket (if available)
if hasattr(self, 'window_overlay') and self.window_overlay:
overlay_view = self.window_overlay
if hasattr(overlay_view, 'socket') and overlay_view.socket and overlay_view.socket.connected:
logger.info("WebSocket available, requesting fixture data...")
# WebSocket request would be handled asynchronously
# For now, fall through to API
pass
# Priority 2: Try API fetch
try:
import requests
base_url = self._get_web_server_base_url()
api_url = f"{base_url}/api/cashier/pending-matches"
logger.info(f"Fetching fixtures data from API: {api_url}")
response = requests.get(api_url, timeout=10)
if response.status_code == 200:
data = response.json()
if data.get('success') and data.get('matches'):
matches = data['matches']
logger.info(f"Successfully fetched {len(matches)} matches from API")
return {'matches': matches}
else:
logger.warning(f"API returned success=false or no matches: {data}")
else:
logger.error(f"API request failed with status {response.status_code}: {response.text}")
except Exception as api_error:
logger.warning(f"API fetch failed: {api_error}")
# Priority 3: Try QWebChannel (last resort)
logger.info("Falling back to QWebChannel for fixture data")
# QWebChannel communication would happen through the overlay
# For now, return None to indicate no data available
logger.warning("QWebChannel fallback not implemented yet")
return None
except Exception as e:
logger.error(f"Failed to fetch fixture data: {e}")
return None
def _fetch_fixtures_data(self):
"""Fetch fixtures data from the web API"""
try:
import requests
from datetime import datetime, date
base_url = self._get_web_server_base_url()
api_url = f"{base_url}/api/cashier/pending-matches"
logger.info(f"Fetching fixtures data from: {api_url}")
# Make API request
response = requests.get(api_url, timeout=10)
if response.status_code == 200:
data = response.json()
if data.get('success') and data.get('matches'):
matches = data['matches']
logger.info(f"Successfully fetched {len(matches)} matches from API")
return {'matches': matches}
else:
logger.warning(f"API returned success=false or no matches: {data}")
return None
else:
logger.error(f"API request failed with status {response.status_code}: {response.text}")
return None
except Exception as e:
logger.error(f"Failed to fetch fixtures data: {e}")
return None
def mouseMoveEvent(self, event): def mouseMoveEvent(self, event):
"""Show controls on mouse movement""" """Show controls on mouse movement"""
...@@ -2229,6 +2353,7 @@ class QtVideoPlayer(QObject): ...@@ -2229,6 +2353,7 @@ class QtVideoPlayer(QObject):
def __init__(self, message_bus: MessageBus, settings: QtConfig, debug_player: bool = False, debug_overlay: bool = False): def __init__(self, message_bus: MessageBus, settings: QtConfig, debug_player: bool = False, debug_overlay: bool = False):
super().__init__() super().__init__()
logger.info(f"DEBUG: QtVideoPlayer.__init__ called with debug_overlay={debug_overlay}")
self.name = "qt_player" self.name = "qt_player"
self.message_bus = message_bus self.message_bus = message_bus
self.settings = settings self.settings = settings
...@@ -2255,113 +2380,14 @@ class QtVideoPlayer(QObject): ...@@ -2255,113 +2380,14 @@ class QtVideoPlayer(QObject):
logger.info("QtVideoPlayer (PyQt6) initialized") logger.info("QtVideoPlayer (PyQt6) initialized")
def _get_web_server_base_url(self) -> str:
"""Get the web server base URL for API requests"""
try:
# Default web server configuration - matches main.py defaults
host = "127.0.0.1" # Default host
port = 5001 # Default port
# Try to get web server settings if available
if hasattr(self, '_message_bus') and self._message_bus:
# Check if we can get web server info from message bus or settings
# For now, use defaults since we don't have direct access to web settings
pass
# Construct base URL
base_url = f"http://{host}:{port}"
logger.debug(f"Web server base URL determined as: {base_url}")
return base_url
except Exception as e:
logger.error(f"Failed to determine web server base URL: {e}")
# Return default fallback
return "http://127.0.0.1:5001"
def _fetch_fixtures_data(self) -> Optional[Dict[str, Any]]:
"""Fetch fixtures data from the web API"""
try:
import requests
from datetime import datetime, date
base_url = self._get_web_server_base_url()
api_url = f"{base_url}/api/cashier/pending-matches"
logger.info(f"Fetching fixtures data from: {api_url}")
# Make API request
response = requests.get(api_url, timeout=10)
if response.status_code == 200:
data = response.json()
if data.get('success') and data.get('matches'):
matches = data['matches']
logger.info(f"Successfully fetched {len(matches)} matches from API")
return {'matches': matches}
else:
logger.warning(f"API returned success=false or no matches: {data}")
return None
else:
logger.error(f"API request failed with status {response.status_code}: {response.text}")
return None
except Exception as e:
logger.error(f"Failed to fetch fixtures data: {e}")
return None
def _refresh_fixtures_data(self):
"""Refresh fixtures data and send to overlay (called by timer)"""
try:
logger.debug("Refreshing fixtures data...")
self._send_fixtures_to_overlay()
except Exception as e:
logger.error(f"Failed to refresh fixtures data: {e}")
def _send_fixtures_to_overlay(self, fixtures_data: Optional[Dict[str, Any]] = None):
"""Send fixtures data to the overlay via WebChannel"""
try:
# If no data provided, fetch it
if fixtures_data is None:
fixtures_data = self._fetch_fixtures_data()
# Send data to overlay
if hasattr(self, 'window_overlay') and self.window_overlay:
if fixtures_data:
logger.info(f"Sending fixtures data to overlay: {len(fixtures_data.get('matches', []))} matches")
# Send via WebChannel signal - primary method
if hasattr(self.window_overlay, 'overlay_channel') and self.window_overlay.overlay_channel:
self.window_overlay.overlay_channel.send_fixture_data_update(fixtures_data)
logger.debug("Sent fixtures data via WebChannel signal")
# Also try calling JavaScript functions directly as fallback
try:
if hasattr(self.window_overlay, 'page'):
page = self.window_overlay.page()
if page:
# Try updateFixtureData function
page.runJavaScript(f"updateFixtureData({json.dumps(fixtures_data)});")
# Also try updateFixtures function for compatibility
page.runJavaScript(f"updateFixtures({json.dumps(fixtures_data)});")
logger.debug("Sent fixtures data via JavaScript function calls")
except Exception as js_error:
logger.debug(f"JavaScript call failed: {js_error}")
else:
logger.warning("No fixtures data available to send to overlay")
# Send empty data to trigger fallback
empty_data = {'matches': []}
if hasattr(self.window_overlay, 'overlay_channel') and self.window_overlay.overlay_channel:
self.window_overlay.overlay_channel.send_fixture_data_update(empty_data)
else:
logger.warning("No overlay available to send fixtures data")
except Exception as e:
logger.error(f"Failed to send fixtures data to overlay: {e}")
def initialize(self) -> bool: def initialize(self) -> bool:
"""Initialize PyQt6 application and components""" """Initialize PyQt6 application and components"""
try: try:
logger.info("DEBUG: QtVideoPlayer.initialize called")
with QMutexLocker(self.mutex): with QMutexLocker(self.mutex):
# Linux-specific system configuration # Linux-specific system configuration
self._configure_linux_system() self._configure_linux_system()
...@@ -2445,6 +2471,8 @@ class QtVideoPlayer(QObject): ...@@ -2445,6 +2471,8 @@ class QtVideoPlayer(QObject):
self.message_bus.subscribe(self.name, MessageType.PLAY_VIDEO_RESULT, self._handle_play_video_result) self.message_bus.subscribe(self.name, MessageType.PLAY_VIDEO_RESULT, self._handle_play_video_result)
self.message_bus.subscribe(self.name, MessageType.GAME_STATUS, self._handle_game_status) self.message_bus.subscribe(self.name, MessageType.GAME_STATUS, self._handle_game_status)
self.message_bus.subscribe(self.name, MessageType.SYSTEM_STATUS, self._handle_system_status) self.message_bus.subscribe(self.name, MessageType.SYSTEM_STATUS, self._handle_system_status)
# Subscribe to web dashboard ready messages to get correct server URL
self.message_bus.subscribe(self.name, MessageType.SYSTEM_STATUS, self._handle_web_dashboard_ready)
logger.info("QtPlayer subscriptions completed successfully") logger.info("QtPlayer subscriptions completed successfully")
# Delay loading default overlay to allow JavaScript initialization # Delay loading default overlay to allow JavaScript initialization
...@@ -2547,6 +2575,15 @@ class QtVideoPlayer(QObject): ...@@ -2547,6 +2575,15 @@ class QtVideoPlayer(QObject):
except Exception as e: except Exception as e:
logger.warning(f"Linux application configuration warning: {e}") logger.warning(f"Linux application configuration warning: {e}")
def _get_web_server_base_url(self) -> str:
"""Get the web server base URL for API requests"""
try:
# Return the stored web dashboard URL, or default
return self.web_dashboard_url or "http://127.0.0.1:5001"
except Exception as e:
logger.error(f"Failed to get web server base URL: {e}")
return "http://127.0.0.1:5001"
def _load_default_overlay(self): def _load_default_overlay(self):
"""Load default overlay display with enhanced WebChannel readiness checking""" """Load default overlay display with enhanced WebChannel readiness checking"""
try: try:
...@@ -2558,20 +2595,17 @@ class QtVideoPlayer(QObject): ...@@ -2558,20 +2595,17 @@ class QtVideoPlayer(QObject):
'webServerBaseUrl': self._get_web_server_base_url() 'webServerBaseUrl': self._get_web_server_base_url()
} }
# Fetch and include fixture data if available # NOTE: Removed fixture data pre-fetching - overlay templates handle their own data fetching via JavaScript
fixture_data = self._fetch_fixtures_data() # The fixtures.html template uses WebSocket, WebChannel, or API calls to get fixture data
if fixture_data:
default_data['fixtures'] = fixture_data.get('matches', [])
logger.info(f"Included {len(default_data['fixtures'])} fixtures in default overlay")
if not self.window: if not self.window:
logger.debug("Window not ready for overlay loading") logger.debug("Window not ready for overlay loading")
return return
# Use the separate window overlay instead of video widget overlay # Use the separate window overlay instead of video widget overlay
if hasattr(self.window, 'window_overlay'): if hasattr(self.window, 'window_overlay'):
overlay_view = self.window.window_overlay overlay_view = self.window.window_overlay
# Check overlay type and handle accordingly # Check overlay type and handle accordingly
if self.window._is_native_overlay(overlay_view): if self.window._is_native_overlay(overlay_view):
# Native overlay is always ready # Native overlay is always ready
...@@ -2583,7 +2617,7 @@ class QtVideoPlayer(QObject): ...@@ -2583,7 +2617,7 @@ class QtVideoPlayer(QObject):
logger.debug("WebEngine overlay not ready, retrying in 1 second...") logger.debug("WebEngine overlay not ready, retrying in 1 second...")
QTimer.singleShot(1000, self._load_default_overlay) QTimer.singleShot(1000, self._load_default_overlay)
return return
# Additional delay before sending data to ensure JavaScript is fully initialized # Additional delay before sending data to ensure JavaScript is fully initialized
logger.debug("WebEngine ready, scheduling overlay update...") logger.debug("WebEngine ready, scheduling overlay update...")
QTimer.singleShot(500, lambda: self._send_safe_overlay_update(overlay_view, default_data)) QTimer.singleShot(500, lambda: self._send_safe_overlay_update(overlay_view, default_data))
...@@ -2591,7 +2625,7 @@ class QtVideoPlayer(QObject): ...@@ -2591,7 +2625,7 @@ class QtVideoPlayer(QObject):
logger.warning(f"Unknown overlay type: {type(overlay_view)}") logger.warning(f"Unknown overlay type: {type(overlay_view)}")
else: else:
logger.warning("No window_overlay available for default overlay loading") logger.warning("No window_overlay available for default overlay loading")
except Exception as e: except Exception as e:
logger.error(f"Failed to load default overlay: {e}") logger.error(f"Failed to load default overlay: {e}")
# Retry on error with longer delay # Retry on error with longer delay
...@@ -3114,18 +3148,6 @@ class QtVideoPlayer(QObject): ...@@ -3114,18 +3148,6 @@ class QtVideoPlayer(QObject):
logger.debug(f"GREEN SCREEN FIX: Protecting video rendering during template load") logger.debug(f"GREEN SCREEN FIX: Protecting video rendering during template load")
overlay_view.load_template(load_specific_template) overlay_view.load_template(load_specific_template)
# If loading fixtures template, fetch and send fixture data
if load_specific_template == "fixtures.html":
logger.info("Loading fixtures template, fetching fixture data...")
fixture_data = self._fetch_fixtures_data()
if fixture_data:
# Send fixture data to the overlay
fixture_payload = {'fixtures': fixture_data.get('matches', [])}
self.window.update_fixture_data(fixture_payload)
logger.info(f"Sent {len(fixture_payload['fixtures'])} fixtures to fixtures overlay")
else:
logger.warning("No fixture data available for fixtures template")
if self.debug_overlay: if self.debug_overlay:
logger.debug(f"GREEN SCREEN DEBUG: Specific template load initiated") logger.debug(f"GREEN SCREEN DEBUG: Specific template load initiated")
# Otherwise reload current template if requested and using WebEngine overlay # Otherwise reload current template if requested and using WebEngine overlay
...@@ -3923,6 +3945,37 @@ class QtVideoPlayer(QObject): ...@@ -3923,6 +3945,37 @@ class QtVideoPlayer(QObject):
logger.info("QtPlayer: System status handling failed, trying to play intro directly") logger.info("QtPlayer: System status handling failed, trying to play intro directly")
self._check_and_play_intro() self._check_and_play_intro()
def _handle_web_dashboard_ready(self, message: Message):
"""Handle web dashboard ready messages to update server URL"""
try:
sender = message.sender
status = message.data.get("status")
details = message.data.get("details", {})
# Check if this is a web_dashboard ready message
if sender == "web_dashboard" and status == "ready":
host = details.get("host")
port = details.get("port")
ssl_enabled = details.get("ssl_enabled", False)
if host and port:
protocol = "https" if ssl_enabled else "http"
web_server_url = f"{protocol}://{host}:{port}"
logger.info(f"QtPlayer: Updated web server URL from web_dashboard ready message: {web_server_url}")
# Update the web dashboard URL for API calls
self.web_dashboard_url = web_server_url
# Update any existing overlay with the correct URL
if hasattr(self, 'window') and self.window and hasattr(self.window, 'window_overlay'):
overlay_view = self.window.window_overlay
if isinstance(overlay_view, OverlayWebView):
logger.info(f"QtPlayer: Sending updated webServerBaseUrl to overlay: {web_server_url}")
self.window._update_overlay_safe(overlay_view, {'webServerBaseUrl': web_server_url})
except Exception as e:
logger.error(f"QtPlayer: Failed to handle web dashboard ready message: {e}")
def _check_and_play_intro(self): def _check_and_play_intro(self):
"""Check if we should play the intro video when no game is active""" """Check if we should play the intro video when no game is active"""
try: try:
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Fixtures Overlay</title> <title>Fixtures Overlay</title>
<script src="qrc:///qtwebchannel/qwebchannel.js"></script>
<style> <style>
* { * {
margin: 0; margin: 0;
...@@ -30,6 +31,61 @@ ...@@ -30,6 +31,61 @@
font-size: 10px; font-size: 10px;
z-index: 9999; z-index: 9999;
} }
/* Debug console overlay */
#debugConsole {
position: absolute;
top: 10px;
right: 10px;
width: 400px;
height: 300px;
background: rgba(0, 0, 0, 0.9);
border: 2px solid #00ff00;
border-radius: 8px;
color: #00ff00;
font-family: 'Courier New', monospace;
font-size: 11px;
padding: 8px;
overflow-y: auto;
z-index: 10000;
display: none; /* Hidden by default, shown when --debug-player */
}
#debugConsole.show {
display: block;
}
#debugConsole .console-header {
font-weight: bold;
border-bottom: 1px solid #00ff00;
padding-bottom: 4px;
margin-bottom: 4px;
}
#debugConsole .console-message {
margin: 2px 0;
word-wrap: break-word;
}
#debugConsole .console-timestamp {
color: #ffff00;
}
#debugConsole .console-level-log {
color: #ffffff;
}
#debugConsole .console-level-error {
color: #ff4444;
}
#debugConsole .console-level-warn {
color: #ffaa00;
}
#debugConsole .console-level-info {
color: #00aaff;
}
.overlay-container { .overlay-container {
position: absolute; position: absolute;
...@@ -280,6 +336,12 @@ ...@@ -280,6 +336,12 @@
</style> </style>
</head> </head>
<body> <body>
<!-- Debug Console Overlay -->
<div id="debugConsole">
<div class="console-header">🔍 JavaScript Debug Console</div>
<div id="consoleOutput"></div>
</div>
<div class="overlay-container"> <div class="overlay-container">
<div class="fixtures-panel" id="fixturesPanel"> <div class="fixtures-panel" id="fixturesPanel">
<div class="fixtures-title">Next 5 matches:</div> <div class="fixtures-title">Next 5 matches:</div>
...@@ -320,6 +382,48 @@ ...@@ -320,6 +382,48 @@
let nextMatchStartTime = null; let nextMatchStartTime = null;
let startTime = null; let startTime = null;
// Apply console.log override immediately with buffering
(function() {
var originalConsoleLog = console.log;
var messageBuffer = [];
console.log = function(...args) {
var message = args.map(String).join(' ');
if (window.overlay && window.overlay.log) {
window.overlay.log('[LOG] ' + message);
} else {
messageBuffer.push('[LOG] ' + message);
}
originalConsoleLog.apply(console, args);
};
// Function to flush buffer when overlay becomes available
window.flushConsoleBuffer = function() {
if (window.overlay && window.overlay.log) {
messageBuffer.forEach(function(msg) {
window.overlay.log(msg);
});
messageBuffer = [];
}
};
// Check periodically for overlay availability
var checkInterval = setInterval(function() {
if (window.overlay && window.overlay.log) {
window.flushConsoleBuffer();
clearInterval(checkInterval);
}
}, 50);
// Clear interval after 5 seconds to avoid infinite polling
setTimeout(function() {
clearInterval(checkInterval);
}, 5000);
})();
// Test console override
console.log('TEST: Console override applied and buffering');
// Debug timing helper // Debug timing helper
function getTimestamp() { function getTimestamp() {
return new Date().toISOString(); return new Date().toISOString();
...@@ -331,104 +435,86 @@ ...@@ -331,104 +435,86 @@
console.log(`🔍 DEBUG [${getTimestamp()}] ${label}${elapsed}`); console.log(`🔍 DEBUG [${getTimestamp()}] ${label}${elapsed}`);
} }
// WebSocket variables // WebChannel fixture data functions
let wsConnection = null; async function fetchFixturesData(retryCount = 0) {
let wsReconnectInterval = null; const maxRetries = 10;
let wsConnected = false; const baseDelay = 1000; // 1 second
// WebSocket connection functions
function connectWebSocket() {
if (wsConnection && wsConnection.readyState === WebSocket.OPEN) {
debugTime('WebSocket already connected');
return;
}
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws/fixtures`;
debugTime(`Attempting WebSocket connection to: ${wsUrl}`);
try { try {
wsConnection = new WebSocket(wsUrl); debugTime('Fetching fixtures data via WebChannel');
wsConnection.onopen = function(event) { // Check if WebChannel is available
debugTime('WebSocket connection opened'); if (!window.overlay || typeof window.overlay.getFixtureData !== 'function') {
wsConnected = true; console.log('DEBUG: WebChannel not ready, retrying...');
if (retryCount < maxRetries) {
// Clear any reconnect interval const delay = baseDelay * Math.pow(2, retryCount);
if (wsReconnectInterval) { console.log(`DEBUG: Retrying in ${delay}ms (attempt ${retryCount + 1}/${maxRetries + 1})`);
clearInterval(wsReconnectInterval); setTimeout(() => {
wsReconnectInterval = null; fetchFixturesData(retryCount + 1);
} }, delay);
} else {
// Request initial data console.log('DEBUG: Max retries reached, using fallback data');
sendWebSocketMessage({ action: 'get_fixtures' }); debugTime('Max retries reached for WebChannel connection');
}; showFallbackFixtures();
wsConnection.onmessage = function(event) {
try {
const data = JSON.parse(event.data);
debugTime('WebSocket message received');
handleWebSocketMessage(data);
} catch (error) {
debugTime(`WebSocket message parse error: ${error.message}`);
} }
}; return;
}
wsConnection.onclose = function(event) { console.log('DEBUG: WebChannel available, calling getFixtureData()');
debugTime(`WebSocket connection closed (code: ${event.code})`);
wsConnected = false; // Call WebChannel method to get fixture data
const fixtureDataJson = await window.overlay.getFixtureData();
// Attempt to reconnect after 5 seconds console.log('DEBUG: WebChannel response received');
if (!wsReconnectInterval) { console.log('DEBUG: Raw response =', fixtureDataJson);
debugTime('Scheduling WebSocket reconnection in 5 seconds');
wsReconnectInterval = setTimeout(() => { let data;
wsReconnectInterval = null; try {
connectWebSocket(); data = JSON.parse(fixtureDataJson);
}, 5000); console.log('DEBUG: WebChannel response JSON parsed successfully');
} console.log('DEBUG: WebChannel response data =', data);
}; } catch (parseError) {
console.log('DEBUG: Failed to parse WebChannel response as JSON');
throw new Error(`JSON parse error: ${parseError.message}`);
}
wsConnection.onerror = function(error) { debugTime('Fixtures data received via WebChannel');
debugTime('WebSocket connection error');
wsConnected = false;
};
if (data && Array.isArray(data) && data.length > 0) {
console.log('DEBUG: WebChannel returned fixtures data');
fixturesData = data;
renderFixtures();
} else {
console.log('DEBUG: WebChannel response did not contain fixtures data');
debugTime('No fixtures data in WebChannel response');
showNoMatches('No fixtures available');
}
} catch (error) { } catch (error) {
debugTime(`WebSocket creation failed: ${error.message}`); console.log('DEBUG: Exception caught in fetchFixturesData');
wsConnected = false; console.log('DEBUG: Error message =', error.message);
} console.log('DEBUG: Error stack =', error.stack);
} debugTime(`Failed to fetch fixtures data: ${error.message}`);
function sendWebSocketMessage(message) { // Check if we should retry
if (wsConnection && wsConnection.readyState === WebSocket.OPEN) { if (retryCount < maxRetries) {
wsConnection.send(JSON.stringify(message)); // Calculate delay with exponential backoff
debugTime(`WebSocket message sent: ${message.action || 'unknown'}`); const delay = baseDelay * Math.pow(2, retryCount);
} else { console.log(`DEBUG: Retrying in ${delay}ms (attempt ${retryCount + 2}/${maxRetries + 1})`);
debugTime('Cannot send WebSocket message - connection not open');
setTimeout(() => {
fetchFixturesData(retryCount + 1);
}, delay);
} else {
console.log('DEBUG: Max retries reached, using fallback data');
debugTime('Max retries reached for fixtures data fetch');
// Show fallback data instead of error message
showFallbackFixtures();
}
} }
} }
function handleWebSocketMessage(data) {
debugTime(`Handling WebSocket message: ${data.type || 'unknown'}`);
if (data.type === 'fixtures_update' && data.fixtures) {
debugTime('Processing fixtures update from WebSocket');
fixturesData = data.fixtures;
renderFixtures();
} else if (data.type === 'matches_update' && data.matches) {
debugTime('Processing matches update from WebSocket');
fixturesData = data.matches;
renderFixtures();
} else if (data.type === 'config_update' && data.webServerBaseUrl) {
debugTime(`Updating web server URL: ${data.webServerBaseUrl}`);
webServerBaseUrl = data.webServerBaseUrl;
} else {
debugTime(`Unknown WebSocket message type: ${data.type}`);
}
}
// Web server configuration - will be set via WebChannel // WebChannel configuration - no HTTP requests needed
let webServerBaseUrl = 'http://127.0.0.1:5001'; // Default fallback let webChannelReady = false;
// Debug logging function that sends messages to Qt application logs // Debug logging function that sends messages to Qt application logs
function debugLog(message, level = 'info') { function debugLog(message, level = 'info') {
...@@ -452,77 +538,129 @@ ...@@ -452,77 +538,129 @@
// Store original console.log before overriding // Store original console.log before overriding
const originalConsoleLog = console.log; const originalConsoleLog = console.log;
// Override console.log to redirect all console.log calls to Qt application logs // Debug console functionality
console.log = function(...args) { let debugConsoleEnabled = false;
// Convert arguments to string message const maxConsoleMessages = 50;
const message = args.map(arg => let consoleMessageCount = 0;
typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
).join(' ');
// Send to Qt application logs directly (avoid calling debugLog to prevent recursion) function addToDebugConsole(level, message) {
try { if (!debugConsoleEnabled) return;
if (typeof qt !== 'undefined' && qt.webChannelTransport) {
if (window.sendDebugMessage) { const consoleOutput = document.getElementById('consoleOutput');
window.sendDebugMessage(`[FIXTURES] ${message}`); if (!consoleOutput) return;
}
const messageDiv = document.createElement('div');
messageDiv.className = `console-message console-level-${level}`;
const timestamp = new Date().toLocaleTimeString();
messageDiv.innerHTML = `<span class="console-timestamp">[${timestamp}]</span> ${message}`;
consoleOutput.appendChild(messageDiv);
consoleMessageCount++;
// Remove old messages if too many
if (consoleMessageCount > maxConsoleMessages) {
const firstMessage = consoleOutput.firstElementChild;
if (firstMessage) {
consoleOutput.removeChild(firstMessage);
consoleMessageCount--;
} }
} catch (e) {
// Fallback handled below
} }
// Always call original console.log for browser debugging // Auto-scroll to bottom
originalConsoleLog.apply(console, args); consoleOutput.scrollTop = consoleOutput.scrollHeight;
}; }
// Function to receive fixture data from Qt WebChannel // Check if debug console should be enabled (via WebChannel or timeout)
function updateFixtureData(data) { function checkDebugConsole() {
debugTime('Received fixture data from WebChannel'); const debugConsole = document.getElementById('debugConsole');
if (data && data.fixtures) { if (debugConsole && debugConsole.classList.contains('show')) {
debugTime('Processing fixtures data'); debugConsoleEnabled = true;
fixturesData = data.fixtures; console.log('🔍 Debug console detected as enabled');
renderFixtures();
} else if (data && data.matches) {
debugTime('Processing matches data');
fixturesData = data.matches;
renderFixtures();
} else {
debugTime('No valid fixture data in WebChannel update, waiting for data');
// Don't show fallback data - wait for real data
} }
} }
// Function to update overlay data (called by Qt WebChannel) // Check periodically for debug console enablement
function updateOverlayData(data) { setInterval(checkDebugConsole, 500);
debugTime('Received overlay data from WebChannel');
overlayData = data || {}; // Setup WebChannel communication
function setupWebChannel() {
// Update web server base URL if provided // Check if WebChannel is already set up by overlay.js
if (data && data.webServerBaseUrl) { if (window.overlay) {
webServerBaseUrl = data.webServerBaseUrl; console.log('🔍 DEBUG: WebChannel already set up by overlay.js');
debugTime(`Updated web server base URL: ${webServerBaseUrl}`);
// Test WebChannel
if (window.overlay && window.overlay.log) {
window.overlay.log('TEST: WebChannel connection successful');
}
// Listen for data updates from Python
if (window.overlay.dataUpdated) {
window.overlay.dataUpdated.connect(function(data) {
console.log('🔍 DEBUG: Received data update from Python:', data);
if (data && data.webServerBaseUrl) {
webServerBaseUrl = data.webServerBaseUrl;
webServerUrlReceived = true;
console.log('🔍 DEBUG: Web server base URL updated to:', webServerBaseUrl);
// If we were waiting for the URL, start fetching data now
if (document.readyState === 'complete' || document.readyState === 'interactive') {
console.log('🔍 DEBUG: Document ready, starting data fetch after URL update');
fetchFixturesData(0);
}
}
if (data && data.debugMode) {
console.log('🔍 DEBUG: Debug mode enabled, showing debug console');
const debugConsole = document.getElementById('debugConsole');
if (debugConsole) {
debugConsole.classList.add('show');
debugConsoleEnabled = true;
}
}
});
}
return;
} }
// Check if we have fixtures data from WebChannel // Fallback: setup WebChannel if overlay.js didn't do it
if (data && data.fixtures) { if (typeof qt !== 'undefined' && qt.webChannelTransport) {
debugTime('Processing fixtures data from overlay data'); try {
fixturesData = data.fixtures; new QWebChannel(qt.webChannelTransport, function(channel) {
renderFixtures(); console.log('🔍 DEBUG: WebChannel connected successfully (fallback)');
} else if (data && data.matches) {
debugTime('Processing matches data from overlay data'); // Connect to overlay object
fixturesData = data.matches; window.overlay = channel.objects.overlay;
renderFixtures();
// Listen for data updates from Python
if (window.overlay && window.overlay.dataUpdated) {
window.overlay.dataUpdated.connect(function(data) {
console.log('🔍 DEBUG: Received data update from Python:', data);
// WebChannel is ready for fixture data
webChannelReady = true;
console.log('🔍 DEBUG: WebChannel ready for fixture data');
if (data && data.debugMode) {
console.log('🔍 DEBUG: Debug mode enabled, showing debug console');
const debugConsole = document.getElementById('debugConsole');
if (debugConsole) {
debugConsole.classList.add('show');
debugConsoleEnabled = true;
}
}
});
} else {
console.log('🔍 DEBUG: Overlay object not available in WebChannel');
}
});
} catch (e) {
console.log('🔍 DEBUG: Failed to setup WebChannel:', e);
}
} else { } else {
debugTime('No fixtures data in overlay data, will wait for updateFixtureData call'); console.log('🔍 DEBUG: WebChannel not available, using default webServerBaseUrl');
// Don't fetch from API - wait for Python side to provide data via updateFixtureData
} }
} }
// Console override is now applied at the beginning of the script with buffering
// Function to receive fixture data from Qt WebChannel (called by Python) - new method
function updateFixtures(data) {
console.log('🔍 DEBUG: Received fixtures data from WebChannel via updateFixtures:', data);
updateFixtureData(data);
}
// Show fallback sample matches when API is not available // Show fallback sample matches when API is not available
...@@ -562,6 +700,57 @@ ...@@ -562,6 +700,57 @@
console.log('🔍 DEBUG: Fallback data loaded, rendering fixtures'); console.log('🔍 DEBUG: Fallback data loaded, rendering fixtures');
renderFixtures(); renderFixtures();
} }
// Show fallback fixtures data when API is not available yet (loading state)
function showFallbackFixtures() {
console.log('🔍 DEBUG: Showing fallback fixtures data - API not available yet');
fixturesData = [
{
id: 1,
match_number: 1,
fighter1_township: 'Loading...',
fighter2_township: 'Loading...',
venue_kampala_township: 'Loading...',
outcomes: [
{ outcome_name: 'WIN1', outcome_value: '-' },
{ outcome_name: 'X', outcome_value: '-' },
{ outcome_name: 'WIN2', outcome_value: '-' },
{ outcome_name: 'UNDER', outcome_value: '-' },
{ outcome_name: 'OVER', outcome_value: '-' }
]
},
{
id: 2,
match_number: 2,
fighter1_township: 'Loading...',
fighter2_township: 'Loading...',
venue_kampala_township: 'Loading...',
outcomes: [
{ outcome_name: 'WIN1', outcome_value: '-' },
{ outcome_name: 'X', outcome_value: '-' },
{ outcome_name: 'WIN2', outcome_value: '-' },
{ outcome_name: 'UNDER', outcome_value: '-' },
{ outcome_name: 'OVER', outcome_value: '-' }
]
},
{
id: 3,
match_number: 3,
fighter1_township: 'Loading...',
fighter2_township: 'Loading...',
venue_kampala_township: 'Loading...',
outcomes: [
{ outcome_name: 'WIN1', outcome_value: '-' },
{ outcome_name: 'X', outcome_value: '-' },
{ outcome_name: 'WIN2', outcome_value: '-' },
{ outcome_name: 'UNDER', outcome_value: '-' },
{ outcome_name: 'OVER', outcome_value: '-' }
]
}
];
console.log('🔍 DEBUG: Fallback fixtures data loaded, rendering fixtures');
renderFixtures();
}
// Show fallback only when absolutely necessary // Show fallback only when absolutely necessary
function showFallbackWithDefaults(message) { function showFallbackWithDefaults(message) {
...@@ -569,46 +758,6 @@ ...@@ -569,46 +758,6 @@
showNoMatches('No live matches available - API connection failed'); showNoMatches('No live matches available - API connection failed');
} }
// Enhance matches with outcomes data by fetching match details for each
async function enhanceMatchesWithOutcomes() {
try {
console.log('Enhancing matches with outcomes data...');
// For each match, try to get its outcomes
for (let i = 0; i < fixturesData.length; i++) {
const match = fixturesData[i];
try {
// Try to get match outcomes from fixture details API
const response = await fetch(`${webServerBaseUrl}/api/fixtures/${match.fixture_id}`);
const fixtureData = await response.json();
if (fixtureData.success && fixtureData.matches) {
// Find this specific match in the fixture data
const matchWithOutcomes = fixtureData.matches.find(m => m.id === match.id);
if (matchWithOutcomes && matchWithOutcomes.outcomes) {
console.log(`Found ${matchWithOutcomes.outcomes.length} outcomes for match ${match.id}`);
fixturesData[i].outcomes = matchWithOutcomes.outcomes;
} else {
console.log(`No outcomes found for match ${match.id}, using defaults`);
fixturesData[i].outcomes = getDefaultOutcomes();
}
} else {
console.log(`Failed to get fixture details for match ${match.id}`);
fixturesData[i].outcomes = getDefaultOutcomes();
}
} catch (error) {
console.error(`Error fetching outcomes for match ${match.id}:`, error);
fixturesData[i].outcomes = getDefaultOutcomes();
}
}
console.log('Finished enhancing matches with outcomes');
} catch (error) {
console.error('Error enhancing matches with outcomes:', error);
}
}
// Get default outcomes when API data is not available // Get default outcomes when API data is not available
function getDefaultOutcomes() { function getDefaultOutcomes() {
...@@ -852,6 +1001,9 @@ ...@@ -852,6 +1001,9 @@
startTime = Date.now(); startTime = Date.now();
debugTime('DOM Content Loaded - Starting fixtures overlay initialization'); debugTime('DOM Content Loaded - Starting fixtures overlay initialization');
// Setup WebChannel first
setupWebChannel();
// Show loading message initially // Show loading message initially
document.getElementById('fixturesContent').style.display = 'none'; document.getElementById('fixturesContent').style.display = 'none';
document.getElementById('noMatches').style.display = 'none'; document.getElementById('noMatches').style.display = 'none';
...@@ -860,107 +1012,33 @@ ...@@ -860,107 +1012,33 @@
debugTime('UI initialized - Loading message displayed'); debugTime('UI initialized - Loading message displayed');
// Try WebSocket connection first (faster than WebChannel) // Wait briefly for WebChannel to connect
debugTime('Attempting WebSocket connection for real-time data');
connectWebSocket();
// Fallback: Try WebChannel if WebSocket fails after 2 seconds
setTimeout(() => {
if (!wsConnected) {
debugTime('WebSocket not connected after 2 seconds, trying WebChannel fallback');
// Initialize WebChannel connection for data updates
debugTime('Initializing WebChannel connection for data updates');
// Poll for QWebChannel availability
let channelInitialized = false;
let pollCount = 0;
const checkWebChannel = function() {
pollCount++;
if (channelInitialized) return;
if (typeof qt !== 'undefined' && qt.webChannelTransport) {
debugTime(`QWebChannel available after ${pollCount} polls, initializing`);
try {
new QWebChannel(qt.webChannelTransport, function(channel) {
if (channelInitialized) return;
channelInitialized = true;
debugTime('WebChannel initialized, setting up connections');
if (channel.objects.overlay && channel.objects.overlay.trigger_fixtures_data_load) {
debugTime('Calling trigger_fixtures_data_load from WebChannel');
channel.objects.overlay.trigger_fixtures_data_load();
}
// Set up signal connections
if (channel.objects.overlay) {
channel.objects.overlay.dataChanged.connect(function(data) {
debugTime('WebChannel dataChanged signal received');
updateOverlayData(data);
});
if (channel.objects.overlay.fixtureDataUpdated) {
channel.objects.overlay.fixtureDataUpdated.connect(function(data) {
debugTime('WebChannel fixture data signal received');
updateFixtureData(data);
});
}
if (channel.objects.overlay.getCurrentData) {
channel.objects.overlay.getCurrentData(function(data) {
debugTime('WebChannel initial data received');
updateOverlayData(data);
});
}
// Additional trigger attempts
if (channel.objects.overlay.trigger_fixtures_data_load) {
channel.objects.overlay.trigger_fixtures_data_load();
} else if (channel.objects.overlay.sendFixtureDataUpdate) {
channel.objects.overlay.sendFixtureDataUpdate({'test': 'fallback_trigger'});
}
}
});
} catch (e) {
debugTime(`WebChannel initialization failed: ${e.message}`);
}
} else {
// Continue polling if not available yet
if (pollCount % 20 === 0) { // Log every 20 polls (every second)
debugTime(`Still polling for QWebChannel... (${pollCount} polls)`);
}
setTimeout(checkWebChannel, 50);
}
};
checkWebChannel();
}
}, 2000);
// Show no matches if no data after 5 seconds total
setTimeout(() => { setTimeout(() => {
if (!fixturesData || fixturesData.length === 0) { console.log('🔍 DEBUG: Starting fixture data fetch via WebChannel');
debugTime('No data received after 5 seconds - showing waiting message');
showNoMatches('Waiting for fixture data...'); // Fetch fixtures data via WebChannel
} else { debugTime('Fetching fixtures data via WebChannel');
debugTime('Data was received before 5 second timeout'); fetchFixturesData();
}
}, 5000); // Set up periodic refresh every 30 seconds
setInterval(() => {
debugTime('Periodic refresh: fetching updated fixtures data');
fetchFixturesData(0); // Start with retry count 0 for periodic refreshes
}, 30000);
// Show no matches if no data after 5 seconds total
setTimeout(() => {
if (!fixturesData || fixturesData.length === 0) {
debugTime('No data received after 5 seconds - showing waiting message');
showNoMatches('Waiting for fixture data...');
} else {
debugTime('Data was received before 5 second timeout');
}
}, 5000);
}, 50); // Wait 50ms for WebChannel setup
}); });
</script> </script>
<!--
IMPORTANT: When creating or editing custom templates, always maintain these two script tags:
1. qrc:///qtwebchannel/qwebchannel.js - Required for Qt WebChannel communication
2. overlay://overlay.js - Required for overlay functionality and data updates
These scripts enable communication between the Qt application and the overlay template.
Without them, the template will not receive data updates or function properly.
NOTE: When editing this template or creating new ones, never remove these script sources!
The overlay:// custom scheme ensures JavaScript files work for both built-in and uploaded templates.
-->
<script src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script src="overlay://overlay.js"></script>
</body> </body>
</html> </html>
\ No newline at end of file
...@@ -10,6 +10,7 @@ from flask import Flask, request, jsonify, render_template, redirect, url_for, s ...@@ -10,6 +10,7 @@ from flask import Flask, request, jsonify, render_template, redirect, url_for, s
from flask_login import LoginManager, login_required, current_user from flask_login import LoginManager, login_required, current_user
from flask_jwt_extended import JWTManager, create_access_token, jwt_required as flask_jwt_required from flask_jwt_extended import JWTManager, create_access_token, jwt_required as flask_jwt_required
from flask_socketio import SocketIO, emit from flask_socketio import SocketIO, emit
from flask_cors import CORS
from werkzeug.serving import make_server from werkzeug.serving import make_server
import threading import threading
...@@ -19,6 +20,7 @@ from ..config.settings import WebConfig ...@@ -19,6 +20,7 @@ from ..config.settings import WebConfig
from ..config.manager import ConfigManager from ..config.manager import ConfigManager
from ..database.manager import DatabaseManager from ..database.manager import DatabaseManager
from ..utils.ssl_utils import get_ssl_certificate_paths, create_ssl_context from ..utils.ssl_utils import get_ssl_certificate_paths, create_ssl_context
from flask_cors import CORS
from .auth import AuthManager from .auth import AuthManager
from .api import DashboardAPI from .api import DashboardAPI
from .routes import main_bp, auth_bp, api_bp from .routes import main_bp, auth_bp, api_bp
...@@ -103,8 +105,19 @@ class WebDashboard(ThreadedComponent): ...@@ -103,8 +105,19 @@ class WebDashboard(ThreadedComponent):
# Create Flask app # Create Flask app
app = Flask(__name__, app = Flask(__name__,
template_folder=str(template_dir), template_folder=str(template_dir),
static_folder=str(static_dir)) static_folder=str(static_dir))
# Initialize CORS
CORS(app, resources={
r"/api/*": {
"origins": ["*"],
"methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
"allow_headers": ["Content-Type", "Authorization", "X-Requested-With"],
"expose_headers": ["Content-Range", "X-Content-Range"],
"supports_credentials": False
}
})
# Initialize SocketIO (disabled for PyInstaller compatibility) # Initialize SocketIO (disabled for PyInstaller compatibility)
try: try:
...@@ -130,8 +143,18 @@ class WebDashboard(ThreadedComponent): ...@@ -130,8 +143,18 @@ class WebDashboard(ThreadedComponent):
login_manager.init_app(app) login_manager.init_app(app)
login_manager.login_view = 'auth.login' login_manager.login_view = 'auth.login'
login_manager.login_message = 'Please log in to access this page.' login_manager.login_message = 'Please log in to access this page.'
jwt_manager = JWTManager(app) jwt_manager = JWTManager(app)
# Initialize CORS
CORS(app, resources={
r"/api/*": {
"origins": ["file://", "http://127.0.0.1:*", "http://localhost:*"],
"methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
"allow_headers": ["Content-Type", "Authorization"],
"supports_credentials": False
}
})
# User loader for Flask-Login # User loader for Flask-Login
@login_manager.user_loader @login_manager.user_loader
...@@ -469,8 +492,6 @@ class WebDashboard(ThreadedComponent): ...@@ -469,8 +492,6 @@ class WebDashboard(ThreadedComponent):
def _process_message(self, message: Message): def _process_message(self, message: Message):
"""Process received message""" """Process received message"""
try: try:
logger.debug(f"WebDashboard processing message: {message}")
# Handle messages directly since some messages don't trigger subscription handlers # Handle messages directly since some messages don't trigger subscription handlers
if message.type == MessageType.CONFIG_UPDATE: if message.type == MessageType.CONFIG_UPDATE:
self._handle_config_update(message) self._handle_config_update(message)
......
...@@ -11,40 +11,84 @@ class OverlayManager { ...@@ -11,40 +11,84 @@ class OverlayManager {
try { try {
// Wait for DOM to be fully loaded // Wait for DOM to be fully loaded
this.waitForFullInitialization(() => { this.waitForFullInitialization(() => {
// Check if Qt WebChannel is available // Check if Qt WebChannel transport is available (set by QWebEnginePage.setWebChannel)
if (typeof Qt === 'object' && typeof QtWebChannel === 'function') { if (typeof qt !== 'undefined' && qt.webChannelTransport) {
// Create channel and connect signals // Use the transport provided by Qt WebEngine
const channel = new QtWebChannel(); new QWebChannel(qt.webChannelTransport, (channel) => {
// Connect to overlay object
// Connect to overlay object window.overlay = channel.objects.overlay;
channel.connectTo('overlay', (overlay) => {
// Connect positionChanged signal // Flush any buffered console messages
overlay.positionChanged.connect((position, duration) => { if (window.flushConsoleBuffer) {
if (position !== null && duration !== null) { window.flushConsoleBuffer();
this.updateProgress(position, duration); }
} else {
console.warn('positionChanged signal received null/undefined parameters, skipping'); // Connect signals if overlay object exists
if (window.overlay) {
// Connect positionChanged signal
if (window.overlay.positionChanged) {
window.overlay.positionChanged.connect((position, duration) => {
if (position !== null && duration !== null) {
this.updateProgress(position, duration);
} else {
console.warn('positionChanged signal received null/undefined parameters, skipping');
}
});
} }
});
// Connect videoInfoChanged signal
// Connect videoInfoChanged signal if (window.overlay.videoInfoChanged) {
overlay.videoInfoChanged.connect((info) => { window.overlay.videoInfoChanged.connect((info) => {
if (info && typeof info === 'object') { if (info && typeof info === 'object') {
this.updateVideoInfo(info); this.updateVideoInfo(info);
} else { } else {
console.warn('videoInfoChanged signal received null/undefined parameter, skipping'); console.warn('videoInfoChanged signal received null/undefined parameter, skipping');
}
});
} }
});
// Process pending updates after full initialization
// Process pending updates after full initialization setTimeout(() => this.processPendingUpdates(), 100);
setTimeout(() => this.processPendingUpdates(), 100); console.log('WebChannel connected and ready');
} else {
console.warn('Overlay object not found in WebChannel');
}
}); });
console.log('WebChannel connected and ready');
} else { } else {
console.warn('QtWebChannel not available'); console.warn('Qt WebChannel transport not available, falling back to QtWebChannel constructor');
// Retry with exponential backoff // Fallback: try the old method
setTimeout(() => this.initWebChannel(), 1000); if (typeof Qt === 'object' && typeof QtWebChannel === 'function') {
const channel = new QtWebChannel();
channel.connectTo('overlay', (overlay) => {
window.overlay = overlay;
// Connect positionChanged signal
overlay.positionChanged.connect((position, duration) => {
if (position !== null && duration !== null) {
this.updateProgress(position, duration);
} else {
console.warn('positionChanged signal received null/undefined parameters, skipping');
}
});
// Connect videoInfoChanged signal
overlay.videoInfoChanged.connect((info) => {
if (info && typeof info === 'object') {
this.updateVideoInfo(info);
} else {
console.warn('videoInfoChanged signal received null/undefined parameter, skipping');
}
});
// Process pending updates after full initialization
setTimeout(() => this.processPendingUpdates(), 100);
console.log('WebChannel connected via fallback method');
});
} else {
console.warn('QtWebChannel not available either');
// Retry with exponential backoff
setTimeout(() => this.initWebChannel(), 1000);
}
} }
}); });
} catch (error) { } catch (error) {
......
...@@ -21,6 +21,7 @@ Werkzeug>=2.3.0 ...@@ -21,6 +21,7 @@ Werkzeug>=2.3.0
Jinja2>=3.1.0 Jinja2>=3.1.0
WTForms>=3.0.0 WTForms>=3.0.0
Flask-SocketIO>=5.3.0 Flask-SocketIO>=5.3.0
Flask-CORS>=4.0.0
# Configuration and environment # Configuration and environment
python-dotenv>=0.19.0 python-dotenv>=0.19.0
......
#!/usr/bin/env python3
"""
Test script to check API endpoints for debugging fixtures overlay
"""
import requests
import json
import sys
from pathlib import Path
def test_fixtures_api():
"""Test the fixtures API endpoint"""
print("Testing fixtures API endpoint...")
# Try different possible URLs
urls_to_test = [
"http://127.0.0.1:5001/api/fixtures",
"http://localhost:5001/api/fixtures",
"http://127.0.0.1:5000/api/fixtures",
"http://localhost:5000/api/fixtures"
]
for url in urls_to_test:
print(f"\nTrying URL: {url}")
try:
response = requests.get(url, timeout=5)
print(f"Status Code: {response.status_code}")
if response.status_code == 200:
try:
data = response.json()
print("Response data:")
print(json.dumps(data, indent=2))
if 'success' in data and data['success']:
print(f"✅ SUCCESS: Found {data.get('total', 0)} fixtures")
return True
else:
print("❌ API returned success=false")
return False
except json.JSONDecodeError as e:
print(f"❌ Invalid JSON response: {e}")
print(f"Raw response: {response.text[:500]}...")
return False
else:
print(f"❌ HTTP Error: {response.status_code}")
print(f"Response: {response.text[:200]}...")
except requests.exceptions.ConnectionError:
print("❌ Connection refused - server not running")
except requests.exceptions.Timeout:
print("❌ Request timed out")
except Exception as e:
print(f"❌ Error: {e}")
print("\n❌ All URLs failed - API server may not be running")
return False
def test_web_dashboard_status():
"""Test if web dashboard is running"""
print("\nTesting web dashboard status...")
urls_to_test = [
"http://127.0.0.1:5001/",
"http://localhost:5001/",
"http://127.0.0.1:5000/",
"http://localhost:5000/"
]
for url in urls_to_test:
print(f"Trying URL: {url}")
try:
response = requests.get(url, timeout=5)
print(f"Status Code: {response.status_code}")
if response.status_code == 200:
print("✅ Web dashboard is running")
return True
except Exception as e:
print(f"❌ Error: {e}")
print("❌ Web dashboard not accessible")
return False
def main():
print("=== MbetterClient API Test ===")
# Test web dashboard first
web_running = test_web_dashboard_status()
if not web_running:
print("\n⚠️ Web dashboard is not running. Make sure to start the web server first.")
print("Run: python main.py --web-only")
sys.exit(1)
# Test fixtures API
api_working = test_fixtures_api()
if api_working:
print("\n✅ API test completed successfully")
else:
print("\n❌ API test failed")
print("\nTroubleshooting tips:")
print("1. Make sure the web server is running")
print("2. Check if the database has fixture data")
print("3. Check the web server logs for errors")
print("4. Verify the fixtures endpoint is properly configured")
if __name__ == "__main__":
main()
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment