${escapeHtml(sourceLabel)}
${escapeHtml(serverName)}
${players} / ${maxPlayers}
- Jugadores
- ${players} / ${maxPlayers}
- Mapa
- ${escapeHtml(currentMap)}
- Region
- ${escapeHtml(region)}
// Progressive enhancement for local frontend-backend checks. const RECENT_SNAPSHOT_WINDOW_MS = 30 * 60 * 1000; const DEFAULT_SERVER_POLL_INTERVAL_MS = 60 * 1000; document.addEventListener("DOMContentLoaded", () => { console.info("HLL Vietnam frontend ready"); const backendBaseUrl = document.body.dataset.backendBaseUrl || "http://127.0.0.1:8000"; const serverPollIntervalMs = getServerPollIntervalMs( document.body.dataset.serverRefreshMs, ); const statusNode = document.getElementById("backend-status"); const trailerFrame = document.getElementById("trailer-frame"); const trailerTitle = document.getElementById("trailer-title"); const serversTitle = document.getElementById("servers-title"); const serversNote = document.getElementById("servers-note"); const serversList = document.getElementById("servers-list"); const serversBadge = document.getElementById("servers-badge"); updateBackendStatus(statusNode, "Backend comprobando", "status-chip--idle"); setServersDataState(serversBadge, { kind: "fallback" }); let serverRefreshInFlight = false; const refreshServers = async () => { if (serverRefreshInFlight) { return; } serverRefreshInFlight = true; try { await hydrateServers( backendBaseUrl, serversTitle, serversNote, serversList, serversBadge, ); } finally { serverRefreshInFlight = false; } }; Promise.allSettled([ fetchHealth(backendBaseUrl, statusNode), hydrateTrailer(backendBaseUrl, trailerFrame, trailerTitle), refreshServers(), ]).catch((error) => { console.warn("Progressive enhancement failed", error); }); if (serverPollIntervalMs > 0) { window.setInterval(() => { void refreshServers(); }, serverPollIntervalMs); } }); async function fetchHealth(backendBaseUrl, statusNode) { try { const response = await fetch(`${backendBaseUrl}/health`); if (!response.ok) { throw new Error(`Health request failed with ${response.status}`); } const payload = await response.json(); if (payload.status === "ok") { updateBackendStatus(statusNode, "Backend operativo", "status-chip--ok"); return; } throw new Error("Unexpected health payload"); } catch (error) { console.warn("Backend health check unavailable", error); updateBackendStatus(statusNode, "Modo estatico activo", "status-chip--fallback"); } } async function hydrateTrailer(backendBaseUrl, trailerFrame, trailerTitle) { if (!trailerFrame || !trailerTitle) { return; } try { const response = await fetch(`${backendBaseUrl}/api/trailer`); if (!response.ok) { throw new Error(`Trailer request failed with ${response.status}`); } const payload = await response.json(); const trailer = payload.data; if (!trailer || !trailer.video_url || !trailer.title) { throw new Error("Trailer payload incomplete"); } trailerFrame.src = trailer.video_url; trailerFrame.title = trailer.title; trailerTitle.textContent = trailer.title; } catch (error) { console.warn("Trailer placeholder remains static", error); } } async function hydrateServers( backendBaseUrl, serversTitle, serversNote, serversList, serversBadge, ) { if (!serversTitle || !serversNote || !serversList || !serversBadge) { return; } try { const response = await fetch(`${backendBaseUrl}/api/servers`); if (!response.ok) { throw new Error(`Servers request failed with ${response.status}`); } const payload = await response.json(); const serversData = payload.data; if (!serversData || !Array.isArray(serversData.items)) { throw new Error("Servers payload incomplete"); } serversTitle.textContent = serversData.title || "Estado actual de servidores"; setServersDataState(serversBadge, { kind: "fallback" }); if (serversData.context === "current-hll-reference") { serversNote.textContent = "Referencia actual de servidores de Hell Let Loose."; } if (serversData.items.length === 0) { serversList.innerHTML = '
Informacion de servidores disponible mas adelante.
'; return; } serversList.innerHTML = serversData.items.map(renderServerCard).join(""); await hydrateServerStats( backendBaseUrl, serversTitle, serversNote, serversList, serversBadge, ); } catch (error) { console.warn("Servers panel remains on static fallback", error); } } async function hydrateServerStats( backendBaseUrl, serversTitle, serversNote, serversList, serversBadge, ) { try { const latestPayload = await fetchJson(`${backendBaseUrl}/api/servers/latest`); const latestItems = latestPayload?.data?.items; if (!Array.isArray(latestItems) || latestItems.length === 0) { return; } const visibleItems = selectPrimaryServerItems(latestItems); const latestState = deriveSnapshotState(visibleItems); const hasRealSnapshots = visibleItems.some(isRealA2SSnapshot); serversTitle.textContent = hasRealSnapshots ? "Servidores disponibles" : latestPayload.data.title || "Actividad reciente de servidores"; serversNote.textContent = hasRealSnapshots ? "Estado actual y acceso directo a los servidores disponibles." : "Ultimo estado disponible de servidores."; setServersDataState(serversBadge, latestState); serversList.innerHTML = renderServerSections(visibleItems); } catch (error) { console.warn("Historical server enrichment unavailable", error); } } function updateBackendStatus(statusNode, label, stateClass) { if (!statusNode) { return; } statusNode.textContent = label; statusNode.classList.remove("status-chip--ok", "status-chip--fallback"); if (stateClass) { statusNode.classList.add(stateClass); } } function setServersDataState(badgeNode, state) { if (!badgeNode) { return; } if (state.kind === "live") { badgeNode.textContent = "Datos en vivo"; badgeNode.classList.remove("status-chip--fallback"); badgeNode.classList.add("status-chip--ok"); return; } if (state.kind === "historical") { badgeNode.textContent = "Actividad reciente"; badgeNode.classList.remove("status-chip--fallback"); badgeNode.classList.add("status-chip--ok"); return; } badgeNode.textContent = "Referencia actual"; badgeNode.classList.remove("status-chip--ok"); badgeNode.classList.add("status-chip--fallback"); } function renderServerCard(server) { const serverName = server.server_name || "Servidor sin nombre"; const serverStatus = server.status === "online" ? "Online" : server.status === "offline" ? "Offline" : "Estado pendiente"; const stateClass = server.status === "online" ? "server-state--online" : "server-state--offline"; const currentMap = server.current_map || "Sin mapa disponible"; const region = server.region || "Region pendiente"; const players = Number.isFinite(server.players) ? server.players : 0; const maxPlayers = Number.isFinite(server.max_players) ? server.max_players : 0; const occupancy = getPopulationPercent(players, maxPlayers); const sourceLabel = isRealA2SSnapshot(server) ? "Snapshot activo" : "Referencia actual"; return `${escapeHtml(sourceLabel)}
${players} / ${maxPlayers}
${isRealSnapshot ? "Snapshot activo" : "Referencia actual"}
${escapeHtml(`${players} / ${maxPlayers}`)}
${connectAction}${escapeHtml(item.label)}
${escapeHtml(item.value)}