Fase 1 (isla 2): estado del servidor de la portada en React/TSX
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 <table> 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) <noreply@anthropic.com>
This commit is contained in:
@@ -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<ServerStatusData | null>(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 (
|
||||
<table className="max-center-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="yellow-info">
|
||||
{data?.server_name ?? '—'}{' '}
|
||||
<span className="blue-info small-font">{data?.expansion ?? ''}</span>
|
||||
</td>
|
||||
<td className="real-info-box green-info">{online}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Login</td>
|
||||
<td className={`real-info-box ${loginClass}`}>{status}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Hora</td>
|
||||
<td className="real-info-box">
|
||||
<span id="server-time">{clock}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colSpan={2}>
|
||||
<hr />
|
||||
</td>
|
||||
</tr>
|
||||
<tr className="centered">
|
||||
<td colSpan={2}>
|
||||
<p>
|
||||
<span>{data?.address ?? ''}</span>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
@@ -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')
|
||||
// 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(
|
||||
<StrictMode>
|
||||
<NewsList />
|
||||
</StrictMode>,
|
||||
)
|
||||
createRoot(el).render(<StrictMode>{node}</StrictMode>)
|
||||
}
|
||||
}
|
||||
|
||||
mount('home-news-app', <NewsList />)
|
||||
mount('home-status-app', <ServerStatus />)
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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'),
|
||||
]
|
||||
|
||||
@@ -8,29 +8,9 @@
|
||||
<div class="raf-index-holder">
|
||||
<div class="right-content" id="right-content-min">
|
||||
<div class="right-body">
|
||||
<table class="max-center-table">
|
||||
<tr>
|
||||
<td class="yellow-info">{{ server.name }} <span class="blue-info small-font">{{ expansion }}<span></td>
|
||||
<td class="real-info-box green-info">{{ online_characters }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Login</td>
|
||||
<td class="real-info-box {% if status == 'Online' %}green-info{% else %}red-info{% endif %}">{{ status }}</td>
|
||||
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Hora</td>
|
||||
<td class="real-info-box"><span id="server-time">{{ server_time }}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><hr /></td>
|
||||
</tr>
|
||||
<tr class="centered">
|
||||
<td colspan="2">
|
||||
<p><span>{{ server.address }}</span></p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- Isla React (Vite): tabla de estado del servidor,
|
||||
alimentada por /api/home/status/. Reloj en vivo en TSX. -->
|
||||
<div id="home-status-app"></div>
|
||||
</div>
|
||||
<div class="raf-index-responsive"><a href="recruit-a-friend">¡RECLUTA A UN AMIGO!</a></div>
|
||||
</div>
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user