diff --git a/client/src/components/RoomsTable.vue b/client/src/components/RoomsTable.vue index 61d4aad..ce2c27c 100644 --- a/client/src/components/RoomsTable.vue +++ b/client/src/components/RoomsTable.vue @@ -110,6 +110,13 @@ G5 + + 🔄 Reiniciar + (); function getRoomDetails(roomId: string) { @@ -279,6 +287,19 @@ function getReadableTextColor(hex?: string): string { transform: translateY(-1px); } +.btn-restart { + background: #1976d2; + color: white; + padding: 6px 12px; + font-size: 12px; + margin-left: 4px; +} + +.btn-restart:hover { + background: #1565c0; + transform: translateY(-1px); +} + .variant-selector-container { display: inline-block; margin: 0 4px; @@ -528,4 +549,4 @@ function getReadableTextColor(hex?: string): string { max-width: 150px; } } - \ No newline at end of file + diff --git a/client/src/views/Dashboard.vue b/client/src/views/Dashboard.vue index 9980b81..e033606 100644 --- a/client/src/views/Dashboard.vue +++ b/client/src/views/Dashboard.vue @@ -160,6 +160,7 @@ @view-room-modal="openRoomModal" @close-room="closeRoom" @change-variant="changeRoomVariant" + @restart-room="restartRoom" /> diff --git a/server/src/adminApi.ts b/server/src/adminApi.ts index db0c512..22299d7 100644 --- a/server/src/adminApi.ts +++ b/server/src/adminApi.ts @@ -527,14 +527,17 @@ adminRouter.get("/admin/uuids-with-names", async (req: Request, res: Response) = // SSE endpoint for real-time dashboard updates adminRouter.get("/dashboard-stream", (req: Request, res: Response) => { - // Set SSE headers + // Set SSE headers (hardened for reverse proxies) res.writeHead(200, { 'Content-Type': 'text/event-stream', - 'Cache-Control': 'no-cache', + 'Cache-Control': 'no-cache, no-transform', // avoid proxy transformations 'Connection': 'keep-alive', 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'Cache-Control' + 'Access-Control-Allow-Headers': 'Cache-Control', + 'X-Accel-Buffering': 'no' // hint nginx to disable buffering }); + // Flush headers immediately so proxies establish the stream + try { (res as any).flushHeaders?.(); } catch {} // Add client to our set sseClients.add(res); diff --git a/server/src/rooms/GameRoom.ts b/server/src/rooms/GameRoom.ts index 1d57d4c..3e2c2ce 100644 --- a/server/src/rooms/GameRoom.ts +++ b/server/src/rooms/GameRoom.ts @@ -73,13 +73,9 @@ export class GameRoom extends Room { // Reset to round 1 and clear decisions when variant changes this.state.currentRound = 1; this.state.resetRound(); - // Reset game status if it was finished - if (this.state.gameStatus === GameStatus.FINISHED) { - this.state.gameStatus = GameStatus.PLAYING; - } - // Update metadata with new variant and round + // Update metadata with new variant and round (don't special-case FINISHED) this.setMetadata({ - gameStatus: this.state.gameStatus === GameStatus.WAITING ? 'waiting' : 'playing', + gameStatus: this.state.gameStatus === GameStatus.WAITING ? 'waiting' : (this.state.gameStatus === GameStatus.PAUSED ? 'paused' : (this.state.gameStatus === GameStatus.FINISHED ? 'finished' : 'playing')), currentRound: this.state.currentRound, currentVariant: this.state.currentVariant }); @@ -478,20 +474,6 @@ export class GameRoom extends Room { }); // Notify dashboard of game end broadcastDashboardUpdate(); - - // Clear UUID -> current room mapping and reconnection tokens for participants - try { - const { NameManager } = require("../utils/nameManager"); - const toClear: string[] = []; - this.state.players.forEach((p) => { - const u = (p as any)?.uuid; - if (u) toClear.push(u); - }); - toClear.forEach(u => { - NameManager.getInstance().clearCurrentRoom(u); - NameManager.getInstance().clearReconnectToken(u); - }); - } catch {} } private resolveP2Action() { @@ -583,12 +565,9 @@ export class GameRoom extends Room { this.state.currentRound = 1; this.state.resetRound(); - if (this.state.gameStatus === GameStatus.FINISHED) { - this.state.gameStatus = GameStatus.PLAYING; - } - + // Update metadata without altering FINISHED status this.setMetadata({ - gameStatus: this.state.gameStatus === GameStatus.WAITING ? 'waiting' : 'playing', + gameStatus: this.state.gameStatus === GameStatus.WAITING ? 'waiting' : (this.state.gameStatus === GameStatus.PAUSED ? 'paused' : (this.state.gameStatus === GameStatus.FINISHED ? 'finished' : 'playing')), currentRound: this.state.currentRound, currentVariant: this.state.currentVariant }); diff --git a/server/src/rooms/LobbyRoom.ts b/server/src/rooms/LobbyRoom.ts index dfa3b82..8dde7cc 100644 --- a/server/src/rooms/LobbyRoom.ts +++ b/server/src/rooms/LobbyRoom.ts @@ -80,8 +80,7 @@ export class LobbyRoom extends Room { if (currentRoomId) { const rooms = await matchMaker.query({ roomId: currentRoomId }); const room = rooms[0]; - const status = room?.metadata?.gameStatus || "waiting"; - if (room && status !== "finished") { + if (room) { const token = NameManager.getInstance().getReconnectToken(uuid); if (token) { client.send("resumeReconnection", { token }); } else { client.send("resumeGame", { roomId: currentRoomId }); } @@ -146,8 +145,7 @@ export class LobbyRoom extends Room { if (currentRoomId) { const rooms = await matchMaker.query({ roomId: currentRoomId }); const room = rooms[0]; - const status = room?.metadata?.gameStatus || "waiting"; - if (room && status !== "finished") { + if (room) { const token = NameManager.getInstance().getReconnectToken(options.uuid); if (token) { client.send("resumeReconnection", { token });