Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
M
MBetterc
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Mbetter
MBetterc
Commits
7b742d32
Commit
7b742d32
authored
Nov 22, 2025
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Intro stage complete, fixture update management for failed downloads
completed
parent
816816c7
Changes
10
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
937 additions
and
495 deletions
+937
-495
client.py
mbetterclient/api_client/client.py
+361
-50
application.py
mbetterclient/core/application.py
+66
-1
games_thread.py
mbetterclient/core/games_thread.py
+217
-34
app.py
mbetterclient/web_dashboard/app.py
+99
-16
routes.py
mbetterclient/web_dashboard/routes.py
+40
-69
dashboard.js
mbetterclient/web_dashboard/static/js/dashboard.js
+154
-7
base.html
mbetterclient/web_dashboard/templates/base.html
+0
-312
bets.html
mbetterclient/web_dashboard/templates/dashboard/bets.html
+0
-2
cashier.html
mbetterclient/web_dashboard/templates/dashboard/cashier.html
+0
-2
new_bet.html
mbetterclient/web_dashboard/templates/dashboard/new_bet.html
+0
-2
No files found.
mbetterclient/api_client/client.py
View file @
7b742d32
This diff is collapsed.
Click to expand it.
mbetterclient/core/application.py
View file @
7b742d32
...
...
@@ -52,6 +52,7 @@ class MbetterClientApplication:
# Timer for automated game start
self
.
_game_start_timer
:
Optional
[
threading
.
Timer
]
=
None
self
.
_original_timer_interval
:
Optional
[
int
]
=
None
# Store original interval for retries
logger
.
info
(
"MbetterClient application initialized"
)
...
...
@@ -178,6 +179,7 @@ class MbetterClientApplication:
self
.
message_bus
.
subscribe
(
"core"
,
MessageType
.
SYSTEM_SHUTDOWN
,
self
.
_handle_shutdown_message
)
self
.
message_bus
.
subscribe
(
"core"
,
MessageType
.
CONFIG_UPDATE
,
self
.
_handle_config_update
)
self
.
message_bus
.
subscribe
(
"core"
,
MessageType
.
LOG_ENTRY
,
self
.
_handle_log_entry
)
self
.
message_bus
.
subscribe
(
"core"
,
MessageType
.
GAME_STATUS
,
self
.
_handle_game_status_response
)
logger
.
info
(
"Message bus initialized"
)
return
True
...
...
@@ -611,6 +613,8 @@ class MbetterClientApplication:
self
.
_handle_config_request
(
message
)
elif
message
.
type
==
MessageType
.
START_GAME
:
self
.
_handle_start_game_message
(
message
)
elif
message
.
type
==
MessageType
.
GAME_STATUS
:
self
.
_handle_game_status_response
(
message
)
elif
message
.
type
==
MessageType
.
SYSTEM_SHUTDOWN
:
self
.
_handle_shutdown_message
(
message
)
else
:
...
...
@@ -759,6 +763,41 @@ class MbetterClientApplication:
except
Exception
as
e
:
logger
.
error
(
f
"Failed to handle START_GAME message: {e}"
)
def
_handle_game_status_response
(
self
,
message
:
Message
):
"""Handle GAME_STATUS responses, particularly for timer-initiated START_GAME failures"""
try
:
status
=
message
.
data
.
get
(
"status"
,
"unknown"
)
sender
=
message
.
sender
# Only process responses that might be related to our timer-initiated START_GAME
# We check if we have an active timer that might need restarting
if
self
.
_game_start_timer
is
None
and
self
.
_original_timer_interval
is
None
:
# No active timer management needed
return
# Check if this is a failure response that should trigger timer restart
failure_statuses
=
[
"waiting_for_downloads"
,
"discarded"
,
"error"
,
"no_matches"
]
if
status
in
failure_statuses
:
logger
.
info
(
f
"START_GAME failed with status '{status}' from {sender} - restarting timer"
)
# Cancel any existing timer
self
.
_cancel_game_timer
()
# Restart timer with original interval
if
self
.
_original_timer_interval
is
not
None
:
logger
.
info
(
f
"Restarting game start timer with original interval: {self._original_timer_interval} minutes"
)
self
.
_start_game_timer_with_interval
(
self
.
_original_timer_interval
)
else
:
logger
.
warning
(
"No original timer interval available for restart"
)
elif
status
==
"started"
:
logger
.
info
(
f
"START_GAME succeeded with status '{status}' from {sender} - timer job completed"
)
# Game started successfully, clear timer state
self
.
_original_timer_interval
=
None
except
Exception
as
e
:
logger
.
error
(
f
"Failed to handle GAME_STATUS response: {e}"
)
def
_run_additional_tasks
(
self
):
"""Placeholder for additional periodic tasks"""
# TODO: Implement additional tasks as requested by user
...
...
@@ -770,6 +809,9 @@ class MbetterClientApplication:
if
self
.
_start_timer_minutes
is
None
:
return
# Store the original interval for potential retries
self
.
_original_timer_interval
=
self
.
_start_timer_minutes
# Special case: --start-timer 0 means 10 seconds delay for system initialization
if
self
.
_start_timer_minutes
==
0
:
delay_seconds
=
10
...
...
@@ -782,6 +824,27 @@ class MbetterClientApplication:
self
.
_game_start_timer
.
daemon
=
True
self
.
_game_start_timer
.
start
()
def
_start_game_timer_with_interval
(
self
,
minutes
:
int
):
"""Start the game timer with a specific interval (used for retries)"""
if
minutes
<
0
:
logger
.
error
(
f
"Invalid timer interval: {minutes} minutes"
)
return
# Update stored interval
self
.
_original_timer_interval
=
minutes
# Special case: timer 0 means 10 seconds delay for system initialization
if
minutes
==
0
:
delay_seconds
=
10
logger
.
info
(
f
"Restarting command line game timer: 0 minutes = 10 seconds delay for system initialization"
)
else
:
delay_seconds
=
minutes
*
60
logger
.
info
(
f
"Restarting command line game timer: {minutes} minutes ({delay_seconds} seconds)"
)
self
.
_game_start_timer
=
threading
.
Timer
(
delay_seconds
,
self
.
_on_game_timer_expired
)
self
.
_game_start_timer
.
daemon
=
True
self
.
_game_start_timer
.
start
()
def
_on_game_timer_expired
(
self
):
"""Called when the game start timer expires"""
logger
.
info
(
"Game start timer expired, sending START_GAME message"
)
...
...
@@ -804,6 +867,7 @@ class MbetterClientApplication:
logger
.
info
(
"Cancelling game start timer"
)
self
.
_game_start_timer
.
cancel
()
self
.
_game_start_timer
=
None
# Note: We keep _original_timer_interval for potential retries
def
_check_component_health
(
self
):
"""Check health of all components"""
...
...
@@ -841,8 +905,9 @@ class MbetterClientApplication:
self
.
running
=
False
self
.
shutdown_event
.
set
()
# Cancel game timer if running
# Cancel game timer if running
and clear timer state
self
.
_cancel_game_timer
()
self
.
_original_timer_interval
=
None
# Send shutdown message to all components
if
self
.
message_bus
:
...
...
mbetterclient/core/games_thread.py
View file @
7b742d32
This diff is collapsed.
Click to expand it.
mbetterclient/web_dashboard/app.py
View file @
7b742d32
...
...
@@ -5,7 +5,7 @@ Flask web dashboard application for MbetterClient
import
time
import
logging
from
pathlib
import
Path
from
typing
import
Optional
,
Dict
,
Any
from
typing
import
Optional
,
Dict
,
Any
,
List
from
flask
import
Flask
,
request
,
jsonify
,
render_template
,
redirect
,
url_for
,
session
,
g
from
flask_login
import
LoginManager
,
login_required
,
current_user
from
flask_jwt_extended
import
JWTManager
,
create_access_token
,
jwt_required
as
flask_jwt_required
...
...
@@ -58,6 +58,12 @@ class WebDashboard(ThreadedComponent):
"start_time"
:
None
}
# Client notification queue for long-polling clients
self
.
notification_queue
:
List
[
Dict
[
str
,
Any
]]
=
[]
self
.
notification_lock
=
threading
.
Lock
()
self
.
waiting_clients
:
List
[
threading
.
Event
]
=
[]
# Events for waiting long-poll clients
self
.
waiting_clients_lock
=
threading
.
Lock
()
# Register message queue
self
.
message_queue
=
self
.
message_bus
.
register_component
(
self
.
name
)
...
...
@@ -89,6 +95,10 @@ class WebDashboard(ThreadedComponent):
self
.
message_bus
.
subscribe
(
self
.
name
,
MessageType
.
CONFIG_UPDATE
,
self
.
_handle_config_update
)
self
.
message_bus
.
subscribe
(
self
.
name
,
MessageType
.
SYSTEM_STATUS
,
self
.
_handle_system_status
)
self
.
message_bus
.
subscribe
(
self
.
name
,
MessageType
.
CUSTOM
,
self
.
_handle_custom_message
)
# Subscribe to messages for client notifications
self
.
message_bus
.
subscribe
(
self
.
name
,
MessageType
.
START_GAME
,
self
.
_handle_client_notification
)
self
.
message_bus
.
subscribe
(
self
.
name
,
MessageType
.
MATCH_START
,
self
.
_handle_client_notification
)
self
.
message_bus
.
subscribe
(
self
.
name
,
MessageType
.
GAME_STATUS
,
self
.
_handle_client_notification
)
logger
.
info
(
"WebDashboard initialized successfully"
)
return
True
...
...
@@ -537,6 +547,7 @@ class WebDashboard(ThreadedComponent):
try
:
response
=
message
.
data
.
get
(
"response"
)
timer_update
=
message
.
data
.
get
(
"timer_update"
)
fixture_status_update
=
message
.
data
.
get
(
"fixture_status_update"
)
if
response
==
"timer_state"
:
# Update stored timer state
...
...
@@ -561,23 +572,95 @@ class WebDashboard(ThreadedComponent):
self
.
current_timer_state
.
update
(
timer_update
)
logger
.
debug
(
f
"Timer update received: {timer_update}"
)
# Broadcast timer update to connected clients via global message bus
# Add timer update to notification queue for long-polling clients
self
.
_add_client_notification
(
"TIMER_UPDATE"
,
timer_update
,
message
.
timestamp
)
elif
fixture_status_update
:
# Handle fixture status updates from games_thread
logger
.
debug
(
f
"Fixture status update received: {fixture_status_update}"
)
# Add fixture status update to notification queue for long-polling clients
self
.
_add_client_notification
(
"FIXTURE_STATUS_UPDATE"
,
fixture_status_update
,
message
.
timestamp
)
except
Exception
as
e
:
logger
.
error
(
f
"Failed to handle custom message: {e}"
)
def
_handle_client_notification
(
self
,
message
:
Message
):
"""Handle messages that should be sent to long-polling clients"""
try
:
timer_update_message
=
Message
(
type
=
MessageType
.
CUSTOM
,
sender
=
self
.
nam
e
,
data
=
{
"timer_update"
:
timer_update
,
"timestamp"
:
time
.
time
()
# Convert message to notification format
notification_data
=
{
"type"
:
message
.
type
.
valu
e
,
"data"
:
message
.
data
,
"timestamp"
:
message
.
timestamp
,
"sender"
:
message
.
sender
}
)
self
.
message_bus
.
publish
(
timer_update_message
,
broadcast
=
True
)
logger
.
debug
(
"Timer update broadcasted to clients"
)
except
Exception
as
broadcast_e
:
logger
.
error
(
f
"Failed to broadcast timer update: {broadcast_e}"
)
# Add to notification queue
self
.
_add_client_notification
(
message
.
type
.
value
,
message
.
data
,
message
.
timestamp
,
message
.
sender
)
except
Exception
as
e
:
logger
.
error
(
f
"Failed to handle custom message: {e}"
)
logger
.
error
(
f
"Failed to handle client notification: {e}"
)
def
_add_client_notification
(
self
,
notification_type
:
str
,
data
:
Dict
[
str
,
Any
],
timestamp
:
float
,
sender
:
str
=
None
):
"""Add notification to the client queue"""
try
:
with
self
.
notification_lock
:
notification
=
{
"type"
:
notification_type
,
"data"
:
data
,
"timestamp"
:
timestamp
}
if
sender
:
notification
[
"sender"
]
=
sender
self
.
notification_queue
.
append
(
notification
)
# Keep queue size reasonable (limit to last 100 notifications)
if
len
(
self
.
notification_queue
)
>
100
:
self
.
notification_queue
=
self
.
notification_queue
[
-
50
:]
logger
.
debug
(
f
"Added client notification: {notification_type}"
)
# Wake up all waiting clients
with
self
.
waiting_clients_lock
:
for
event
in
self
.
waiting_clients
:
event
.
set
()
self
.
waiting_clients
.
clear
()
except
Exception
as
e
:
logger
.
error
(
f
"Failed to add client notification: {e}"
)
def
get_pending_notifications
(
self
)
->
List
[
Dict
[
str
,
Any
]]:
"""Get the first pending notification for a client (one at a time)"""
try
:
with
self
.
notification_lock
:
if
self
.
notification_queue
:
# Return only the first notification and remove it from queue
notification
=
self
.
notification_queue
.
pop
(
0
)
return
[
notification
]
return
[]
except
Exception
as
e
:
logger
.
error
(
f
"Failed to get pending notifications: {e}"
)
return
[]
def
register_waiting_client
(
self
,
event
:
threading
.
Event
):
"""Register a waiting client Event to be notified when notifications arrive"""
try
:
with
self
.
waiting_clients_lock
:
self
.
waiting_clients
.
append
(
event
)
except
Exception
as
e
:
logger
.
error
(
f
"Failed to register waiting client: {e}"
)
def
unregister_waiting_client
(
self
,
event
:
threading
.
Event
):
"""Unregister a waiting client Event"""
try
:
with
self
.
waiting_clients_lock
:
if
event
in
self
.
waiting_clients
:
self
.
waiting_clients
.
remove
(
event
)
except
Exception
as
e
:
logger
.
error
(
f
"Failed to unregister waiting client: {e}"
)
def
get_app_context
(
self
):
"""Get Flask application context"""
...
...
mbetterclient/web_dashboard/routes.py
View file @
7b742d32
...
...
@@ -2600,89 +2600,60 @@ def notifications():
import
threading
import
ssl
import
socket
from
..core.message_bus
import
MessageType
# Get timeout from query parameter (default 30 seconds)
timeout
=
int
(
request
.
args
.
get
(
'timeout'
,
30
))
if
timeout
>
60
:
# Max 60 seconds
timeout
=
60
# Create a queue for this client
notification_queue
=
[]
# Create an event for timeout handling
notification_received
=
threading
.
Event
()
def
message_handler
(
message
):
"""Handle incoming messages for this client"""
if
message
.
type
in
[
MessageType
.
START_GAME
,
MessageType
.
GAME_STARTED
,
MessageType
.
MATCH_START
,
MessageType
.
GAME_STATUS
]:
notification_data
=
{
"type"
:
message
.
type
.
value
,
"data"
:
message
.
data
,
"timestamp"
:
message
.
timestamp
,
"sender"
:
message
.
sender
}
notification_queue
.
append
(
notification_data
)
notification_received
.
set
()
elif
message
.
type
==
MessageType
.
CUSTOM
and
"timer_update"
in
message
.
data
:
# Handle timer updates from match_timer component
notification_data
=
{
"type"
:
"TIMER_UPDATE"
,
"data"
:
message
.
data
[
"timer_update"
],
"timestamp"
:
message
.
timestamp
,
"sender"
:
message
.
sender
}
notification_queue
.
append
(
notification_data
)
notification_received
.
set
()
elif
message
.
type
==
MessageType
.
CUSTOM
and
"fixture_status_update"
in
message
.
data
:
# Handle fixture status updates from games_thread
notification_data
=
{
"type"
:
"FIXTURE_STATUS_UPDATE"
,
"data"
:
message
.
data
[
"fixture_status_update"
],
"timestamp"
:
message
.
timestamp
,
"sender"
:
message
.
sender
}
notification_queue
.
append
(
notification_data
)
notification_received
.
set
()
# Subscribe to relevant message types
if
api_bp
.
message_bus
:
api_bp
.
message_bus
.
subscribe_global
(
MessageType
.
START_GAME
,
message_handler
)
api_bp
.
message_bus
.
subscribe_global
(
MessageType
.
MATCH_START
,
message_handler
)
api_bp
.
message_bus
.
subscribe_global
(
MessageType
.
GAME_STATUS
,
message_handler
)
api_bp
.
message_bus
.
subscribe_global
(
MessageType
.
CUSTOM
,
message_handler
)
# Wait for notification or timeout
notification_received
.
wait
(
timeout
=
timeout
)
# Check for pending notifications from the web dashboard
if
hasattr
(
g
,
'main_app'
)
and
g
.
main_app
and
hasattr
(
g
.
main_app
,
'web_dashboard'
):
# Register this event with the web dashboard so it gets notified when notifications arrive
g
.
main_app
.
web_dashboard
.
register_waiting_client
(
notification_received
)
# Unsubscribe from messages safely
if
api_bp
.
message_bus
:
try
:
# Use proper unsubscribe methods instead of direct removal
for
msg_type
in
[
MessageType
.
START_GAME
,
MessageType
.
MATCH_START
,
MessageType
.
GAME_STATUS
,
MessageType
.
CUSTOM
]:
try
:
if
hasattr
(
api_bp
.
message_bus
,
'_global_handlers'
)
and
msg_type
in
api_bp
.
message_bus
.
_global_handlers
:
handlers
=
api_bp
.
message_bus
.
_global_handlers
[
msg_type
]
if
message_handler
in
handlers
:
handlers
.
remove
(
message_handler
)
except
(
AttributeError
,
KeyError
,
ValueError
)
as
e
:
logger
.
debug
(
f
"Handler cleanup warning for {msg_type}: {e}"
)
except
Exception
as
e
:
logger
.
warning
(
f
"Error during notification cleanup: {e}"
)
# Prepare response data
if
notification_queue
:
# Return the first notification received
notification
=
notification_queue
[
0
]
logger
.
debug
(
f
"Notification sent to client: {notification['type']}"
)
# First check for any pending notifications
pending_notifications
=
g
.
main_app
.
web_dashboard
.
get_pending_notifications
()
if
pending_notifications
:
# Return pending notifications immediately
logger
.
debug
(
f
"Returning {len(pending_notifications)} pending notifications to client"
)
response_data
=
{
"success"
:
True
,
"notification"
:
notification
"notifications"
:
pending_notifications
}
else
:
# No pending notifications, wait for new ones or timeout
logger
.
debug
(
f
"Waiting for notifications with {timeout}s timeout"
)
notification_received
.
wait
(
timeout
=
timeout
)
# After waiting, check again for any notifications that arrived
pending_notifications
=
g
.
main_app
.
web_dashboard
.
get_pending_notifications
()
if
pending_notifications
:
logger
.
debug
(
f
"Returning {len(pending_notifications)} notifications after wait"
)
response_data
=
{
"success"
:
True
,
"notifications"
:
pending_notifications
}
else
:
# Timeout - return empty response
logger
.
debug
(
"Notification wait timed out, returning empty response"
)
response_data
=
{
"success"
:
True
,
"notification"
:
None
"notifications"
:
[]
}
finally
:
# Always unregister the event when done
g
.
main_app
.
web_dashboard
.
unregister_waiting_client
(
notification_received
)
else
:
# Web dashboard not available, return empty response
response_data
=
{
"success"
:
True
,
"notifications"
:
[]
}
# Response data is already prepared above
# Handle SSL/connection errors gracefully when sending response
try
:
...
...
mbetterclient/web_dashboard/static/js/dashboard.js
View file @
7b742d32
...
...
@@ -10,9 +10,15 @@ window.Dashboard = (function() {
let
statusInterval
=
null
;
let
isOnline
=
navigator
.
onLine
;
let
cache
=
{};
let
initialized
=
false
;
// Prevent multiple initializations
// Initialize dashboard
function
init
(
userConfig
)
{
if
(
initialized
)
{
console
.
log
(
'Dashboard already initialized, skipping...'
);
return
;
}
config
=
Object
.
assign
({
statusUpdateInterval
:
5000
,
apiEndpoint
:
'/api'
,
...
...
@@ -36,6 +42,7 @@ window.Dashboard = (function() {
// Initialize match timer
initMatchTimer
();
initialized
=
true
;
console
.
log
(
'Dashboard initialized successfully'
);
}
...
...
@@ -463,9 +470,11 @@ window.Dashboard = (function() {
let
lastServerSync
=
0
;
let
cachedMatchInterval
=
null
;
// Cache the match interval configuration
const
SYNC_INTERVAL
=
30000
;
// Sync with server every 30 seconds
let
longPollingActive
=
false
;
// Flag to control long polling
let
longPollingController
=
null
;
// AbortController for cancelling requests
function
initMatchTimer
()
{
console
.
log
(
'Initializing server-only match timer
(no local countdown)
...'
);
console
.
log
(
'Initializing server-only match timer
with real-time notifications
...'
);
// Load match interval config once at initialization
loadMatchIntervalConfig
().
then
(
function
(
intervalSeconds
)
{
...
...
@@ -474,8 +483,8 @@ window.Dashboard = (function() {
// Initial sync with server
syncWithServerTimer
();
//
REMOVED: No periodic sync - rely on notification
s
// REMOVED: No local countdown - rely on server updates only
//
Start real-time notification polling for timer update
s
startNotificationPolling
();
}).
catch
(
function
(
error
)
{
console
.
error
(
'Failed to load match timer config at initialization:'
,
error
);
...
...
@@ -484,6 +493,9 @@ window.Dashboard = (function() {
// Initial sync with server
syncWithServerTimer
();
// Start real-time notification polling for timer updates
startNotificationPolling
();
});
}
...
...
@@ -568,6 +580,17 @@ window.Dashboard = (function() {
}
}
function
stopNotificationPolling
()
{
console
.
log
(
'Stopping long polling for notifications...'
);
longPollingActive
=
false
;
// Cancel any ongoing request
if
(
longPollingController
)
{
longPollingController
.
abort
();
longPollingController
=
null
;
}
}
function
updateMatchTimerDisplay
()
{
let
displaySeconds
=
serverTimerState
.
remaining_seconds
;
...
...
@@ -661,6 +684,130 @@ window.Dashboard = (function() {
});
}
function
startNotificationPolling
()
{
console
.
log
(
'Starting long polling for real-time notifications...'
);
if
(
longPollingActive
)
{
stopNotificationPolling
();
}
longPollingActive
=
true
;
performLongPoll
();
}
function
performLongPoll
()
{
if
(
!
longPollingActive
||
!
isOnline
)
{
return
;
}
// Create AbortController for this request
longPollingController
=
new
AbortController
();
const
signal
=
longPollingController
.
signal
;
// Make long polling request (server handles 30-second timeout)
const
url
=
config
.
apiEndpoint
+
'/notifications?_='
+
Date
.
now
()
+
Math
.
random
();
const
options
=
{
method
:
'GET'
,
headers
:
{
'Content-Type'
:
'application/json'
,
},
signal
:
signal
};
fetch
(
url
,
options
)
.
then
(
function
(
response
)
{
if
(
!
response
.
ok
)
{
throw
new
Error
(
'HTTP '
+
response
.
status
);
}
return
response
.
json
();
})
.
then
(
function
(
data
)
{
if
(
data
.
success
&&
data
.
notifications
&&
data
.
notifications
.
length
>
0
)
{
// Process each notification
data
.
notifications
.
forEach
(
function
(
notification
)
{
handleNotification
(
notification
);
});
}
// Immediately start the next long poll after processing notifications
if
(
longPollingActive
)
{
// Small delay to prevent overwhelming the server
setTimeout
(
performLongPoll
,
100
);
}
})
.
catch
(
function
(
error
)
{
if
(
error
.
name
===
'AbortError'
)
{
// Request was cancelled, don't restart
console
.
debug
(
'Long poll cancelled'
);
return
;
}
console
.
debug
(
'Long poll failed:'
,
error
.
message
);
// Retry after a delay if still active
if
(
longPollingActive
)
{
setTimeout
(
performLongPoll
,
2000
);
// Retry after 2 seconds on error
}
});
}
function
handleNotification
(
notification
)
{
console
.
log
(
'Received notification:'
,
notification
.
type
,
notification
);
if
(
notification
.
type
===
'TIMER_UPDATE'
&&
notification
.
data
)
{
// Update timer state from server notification
serverTimerState
=
{
running
:
notification
.
data
.
running
||
false
,
remaining_seconds
:
notification
.
data
.
remaining_seconds
||
0
,
total_seconds
:
notification
.
data
.
total_seconds
||
0
,
fixture_id
:
notification
.
data
.
fixture_id
||
null
,
match_id
:
notification
.
data
.
match_id
||
null
,
start_time
:
notification
.
data
.
start_time
||
null
};
// Update display immediately
updateMatchTimerDisplay
();
console
.
log
(
'Timer updated from notification:'
,
serverTimerState
);
}
else
if
(
notification
.
type
===
'START_GAME'
)
{
console
.
log
(
'Start game notification received'
);
onStartGameMessage
();
showNotification
(
'Games started - match timer is now running'
,
'success'
);
}
else
if
(
notification
.
type
===
'MATCH_START'
)
{
console
.
log
(
'Match started:'
,
notification
.
data
);
showNotification
(
'New match has started!'
,
'info'
);
}
else
if
(
notification
.
type
===
'GAME_STATUS'
)
{
console
.
log
(
'Game status update:'
,
notification
.
data
);
// Update system status if provided
if
(
notification
.
data
.
status
)
{
const
systemStatus
=
document
.
getElementById
(
'system-status'
);
if
(
systemStatus
)
{
systemStatus
.
textContent
=
notification
.
data
.
status
.
charAt
(
0
).
toUpperCase
()
+
notification
.
data
.
status
.
slice
(
1
);
systemStatus
.
className
=
'badge '
+
(
notification
.
data
.
status
===
'running'
?
'bg-success'
:
'bg-secondary'
);
}
}
}
else
if
(
notification
.
type
===
'FIXTURE_STATUS_UPDATE'
)
{
console
.
log
(
'Fixture status update:'
,
notification
.
data
);
// Trigger page refresh if on fixtures page
if
(
window
.
location
.
pathname
.
includes
(
'/fixtures'
))
{
// Refresh fixtures data if function exists
if
(
typeof
loadFixtures
===
'function'
)
{
loadFixtures
();
}
if
(
typeof
loadFixtureDetails
===
'function'
)
{
loadFixtureDetails
();
}
}
}
else
{
console
.
log
(
'Unknown notification type:'
,
notification
.
type
);
}
}
function
onStartGameMessage
()
{
console
.
log
(
'Received START_GAME message, initializing timer...'
);
...
...
@@ -675,7 +822,6 @@ window.Dashboard = (function() {
// Update display
updateMatchTimerDisplay
();
startLocalCountdown
();
showNotification
(
'Games started - match timer is now running'
,
'success'
);
});
...
...
@@ -731,7 +877,8 @@ window.Dashboard = (function() {
startMatchTimer
:
startMatchTimer
,
stopMatchTimer
:
stopMatchTimer
,
resetMatchTimer
:
resetMatchTimer
,
startNextMatch
:
startNextMatch
startNextMatch
:
startNextMatch
,
stopNotificationPolling
:
stopNotificationPolling
};
})();
...
...
mbetterclient/web_dashboard/templates/base.html
View file @
7b742d32
This diff is collapsed.
Click to expand it.
mbetterclient/web_dashboard/templates/dashboard/bets.html
View file @
7b742d32
...
...
@@ -1082,8 +1082,6 @@ function showNotification(message, type = 'info') {
},
3000
);
}
</script>
<!-- Include the main dashboard.js for timer functionality -->
<script
src=
"{{ url_for('static', filename='js/dashboard.js') }}"
></script>
<script>
// Initialize dashboard with timer functionality
document
.
addEventListener
(
'DOMContentLoaded'
,
function
()
{
...
...
mbetterclient/web_dashboard/templates/dashboard/cashier.html
View file @
7b742d32
...
...
@@ -738,8 +738,6 @@ function loadAvailableTemplates() {
});
}
</script>
<!-- Include the main dashboard.js for timer functionality -->
<script
src=
"{{ url_for('static', filename='js/dashboard.js') }}"
></script>
<script>
// Initialize dashboard with timer functionality
document
.
addEventListener
(
'DOMContentLoaded'
,
function
()
{
...
...
mbetterclient/web_dashboard/templates/dashboard/new_bet.html
View file @
7b742d32
...
...
@@ -927,8 +927,6 @@ function showNotification(message, type = 'info') {
},
3000
);
}
</script>
<!-- Include the main dashboard.js for timer functionality -->
<script
src=
"{{ url_for('static', filename='js/dashboard.js') }}"
></script>
<script>
// Initialize dashboard with timer functionality
document
.
addEventListener
(
'DOMContentLoaded'
,
function
()
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment