colores asignados al azar y simulador de jugadores en el dashboard
This commit is contained in:
@@ -331,6 +331,17 @@ class ColyseusService {
|
|||||||
return room;
|
return room;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fetchAllowedUuids(): Promise<string[]> {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${this.apiBase}/admin/uuids`);
|
||||||
|
const data = await res.json();
|
||||||
|
return Array.isArray(data?.uuids) ? data.uuids : [];
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to fetch allowed UUIDs', e);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private getUuidFromPath(): string {
|
private getUuidFromPath(): string {
|
||||||
if (typeof window === 'undefined') return '';
|
if (typeof window === 'undefined') return '';
|
||||||
const path = window.location.pathname.replace(/^\/+/, '');
|
const path = window.location.pathname.replace(/^\/+/, '');
|
||||||
|
|||||||
@@ -25,6 +25,29 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dashboard-content">
|
<div class="dashboard-content">
|
||||||
|
<!-- Open Tabs Section -->
|
||||||
|
<div class="open-tabs-section">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>🧪 Abrir Pestañas de Jugadores</h2>
|
||||||
|
</div>
|
||||||
|
<div class="open-tabs-controls">
|
||||||
|
<label>Cantidad:</label>
|
||||||
|
<select v-model="openCount" class="select">
|
||||||
|
<option :value="1">1 (específico)</option>
|
||||||
|
<option :value="2">2</option>
|
||||||
|
<option :value="6">6</option>
|
||||||
|
<option :value="10">10</option>
|
||||||
|
</select>
|
||||||
|
<template v-if="openCount === 1">
|
||||||
|
<label>UUID:</label>
|
||||||
|
<select v-model="selectedUuid" class="select">
|
||||||
|
<option v-for="u in allowedUuids" :key="u" :value="u">{{ u }}</option>
|
||||||
|
</select>
|
||||||
|
</template>
|
||||||
|
<button class="btn btn-open" @click="openTabs">Abrir pestañas</button>
|
||||||
|
<span class="hint">Abre /{uuid} en nuevas pestañas.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<!-- Global Controls Section -->
|
<!-- Global Controls Section -->
|
||||||
<div class="global-controls-section">
|
<div class="global-controls-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -213,13 +236,26 @@ const maxReconnectAttempts = 5;
|
|||||||
const selectedGlobalVariant = ref('');
|
const selectedGlobalVariant = ref('');
|
||||||
const isLoadingGlobal = ref(false);
|
const isLoadingGlobal = ref(false);
|
||||||
|
|
||||||
|
// Open tabs UI state
|
||||||
|
const allowedUuids = ref<string[]>([]);
|
||||||
|
const openCount = ref<1|2|6|10>(1);
|
||||||
|
const selectedUuid = ref('');
|
||||||
|
|
||||||
const gameRooms = computed(() => rooms.value.filter(r => r.name === 'game'));
|
const gameRooms = computed(() => rooms.value.filter(r => r.name === 'game'));
|
||||||
const lobbyRooms = computed(() => rooms.value.filter(r => r.name === 'lobby'));
|
const lobbyRooms = computed(() => rooms.value.filter(r => r.name === 'lobby'));
|
||||||
const totalPlayers = computed(() => rooms.value.reduce((sum, room) => sum + room.clients, 0));
|
const totalPlayers = computed(() => rooms.value.reduce((sum, room) => sum + room.clients, 0));
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
// Try SSE first, fallback to polling if it fails
|
// Try SSE first, fallback to polling if it fails
|
||||||
initSSE();
|
initSSE();
|
||||||
|
// Load allowed UUIDs
|
||||||
|
try {
|
||||||
|
const list = await colyseusService.fetchAllowedUuids();
|
||||||
|
allowedUuids.value = list || [];
|
||||||
|
if (!selectedUuid.value && allowedUuids.value.length > 0) {
|
||||||
|
selectedUuid.value = allowedUuids.value[0];
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
@@ -302,6 +338,31 @@ function goToLobby() {
|
|||||||
router.push('/');
|
router.push('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openTabs() {
|
||||||
|
const base = window.location.origin;
|
||||||
|
const openOne = (uuid: string) => {
|
||||||
|
const url = `${base}/${uuid}`;
|
||||||
|
window.open(url, '_blank');
|
||||||
|
};
|
||||||
|
|
||||||
|
if (openCount.value === 1) {
|
||||||
|
if (!selectedUuid.value) return;
|
||||||
|
openOne(selectedUuid.value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Randomly select N distinct UUIDs
|
||||||
|
const N = openCount.value as number;
|
||||||
|
const pool = [...allowedUuids.value];
|
||||||
|
if (pool.length === 0) return;
|
||||||
|
for (let i = pool.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
[pool[i], pool[j]] = [pool[j], pool[i]];
|
||||||
|
}
|
||||||
|
const pick = pool.slice(0, Math.min(N, pool.length));
|
||||||
|
pick.forEach(u => openOne(u));
|
||||||
|
}
|
||||||
|
|
||||||
// Global control functions
|
// Global control functions
|
||||||
async function pauseAllGames() {
|
async function pauseAllGames() {
|
||||||
if (!confirm('Are you sure you want to pause ALL active games?')) return;
|
if (!confirm('Are you sure you want to pause ALL active games?')) return;
|
||||||
@@ -649,6 +710,13 @@ const selectedRoom = computed(() => {
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.open-tabs-section { margin: 16px 0 24px; padding: 12px; background:#f8f9fa; border-radius: 10px; color:#333; }
|
||||||
|
.open-tabs-controls { display:flex; flex-wrap:wrap; gap:10px; align-items:center; }
|
||||||
|
.open-tabs-controls .select { padding:6px 10px; border-radius:6px; border:1px solid #ddd; }
|
||||||
|
.btn-open { background:#2196f3; color:white; padding: 8px 12px; border:none; border-radius:8px; cursor:pointer; }
|
||||||
|
.btn-open:hover { background:#1976d2; }
|
||||||
|
.hint { color:#666; font-size:12px; }
|
||||||
|
|
||||||
.global-controls-section {
|
.global-controls-section {
|
||||||
margin-bottom: 40px;
|
margin-bottom: 40px;
|
||||||
}
|
}
|
||||||
@@ -860,4 +928,4 @@ const selectedRoom = computed(() => {
|
|||||||
.btn-lobby:hover {
|
.btn-lobby:hover {
|
||||||
background: rgba(255, 255, 255, 0.3);
|
background: rgba(255, 255, 255, 0.3);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Request, Response, Router } from "express";
|
|||||||
import { matchMaker } from "colyseus";
|
import { matchMaker } from "colyseus";
|
||||||
import { GameRoom } from "./rooms/GameRoom";
|
import { GameRoom } from "./rooms/GameRoom";
|
||||||
import { NameManager } from "./utils/nameManager";
|
import { NameManager } from "./utils/nameManager";
|
||||||
|
import { getAllowedUuidCount, listAllowedUuids } from "./utils/uuidRegistry";
|
||||||
|
|
||||||
// SSE connections storage
|
// SSE connections storage
|
||||||
const sseClients = new Set<Response>();
|
const sseClients = new Set<Response>();
|
||||||
@@ -432,6 +433,17 @@ adminRouter.post("/admin/shuffle-players", async (req: Request, res: Response) =
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// UUID allowlist endpoint
|
||||||
|
adminRouter.get("/admin/uuids", async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const uuids = listAllowedUuids();
|
||||||
|
res.json({ count: getAllowedUuidCount(), uuids });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[AdminAPI] Error fetching UUIDs:", error);
|
||||||
|
res.status(500).json({ error: "Failed to fetch UUIDs" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// SSE endpoint for real-time dashboard updates
|
// SSE endpoint for real-time dashboard updates
|
||||||
adminRouter.get("/dashboard-stream", (req: Request, res: Response) => {
|
adminRouter.get("/dashboard-stream", (req: Request, res: Response) => {
|
||||||
// Set SSE headers
|
// Set SSE headers
|
||||||
@@ -558,4 +570,4 @@ function broadcastDashboardUpdate() {
|
|||||||
sendDashboardUpdate();
|
sendDashboardUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
export { adminRouter, broadcastDashboardUpdate };
|
export { adminRouter, broadcastDashboardUpdate };
|
||||||
|
|||||||
@@ -6,6 +6,23 @@ import { isUuidAllowed } from "../utils/uuidRegistry";
|
|||||||
export class LobbyRoom extends Room<LobbyState> {
|
export class LobbyRoom extends Room<LobbyState> {
|
||||||
private updateInterval?: NodeJS.Timeout;
|
private updateInterval?: NodeJS.Timeout;
|
||||||
private sessionToUuid: Map<string, string> = new Map();
|
private sessionToUuid: Map<string, string> = new Map();
|
||||||
|
|
||||||
|
// Generate a random dark-ish color (for white backgrounds)
|
||||||
|
private randomDarkHex(): string {
|
||||||
|
const h = Math.floor(Math.random() * 360);
|
||||||
|
const s = 65 + Math.floor(Math.random() * 20); // 65% - 85%
|
||||||
|
const l = 30 + Math.floor(Math.random() * 10); // 30% - 40%
|
||||||
|
return this.hslToHex(h, s, l);
|
||||||
|
}
|
||||||
|
|
||||||
|
private hslToHex(h: number, s: number, l: number): string {
|
||||||
|
s /= 100; l /= 100;
|
||||||
|
const k = (n: number) => (n + h / 30) % 12;
|
||||||
|
const a = s * Math.min(l, 1 - l);
|
||||||
|
const f = (n: number) => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
||||||
|
const toHex = (x: number) => Math.round(255 * x).toString(16).padStart(2, '0');
|
||||||
|
return `#${toHex(f(0))}${toHex(f(8))}${toHex(f(4))}`;
|
||||||
|
}
|
||||||
|
|
||||||
onCreate(options: any) {
|
onCreate(options: any) {
|
||||||
this.setState(new LobbyState());
|
this.setState(new LobbyState());
|
||||||
@@ -80,10 +97,14 @@ export class LobbyRoom extends Room<LobbyState> {
|
|||||||
// Add player temporarily to lobby state
|
// Add player temporarily to lobby state
|
||||||
const existingName = NameManager.getInstance().getPlayerName(options.uuid);
|
const existingName = NameManager.getInstance().getPlayerName(options.uuid);
|
||||||
this.state.addPlayer(client.sessionId, existingName || "");
|
this.state.addPlayer(client.sessionId, existingName || "");
|
||||||
const existingColor = NameManager.getInstance().getPlayerColor(options.uuid);
|
const p = this.state.players.get(client.sessionId);
|
||||||
if (existingColor) {
|
if (p) {
|
||||||
const p = this.state.players.get(client.sessionId);
|
let color = NameManager.getInstance().getPlayerColor(options.uuid);
|
||||||
if (p) p.color = existingColor;
|
if (!color) {
|
||||||
|
color = this.randomDarkHex();
|
||||||
|
NameManager.getInstance().setPlayerColor(options.uuid, color);
|
||||||
|
}
|
||||||
|
p.color = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send welcome first
|
// Send welcome first
|
||||||
@@ -106,10 +127,14 @@ export class LobbyRoom extends Room<LobbyState> {
|
|||||||
// Check if this UUID already has a name
|
// Check if this UUID already has a name
|
||||||
const existingName = NameManager.getInstance().getPlayerName(options.uuid);
|
const existingName = NameManager.getInstance().getPlayerName(options.uuid);
|
||||||
this.state.addPlayer(client.sessionId, existingName || "");
|
this.state.addPlayer(client.sessionId, existingName || "");
|
||||||
const existingColor = NameManager.getInstance().getPlayerColor(options.uuid);
|
const p = this.state.players.get(client.sessionId);
|
||||||
if (existingColor) {
|
if (p) {
|
||||||
const p = this.state.players.get(client.sessionId);
|
let color = NameManager.getInstance().getPlayerColor(options.uuid);
|
||||||
if (p) p.color = existingColor;
|
if (!color) {
|
||||||
|
color = this.randomDarkHex();
|
||||||
|
NameManager.getInstance().setPlayerColor(options.uuid, color);
|
||||||
|
}
|
||||||
|
p.color = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
client.send("welcome", {
|
client.send("welcome", {
|
||||||
|
|||||||
@@ -62,3 +62,7 @@ export function isUuidAllowed(uuid: string | undefined | null): boolean {
|
|||||||
export function getAllowedUuidCount(): number {
|
export function getAllowedUuidCount(): number {
|
||||||
return allowedUuids.size;
|
return allowedUuids.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function listAllowedUuids(): string[] {
|
||||||
|
return Array.from(allowedUuids.values());
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user