From d612b7916c98af2fdcc0eb18bcf75cbc8331f6a3 Mon Sep 17 00:00:00 2001 From: adevopg Date: Sun, 12 Jul 2026 20:22:32 +0000 Subject: [PATCH] Fase 1 (isla 2): estado del servidor de la portada en React/TSX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Segunda isla de la home: la tabla de estado (nombre, online, login, hora, dirección) pasa de plantilla Django a componente React. - API: home_status_api -> /api/home/status/ (server_name, address, expansion, online_characters, status). Reutiliza check_server_status y get_online_characters_count de views/pages. - React: ServerStatus.tsx (fetch + reloj en vivo con setInterval, mismas clases CSS). El entry home.tsx ahora monta dos islas: #home-news-app y #home-status-app. - partials/noticias.html: la de estado se sustituye por el contenedor. Verificado: check OK, ambos endpoints 200, la home monta las dos islas. Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/src/components/ServerStatus.tsx | 74 ++++++++++++++++++++++++ frontend/src/entries/home.tsx | 20 ++++--- frontend/src/types.ts | 8 +++ home/api_urls.py | 1 + home/templates/partials/noticias.html | 26 +-------- home/views/api.py | 18 ++++++ 6 files changed, 115 insertions(+), 32 deletions(-) create mode 100644 frontend/src/components/ServerStatus.tsx diff --git a/frontend/src/components/ServerStatus.tsx b/frontend/src/components/ServerStatus.tsx new file mode 100644 index 0000000..0bee0f0 --- /dev/null +++ b/frontend/src/components/ServerStatus.tsx @@ -0,0 +1,74 @@ +import { useEffect, useState } from 'react' +import type { ServerStatusData } from '../types' + +function useClock(): string { + const [time, setTime] = useState(() => new Date().toLocaleTimeString('es-ES')) + useEffect(() => { + const id = setInterval(() => setTime(new Date().toLocaleTimeString('es-ES')), 1000) + return () => clearInterval(id) + }, []) + return time +} + +export function ServerStatus() { + const [data, setData] = useState(null) + const clock = useClock() + + useEffect(() => { + let cancelled = false + fetch('/api/home/status/', { headers: { Accept: 'application/json' } }) + .then((r) => { + if (!r.ok) throw new Error(`HTTP ${r.status}`) + return r.json() + }) + .then((d: ServerStatusData) => { + if (!cancelled) setData(d) + }) + .catch(() => { + if (!cancelled) setData(null) + }) + return () => { + cancelled = true + } + }, []) + + const online = data ? data.online_characters : 0 + const status = data ? data.status : 'Offline' + const loginClass = status === 'Online' ? 'green-info' : 'red-info' + + return ( +
+ + + + + + + + + + + + + + + + + + + + +
+ {data?.server_name ?? '—'}{' '} + {data?.expansion ?? ''} + {online}
Login{status}
Hora + {clock} +
+
+
+

+ {data?.address ?? ''} +

+
+ ) +} diff --git a/frontend/src/entries/home.tsx b/frontend/src/entries/home.tsx index 6624c09..f5340cb 100644 --- a/frontend/src/entries/home.tsx +++ b/frontend/src/entries/home.tsx @@ -1,14 +1,16 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import { NewsList } from '../components/NewsList' +import { ServerStatus } from '../components/ServerStatus' -// Isla de la portada: monta la lista de noticias (React) donde Django deja -// el contenedor #home-news-app. El resto de la página sigue siendo Django. -const el = document.getElementById('home-news-app') -if (el) { - createRoot(el).render( - - - , - ) +// Islas de la portada: cada componente React se monta donde Django deja su +// contenedor. El resto de la página sigue siendo Django. +function mount(id: string, node: React.ReactElement) { + const el = document.getElementById(id) + if (el) { + createRoot(el).render({node}) + } } + +mount('home-news-app', ) +mount('home-status-app', ) diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 928f0ea..1334904 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -5,3 +5,11 @@ export interface Noticia { contenido: string enlace: string | null } + +export interface ServerStatusData { + server_name: string | null + address: string | null + expansion: string + online_characters: number + status: 'Online' | 'Offline' +} diff --git a/home/api_urls.py b/home/api_urls.py index 05f6c0a..ff8e5b3 100644 --- a/home/api_urls.py +++ b/home/api_urls.py @@ -4,4 +4,5 @@ from . import views urlpatterns = [ path('home/news/', views.home_news_api, name='api_home_news'), + path('home/status/', views.home_status_api, name='api_home_status'), ] diff --git a/home/templates/partials/noticias.html b/home/templates/partials/noticias.html index 2d16e4e..f6d8ea2 100644 --- a/home/templates/partials/noticias.html +++ b/home/templates/partials/noticias.html @@ -8,29 +8,9 @@
- - - - - - - - - - - - - - - - - - - - -
{{ server.name }} {{ expansion }}{{ online_characters }}
Login{{ status }}
Hora{{ server_time }}

-

{{ server.address }}

-
+ +
diff --git a/home/views/api.py b/home/views/api.py index 148f0d0..25d7d77 100644 --- a/home/views/api.py +++ b/home/views/api.py @@ -1,4 +1,5 @@ from ._base import * +from .pages import check_server_status, get_online_characters_count # Endpoints JSON que alimentan las islas React/TSX del frontend (Vite). # Se montan bajo /api/ (fuera de i18n_patterns). De momento con JsonResponse; @@ -19,3 +20,20 @@ def home_news_api(request): for n in noticias ] return JsonResponse({"noticias": data}) + + +def home_status_api(request): + """Estado del servidor para la isla de la portada (nombre, online, login, dirección).""" + server = ServerSelection.objects.first() + online = get_online_characters_count() + status = "Online" if (server and check_server_status(server.address, server.port)) else "Offline" + expansion = "WotLK" if (server and server.gamebuild == 12340) else "Cataclysm" + return JsonResponse( + { + "server_name": server.name if server else None, + "address": server.address if server else None, + "expansion": expansion, + "online_characters": online, + "status": status, + } + )