Fase 3 (islas): historiales de seguridad y transacciones en React/TSX

Tabla genérica reutilizable HistoryTable (columnas por data-columns, filas por
json_script) + entry history_table:
- security_history: intentos de conexión (username/status/ip/fecha). Se elimina la
  caja "actividad" duplicada. Las vistas pasan login_attempts_data serializable.
- trans_history: historial de transacciones (StripeLog) con transactions_data.

Datos embebidos con json_script (sin endpoint extra). Verificado: check OK, ambas
plantillas renderizan la isla con datos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-12 21:52:20 +00:00
parent cefa30dabc
commit 230d401c75
7 changed files with 107 additions and 82 deletions
+7 -44
View File
@@ -11,53 +11,16 @@
</div>
</div>
<div class="box-content">
<div class="title-box-content"><h2>Historial de actividad</h2></div>
<div class="body-box-content centered">
<table class="max-center-table-aligned2">
<tbody>
{% if login_attempts %}
{% for attempt in login_attempts %}
<tr>
<td>{{ attempt.username }}</td> <!-- Agregado: Mostrar el nombre de usuario -->
<td>{{ attempt.timestamp|date:"d-m-Y H:i:s" }}</td>
<td>{{ attempt.status }}</td>
<td>{{ attempt.ip_address }}</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="4" class="centered"><span>No hay actividad</span></td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
<div class="box-content">
<div class="title-box-content"><h2>Historial de conexiones</h2></div>
<div class="body-box-content centered">
<table class="max-center-table-aligned">
<thead>
<tr>
<th>Usuario</th>
<th>Estado</th>
<th>IP</th>
<th>Fecha</th>
</tr>
</thead>
<tbody>
{% for attempt in login_attempts %}
<tr>
<td>{{ attempt.username }}</td>
<td>{{ attempt.status }}</td>
<td>{{ attempt.ip_address }}</td>
<td>{{ attempt.timestamp|date:"d-m-Y H:i:s" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% load django_vite %}
<!-- Isla React (Vite): tabla de intentos de conexión (últimos 20). -->
<div id="history-table-app"
data-columns='[{"key":"username","label":"Usuario"},{"key":"status","label":"Estado"},{"key":"ip_address","label":"IP"},{"key":"timestamp","label":"Fecha"}]'
data-empty-text="No hay actividad"></div>
{{ login_attempts_data|json_script:"history-table-data" }}
{% vite_asset 'src/entries/history_table.tsx' %}
</div>
</div>
</div>
+7 -36
View File
@@ -22,42 +22,13 @@
<h2>Historial de transacciones</h2>
</div>
<div class="body-box-content">
<table class="max-center-table-aligned">
<thead>
<tr>
<th>ID de Cuenta</th>
<th>Usuario</th> <!-- Columna para el nombre de la cuenta -->
<th>Personaje</th>
<th>IP</th>
<th>Email</th>
<th>Producto</th>
<th>Monto</th>
<th>Fecha</th>
<th>Modo</th>
</tr>
</thead>
<tbody>
{% if transactions %}
{% for transaction in transactions %}
<tr>
<td>{{ transaction.account_id }}</td> <!-- ID de la cuenta -->
<td>{{ transaction.username }}</td> <!-- Nombre de la cuenta -->
<td>{{ transaction.character_name }}</td>
<td>{{ transaction.acore_ip }}</td> <!-- IP de acore_auth -->
<td>{{ transaction.email }}</td> <!-- Email -->
<td>{{ transaction.product_name }}</td>
<td>{{ transaction.amount }} €</td>
<td>{{ transaction.timestamp|date:"d-m-Y H:i:s" }}</td>
<td>{{ transaction.mode }}</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="9"><span>No hay movimientos</span></td>
</tr>
{% endif %}
</tbody>
</table>
{% load django_vite %}
<!-- Isla React (Vite): tabla del historial de transacciones. -->
<div id="history-table-app"
data-columns='[{"key":"account_id","label":"ID de Cuenta"},{"key":"username","label":"Usuario"},{"key":"character_name","label":"Personaje"},{"key":"acore_ip","label":"IP"},{"key":"email","label":"Email"},{"key":"product_name","label":"Producto"},{"key":"amount","label":"Monto"},{"key":"timestamp","label":"Fecha"},{"key":"mode","label":"Modo"}]'
data-empty-text="No hay movimientos"></div>
{{ transactions_data|json_script:"history-table-data" }}
{% vite_asset 'src/entries/history_table.tsx' %}
</div>
</div>
+14 -1
View File
@@ -68,4 +68,17 @@ def security_history_view(request):
# Obtener los últimos 20 intentos de inicio de sesión
login_attempts = LoginAttempt.objects.filter(username=username).order_by('-timestamp')[:20]
return render(request, 'account/security_history.html', {'login_attempts': login_attempts})
login_attempts_data = [
{
'username': a.username,
'status': a.status,
'ip_address': a.ip_address,
'timestamp': a.timestamp.strftime('%d-%m-%Y %H:%M:%S') if a.timestamp else '',
}
for a in login_attempts
]
return render(request, 'account/security_history.html', {
'login_attempts': login_attempts,
'login_attempts_data': login_attempts_data,
})
+19 -1
View File
@@ -86,8 +86,26 @@ def trans_history_view(request):
else:
transactions = []
transactions_data = [
{
'account_id': t.account_id,
'username': t.username,
'character_name': t.character_name,
'acore_ip': t.acore_ip,
'email': t.email,
'product_name': t.product_name,
'amount': f"{t.amount}",
'timestamp': t.timestamp.strftime('%d-%m-%Y %H:%M:%S') if t.timestamp else '',
'mode': t.mode,
}
for t in transactions
]
# Renderizamos la vista con el historial de transacciones
return render(request, 'account/trans_history.html', {'transactions': transactions})
return render(request, 'account/trans_history.html', {
'transactions': transactions,
'transactions_data': transactions_data,
})
def trade_points_view(request):
"""
Vista para la página de intercambio de puntos.