Implementar controles globales de admin en dashboard

- Agregados botones de control global: pausar, reanudar, reiniciar, cambiar variante, enviar al lobby
- Implementados endpoints API para operaciones masivas en todas las game rooms
- Agregado método executeAdminCommand en GameRoom para manejo unificado de comandos admin
- Mejorado manejo de desconexión automática al lobby cuando admin cierra rooms
- Interfaz responsiva con confirmaciones de usuario y estados de carga
- Sistema robusto de limpieza de rooms con fallback de forzado
This commit is contained in:
2025-08-12 18:13:53 -06:00
parent b754ec043a
commit e14bdddc62
4 changed files with 568 additions and 16 deletions

View File

@@ -221,18 +221,6 @@ export class GameRoom extends Room<GameState> {
// Removed nextRound handler - rounds now auto-advance
this.onMessage("admin:pause", () => {
this.state.pauseGame();
});
this.onMessage("admin:resume", () => {
this.state.resumeGame();
});
this.onMessage("admin:restart", () => {
this.handleRestart();
});
this.onMessage("admin:kick", (client, playerId: string) => {
this.handleKick(playerId);
});
@@ -480,6 +468,111 @@ export class GameRoom extends Room<GameState> {
}
}
private handleSetVariant(variant: string) {
console.log(`[GameRoom] Admin set variant to ${variant} in room ${this.roomId}`);
this.state.currentVariant = variant;
this.state.currentRound = 1;
this.state.resetRound();
if (this.state.gameStatus === GameStatus.FINISHED) {
this.state.gameStatus = GameStatus.PLAYING;
}
this.setMetadata({
gameStatus: this.state.gameStatus === GameStatus.WAITING ? 'waiting' : 'playing',
currentRound: this.state.currentRound,
currentVariant: this.state.currentVariant
});
if (variant === 'G2') {
this.state.forcedByP2 = true;
}
this.broadcast("variantChanged", { variant });
this.sysChat(`🔄 Admin cambió variante a ${variant}`, 'admin_variant_change');
broadcastDashboardUpdate();
}
private handleSendToLobby() {
console.log(`[GameRoom] Admin send all players to lobby from room ${this.roomId}`);
this.sysChat('👋 Admin envía a todos al lobby', 'admin_send_lobby');
// Give players a moment to see the message
setTimeout(() => {
// Disconnect all clients, which will send them back to lobby
this.clients.forEach(client => {
try {
client.leave(1000);
} catch (error) {
console.error(`Failed to disconnect client ${client.sessionId}:`, error);
}
});
// Dispose the room
setTimeout(() => {
this.disconnect();
}, 500);
}, 1000);
}
// Public method for admin API calls
executeAdminCommand(command: string, ...args: any[]) {
console.log(`[GameRoom] Executing admin command: ${command} with args:`, args);
switch (command) {
case 'pause':
this.state.pauseGame();
this.broadcast("gamePaused");
this.setMetadata({
gameStatus: 'paused',
currentRound: this.state.currentRound,
currentVariant: this.state.currentVariant
});
this.sysChat('⏸️ Admin pausó el juego', 'admin_pause');
broadcastDashboardUpdate();
break;
case 'resume':
this.state.resumeGame();
this.setMetadata({
gameStatus: 'playing',
currentRound: this.state.currentRound,
currentVariant: this.state.currentVariant
});
this.sysChat('▶️ Admin reanudó el juego', 'admin_resume');
broadcastDashboardUpdate();
break;
case 'restart':
this.handleRestart();
break;
case 'setVariant':
const variant = args[0];
if (variant) {
this.handleSetVariant(variant);
}
break;
case 'sendToLobby':
this.handleSendToLobby();
break;
case 'kick':
const playerId = args[0];
if (playerId) {
this.handleKick(playerId);
}
break;
default:
console.warn(`[GameRoom] Unknown admin command: ${command}`);
}
}
private getConnectedPlayersCount(): number {
let count = 0;
this.state.players.forEach(player => {