Implementar tarjeta de Vulnerabilidad y mejorar interactividad

- Reemplazar tarjeta 'Más Común' por 'Vulnerabilidad'
- Backend: Agregar campo critical_actions_today al schema de stats
- Backend: Calcular acciones críticas (delete, update sensibles, logout)
- Frontend: Mostrar contador de acciones críticas con código de color
- Frontend: Click en tarjeta filtra por acciones críticas del día
- Frontend: Botón 'Ver' funcional con icono
- Color rojo si >10 críticas, ámbar si >5, verde si <=5
- Función filterCriticalActions() para búsqueda rápida
- UX mejorada con hover effects y transiciones
This commit is contained in:
2026-02-16 08:38:39 -07:00
parent 90f9c9c6e6
commit 9f973464b9
3 changed files with 60 additions and 8 deletions

View File

@@ -84,6 +84,7 @@ class AuditLogStats(BaseModel):
total_actions: int = Field(description="Total de acciones registradas")
actions_today: int = Field(description="Acciones en las ├║ltimas 24 horas")
actions_this_week: int = Field(description="Acciones en los últimos 7 días")
critical_actions_today: int = Field(description="Acciones críticas hoy (delete, cambios sensibles)")
# Top acciones
top_actions: Dict[str, int] = Field(description="Acciones más frecuentes")

View File

@@ -270,10 +270,27 @@ async def get_audit_stats(
# En producción podrías hacer un join con users para obtener nombres
top_users = {} # Placeholder - implementar con join si es necesario
# Acciones críticas hoy (delete, update sensibles, etc.)
critical_actions_query = select(func.count()).select_from(AuditLog).where(
and_(
AuditLog.tenant_id == current_tenant.id,
AuditLog.created_at >= today_start,
or_(
AuditLog.action.like('%.delete'),
AuditLog.action.like('user.update'),
AuditLog.action.like('%.assign'),
AuditLog.action.in_(['user.login_failed', 'user.logout'])
)
)
)
critical_result = await db.execute(critical_actions_query)
critical_actions_today = critical_result.scalar() or 0
return AuditLogStats(
total_actions=total_actions,
actions_today=actions_today,
actions_this_week=actions_this_week,
critical_actions_today=critical_actions_today,
top_actions=top_actions,
top_users=top_users,
by_resource_type=by_resource_type

View File

@@ -193,6 +193,27 @@
loadLogs();
}
/**
* Filtrar por acciones críticas (vulnerabilidad)
*/
function filterCriticalActions() {
// Limpiar otros filtros
filterUserId = '';
filterResourceType = '';
// Buscar acciones críticas: delete, update sensibles, etc.
searchText = 'delete';
// Cambiar a hoy para ver las del día
periodFilter = 'today';
// Expandir filtros avanzados para que el usuario vea lo aplicado
showAdvancedFilters = true;
currentPage = 1;
loadLogs();
}
/**
* Cambiar página
*/
@@ -408,15 +429,28 @@
<div class="text-sm text-gray-500">Esta Semana</div>
<div class="text-2xl font-bold text-green-600">{stats.actions_this_week}</div>
</div>
<div class="bg-white rounded-lg shadow p-4">
<div class="text-sm text-gray-500 truncate">Más Común</div>
<div class="text-sm font-semibold text-gray-900 truncate">
{#if stats.top_actions && Object.keys(stats.top_actions).length > 0}
{formatActionText(Object.keys(stats.top_actions)[0])}
{:else}
N/A
{/if}
<div class="bg-white rounded-lg shadow p-4 cursor-pointer hover:shadow-lg transition-shadow" on:click={() => filterCriticalActions()}>
<div class="flex items-center gap-2 mb-1">
<svg class="w-4 h-4 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="text-sm text-gray-500">Vulnerabilidad</div>
</div>
<div class="flex items-center justify-between">
<div class="text-2xl font-bold {stats.critical_actions_today > 10 ? 'text-red-600' : stats.critical_actions_today > 5 ? 'text-amber-600' : 'text-green-600'}">
{stats.critical_actions_today}
</div>
<button
class="text-xs text-primary-600 hover:text-primary-700 font-medium flex items-center gap-1"
on:click|stopPropagation={() => filterCriticalActions()}
>
Ver
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
<div class="text-xs text-gray-500 mt-1">Acciones críticas hoy</div>
</div>
</div>
{/if}