- Tabla activity_log en SQLite (migración automática) - Registra: login, login_fallido, rda_enviado, rda_masivo, rda_directo, automation_run, contrato_creado, contrato_editado, contrato_toggle - Endpoint GET /logs/actividad con filtros por usuario, acción y fecha - UI /logs/actividad con badges de color por tipo de acción - Enlace "Actividad" en navegación lateral Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
146 lines
8.1 KiB
HTML
146 lines
8.1 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}Actividad{% endblock %}
|
||
{% block header %}Registro de Actividad{% endblock %}
|
||
{% block content %}
|
||
|
||
<!-- Tabs -->
|
||
<div class="flex gap-2 mb-5">
|
||
<a href="/logs" class="px-4 py-2 rounded-lg text-sm font-medium bg-white border border-gray-200 text-gray-600 hover:bg-gray-50">
|
||
<i class="fas fa-paper-plane mr-1"></i> Envíos TNS
|
||
</a>
|
||
<span class="px-4 py-2 rounded-lg text-sm font-medium bg-blue-600 text-white">
|
||
<i class="fas fa-user-clock mr-1"></i> Actividad usuarios
|
||
</span>
|
||
</div>
|
||
|
||
<!-- Filtros -->
|
||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 mb-5">
|
||
<form method="get" action="/logs/actividad" class="p-4">
|
||
<input type="hidden" name="offset" value="0">
|
||
<div class="flex flex-wrap items-end gap-3">
|
||
<div>
|
||
<label class="block text-xs font-medium text-gray-500 mb-1">Usuario</label>
|
||
<select name="username" class="px-3 py-1.5 border border-gray-300 rounded-lg text-sm min-w-[130px]">
|
||
<option value="">Todos</option>
|
||
{% for u in usuarios %}
|
||
<option value="{{ u }}" {{ 'selected' if filtro_username == u }}>{{ u }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-gray-500 mb-1">Acción</label>
|
||
<select name="accion" class="px-3 py-1.5 border border-gray-300 rounded-lg text-sm min-w-[150px]">
|
||
<option value="">Todas</option>
|
||
{% for a in acciones %}
|
||
<option value="{{ a }}" {{ 'selected' if filtro_accion == a }}>{{ a }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-gray-500 mb-1">Desde</label>
|
||
<input type="date" name="fecha_desde" value="{{ filtro_fecha_desde }}"
|
||
class="px-3 py-1.5 border border-gray-300 rounded-lg text-sm">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-gray-500 mb-1">Hasta</label>
|
||
<input type="date" name="fecha_hasta" value="{{ filtro_fecha_hasta }}"
|
||
class="px-3 py-1.5 border border-gray-300 rounded-lg text-sm">
|
||
</div>
|
||
<div class="flex gap-2">
|
||
<button type="submit" class="px-4 py-1.5 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700">
|
||
<i class="fas fa-search mr-1"></i> Filtrar
|
||
</button>
|
||
<a href="/logs/actividad" class="px-3 py-1.5 bg-gray-100 text-gray-600 rounded-lg text-sm hover:bg-gray-200" title="Limpiar">
|
||
<i class="fas fa-times"></i>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<!-- Tabla -->
|
||
<div class="bg-white rounded-xl shadow-sm border border-gray-200">
|
||
<div class="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
|
||
<h3 class="font-semibold text-gray-800"><i class="fas fa-list mr-2 text-gray-400"></i>Eventos</h3>
|
||
<span class="text-xs text-gray-400">{{ total }} registro(s)</span>
|
||
</div>
|
||
<div class="overflow-x-auto">
|
||
<table class="w-full text-sm">
|
||
<thead class="bg-gray-50 border-b border-gray-200">
|
||
<tr>
|
||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase">Fecha/Hora</th>
|
||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase">Usuario</th>
|
||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase">Acción</th>
|
||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase">Detalle</th>
|
||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase">IP</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-gray-100">
|
||
{% for r in rows %}
|
||
<tr class="hover:bg-gray-50">
|
||
<td class="px-4 py-2.5 text-xs text-gray-500 font-mono whitespace-nowrap">
|
||
{{ r.created_at[:16].replace('T',' ') }}
|
||
</td>
|
||
<td class="px-4 py-2.5">
|
||
<span class="font-medium text-gray-700">{{ r.username }}</span>
|
||
</td>
|
||
<td class="px-4 py-2.5">
|
||
{% set a = r.accion %}
|
||
{% if a == 'login' %}
|
||
<span class="px-2 py-0.5 bg-green-100 text-green-700 rounded-full text-xs font-medium">Login</span>
|
||
{% elif a == 'login_fallido' %}
|
||
<span class="px-2 py-0.5 bg-red-100 text-red-700 rounded-full text-xs font-medium">Login fallido</span>
|
||
{% elif a == 'rda_enviado' %}
|
||
<span class="px-2 py-0.5 bg-purple-100 text-purple-700 rounded-full text-xs font-medium">RDA enviado</span>
|
||
{% elif a == 'rda_masivo' %}
|
||
<span class="px-2 py-0.5 bg-indigo-100 text-indigo-700 rounded-full text-xs font-medium">RDA masivo</span>
|
||
{% elif a == 'rda_directo' %}
|
||
<span class="px-2 py-0.5 bg-blue-100 text-blue-700 rounded-full text-xs font-medium">RDA directo</span>
|
||
{% elif a == 'automation_run' %}
|
||
<span class="px-2 py-0.5 bg-orange-100 text-orange-700 rounded-full text-xs font-medium">Automatización</span>
|
||
{% elif a == 'contrato_creado' %}
|
||
<span class="px-2 py-0.5 bg-teal-100 text-teal-700 rounded-full text-xs font-medium">Contrato creado</span>
|
||
{% elif a == 'contrato_editado' %}
|
||
<span class="px-2 py-0.5 bg-yellow-100 text-yellow-700 rounded-full text-xs font-medium">Contrato editado</span>
|
||
{% elif a == 'contrato_toggle' %}
|
||
<span class="px-2 py-0.5 bg-gray-100 text-gray-700 rounded-full text-xs font-medium">Contrato toggle</span>
|
||
{% else %}
|
||
<span class="px-2 py-0.5 bg-gray-100 text-gray-600 rounded-full text-xs font-medium">{{ a }}</span>
|
||
{% endif %}
|
||
</td>
|
||
<td class="px-4 py-2.5 text-xs text-gray-600 max-w-xs truncate" title="{{ r.detalle }}">
|
||
{{ r.detalle }}
|
||
</td>
|
||
<td class="px-4 py-2.5 text-xs text-gray-400 font-mono">{{ r.ip }}</td>
|
||
</tr>
|
||
{% else %}
|
||
<tr>
|
||
<td colspan="5" class="px-4 py-10 text-center text-gray-400">
|
||
No hay eventos registrados aún
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
{% if has_more or offset > 0 %}
|
||
<div class="px-6 py-3 border-t border-gray-100 flex justify-between items-center">
|
||
{% if offset > 0 %}
|
||
<a href="?username={{ filtro_username }}&accion={{ filtro_accion }}&fecha_desde={{ filtro_fecha_desde }}&fecha_hasta={{ filtro_fecha_hasta }}&offset={{ [offset - page_size, 0]|max }}"
|
||
class="px-3 py-1.5 bg-gray-100 text-gray-700 rounded-lg text-xs hover:bg-gray-200">
|
||
<i class="fas fa-chevron-left mr-1"></i> Anterior
|
||
</a>
|
||
{% else %}<span></span>{% endif %}
|
||
<span class="text-xs text-gray-400">{{ offset + 1 }}–{{ [offset + page_size, total]|min }} de {{ total }}</span>
|
||
{% if has_more %}
|
||
<a href="?username={{ filtro_username }}&accion={{ filtro_accion }}&fecha_desde={{ filtro_fecha_desde }}&fecha_hasta={{ filtro_fecha_hasta }}&offset={{ offset + page_size }}"
|
||
class="px-3 py-1.5 bg-gray-100 text-gray-700 rounded-lg text-xs hover:bg-gray-200">
|
||
Siguiente <i class="fas fa-chevron-right ml-1"></i>
|
||
</a>
|
||
{% else %}<span></span>{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% endblock %}
|