diff --git a/app/static/admin.html b/app/static/admin.html
index e884098..7b9721a 100644
--- a/app/static/admin.html
+++ b/app/static/admin.html
@@ -241,14 +241,16 @@ function stopChainStatusPolling() {
}
const VIEWS = ['parametri', 'utenti', 'round', 'pending', 'audit'];
+const VIEW_LOADERS = { utenti: loadUsers, round: loadRounds, pending: loadPending, audit: loadAuditLog };
+let currentAdminView = 'parametri';
function switchView(name) {
+ currentAdminView = name;
for (const key of VIEWS) {
document.getElementById('nav-' + key).classList.toggle('active', key === name);
document.getElementById('view-' + key).classList.toggle('active', key === name);
}
- const loaders = { utenti: loadUsers, round: loadRounds, pending: loadPending, audit: loadAuditLog };
- if (loaders[name]) loaders[name]();
+ if (VIEW_LOADERS[name]) VIEW_LOADERS[name]();
}
function showDashboard() {
@@ -521,6 +523,31 @@ window.addEventListener('pageshow', (event) => {
if (event.persisted) initAuthState();
});
+// Same server-push channel as app/static/index.html (see app/rounds/events.py):
+// a content-free "something changed" ping. Here it refreshes the chain-status
+// bar immediately, and reloads whichever admin section is currently open
+// (Utenti/Round/Transazioni pendenti/Audit log) so it doesn't need a manual
+// switch-away-and-back to pick up a new row. Polling stays in place as a
+// fallback if this connection is ever blocked or drops.
+let adminEventSource = null;
+
+function onAdminServerEvent() {
+ if (!adminToken) return;
+ refreshChainStatus();
+ if (VIEW_LOADERS[currentAdminView]) VIEW_LOADERS[currentAdminView]();
+}
+
+function connectAdminEvents() {
+ if (adminEventSource) return;
+ adminEventSource = new EventSource('/rounds/stream');
+ adminEventSource.addEventListener('update', onAdminServerEvent);
+ // Fires on the initial connection AND every successful auto-reconnect —
+ // re-syncs immediately instead of waiting for the next event or poll tick
+ // to notice whatever changed while this connection was down.
+ adminEventSource.addEventListener('open', onAdminServerEvent);
+}
+
+connectAdminEvents();
initAuthState();
diff --git a/app/static/index.html b/app/static/index.html
index 2d54dbf..c204974 100644
--- a/app/static/index.html
+++ b/app/static/index.html
@@ -682,8 +682,14 @@ async function showDashboard() {
// data.winner_user_id === myUserId — otherwise that comparison could race
// against an unset myUserId right after a reload.
await refreshMe();
+ // Awaited too, and before refreshRound(): on a brand-new browser/device that
+ // never saw this round live (nothing in localStorage), this is the only
+ // thing that knows the outcome once the round has fully closed. Resolving
+ // it first means refreshRound() finds the answer already in place instead
+ // of momentarily rendering "no result" and then flipping to the win/lose
+ // box a moment later once this backstop catches up.
+ await checkLastRoundResult();
refreshRound();
- checkLastRoundResult();
clearInterval(lastResultInterval);
lastResultInterval = setInterval(checkLastRoundResult, 20000);
clearInterval(roundTimerInterval);
@@ -906,6 +912,39 @@ async function withdraw() {
refreshMe();
}
+// Server push: an SSE channel that notifies the instant round/bet/balance
+// state changes anywhere (see app/rounds/events.py), instead of everyone
+// waiting for their next poll tick. The message carries no payload — it just
+// means "something changed", so we react by immediately re-running the same
+// refreshes the polling loop would eventually do on its own. Polling is left
+// completely in place as a fallback: if this connection is blocked/dropped
+// (proxy, browser setting, flaky network), the page keeps working exactly as
+// before, just without the instant nudge.
+let roundEventSource = null;
+
+function onRoundServerEvent() {
+ if (token) {
+ refreshRound();
+ refreshMe();
+ checkLastRoundResult();
+ } else {
+ refreshChainStatusOnly();
+ }
+}
+
+function connectRoundEvents() {
+ if (roundEventSource) return;
+ roundEventSource = new EventSource('/rounds/stream');
+ roundEventSource.addEventListener('update', onRoundServerEvent);
+ // Fires on the initial connection AND every successful auto-reconnect (the
+ // browser retries this on its own after a drop) — re-syncs immediately
+ // instead of leaving the page on whatever it last knew until the next event
+ // or poll tick, which would otherwise widen the "missed while disconnected"
+ // window to the full reconnect gap.
+ roundEventSource.addEventListener('open', onRoundServerEvent);
+}
+
+connectRoundEvents();
initAuthState();