Nuevas funcionalidades: - Sistema completo de análisis de seguridad con detección de amenazas - Detección de patrones: fuerza bruta, escalada de privilegios, eliminaciones masivas, cuentas comprometidas - Panel de vulnerabilidades con visualización detallada - Acciones de seguridad: bloqueo de IPs, notificaciones, reset de contraseñas - Análisis configurable (24h, 48h, 7 días) Backend (/audit/security/): - GET /analysis: Análisis completo de seguridad con amenazas detectadas - POST /action: Ejecutar acciones de seguridad (solo ADMIN/SUPPORT_MANAGER) - Schemas nuevos: SecurityAnalysisResponse, SecurityThreatPattern, SecurityActionRequest Frontend (/audit/security): - Panel completo de análisis con nivel de riesgo general - Visualización de amenazas con severidad (critical, high, medium, low) - Estadísticas: amenazas, intentos fallidos, IPs sospechosas, acciones críticas - Opciones de acción por amenaza: bloquear IP, resetear contraseña, notificar admin - Modal de ejecución de acciones de seguridad Mejoras: - Sidebar actualizado con enlace 'Seguridad' - Permisos: Solo ADMIN/SUPPORT_MANAGER/AUDITOR pueden ver análisis - Solo ADMIN/SUPPORT_MANAGER pueden ejecutar acciones - Audit log de todas las acciones de seguridad ejecutadas
193 lines
6.8 KiB
Svelte
193 lines
6.8 KiB
Svelte
<script lang="ts">
|
|
import { page } from '$app/stores';
|
|
import { auth } from '$lib/stores/auth.js';
|
|
|
|
export let open = false;
|
|
|
|
// Navigation items based on user role
|
|
$: navigation = getNavigationForRole($auth.user?.role);
|
|
|
|
function getNavigationForRole(role: string | undefined) {
|
|
const baseNavigation = [
|
|
{
|
|
name: 'Dashboard',
|
|
href: '/',
|
|
icon: 'M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z'
|
|
},
|
|
{
|
|
name: 'Tickets',
|
|
href: '/tickets',
|
|
icon: 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2'
|
|
}
|
|
];
|
|
|
|
if (role === 'ADMIN' || role === 'SUPPORT_MANAGER') {
|
|
baseNavigation.push(
|
|
{
|
|
name: 'Clientes',
|
|
href: '/tenants',
|
|
icon: 'M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4'
|
|
},
|
|
{
|
|
name: 'Usuarios',
|
|
href: '/users',
|
|
icon: 'M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z'
|
|
},
|
|
{
|
|
name: 'Categorías',
|
|
href: '/categories',
|
|
icon: 'M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10'
|
|
},
|
|
{
|
|
name: 'Sistemas',
|
|
href: '/systems',
|
|
icon: 'M5 12a1 1 0 102 0V6.414l1.293 1.293a1 1 0 001.414-1.414l-3-3a1 1 0 00-1.414 0l-3 3a1 1 0 001.414 1.414L5 6.414V12zm14 0a1 1 0 10-2 0v5.586l-1.293-1.293a1 1 0 00-1.414 1.414l3 3a1 1 0 001.414 0l3-3a1 1 0 00-1.414-1.414L19 17.586V12z'
|
|
}
|
|
);
|
|
}
|
|
|
|
if (role === 'ADMIN') {
|
|
baseNavigation.push(
|
|
{
|
|
name: 'SLA Management',
|
|
href: '/sla',
|
|
icon: 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z'
|
|
},
|
|
{
|
|
name: 'Reportes',
|
|
href: '/reports',
|
|
icon: 'M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z'
|
|
},
|
|
{
|
|
name: 'Auditoría',
|
|
href: '/audit',
|
|
icon: 'M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z'
|
|
},
|
|
{
|
|
name: 'Seguridad',
|
|
href: '/audit/security',
|
|
icon: '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'
|
|
}
|
|
);
|
|
}
|
|
|
|
return baseNavigation;
|
|
}
|
|
|
|
function isCurrentPage(href: string) {
|
|
return $page.url.pathname === href || ($page.url.pathname.startsWith(href) && href !== '/');
|
|
}
|
|
</script>
|
|
|
|
<!-- Mobile sidebar backdrop -->
|
|
{#if open}
|
|
<div class="fixed inset-0 z-40 lg:hidden">
|
|
<div
|
|
class="fixed inset-0 bg-gray-600 bg-opacity-75"
|
|
role="dialog"
|
|
tabindex="0"
|
|
on:click={() => (open = false)}
|
|
on:keydown={e => e.key === 'Escape' && (open = false)}
|
|
/>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Sidebar -->
|
|
<div
|
|
class="fixed inset-y-0 left-0 z-50 w-64 bg-white shadow-lg transform {open
|
|
? 'translate-x-0'
|
|
: '-translate-x-full'} transition-transform duration-300 ease-in-out lg:translate-x-0 lg:static lg:inset-0"
|
|
>
|
|
<div class="flex flex-col h-full">
|
|
<!-- Logo -->
|
|
<div class="flex items-center justify-between h-16 px-6 bg-primary-600">
|
|
<div class="flex items-center space-x-2">
|
|
<div class="w-8 h-8 bg-white rounded-lg flex items-center justify-center">
|
|
<svg
|
|
class="w-5 h-5 text-primary-600"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M18.364 5.636l-3.536 3.536m0 5.656l3.536 3.536M9.172 9.172L5.636 5.636m3.536 9.192L5.636 18.364M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-5 0a4 4 0 11-8 0 4 4 0 018 0z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<div class="text-white">
|
|
<h1 class="text-sm font-semibold">ServiceManager</h1>
|
|
<p class="text-xs text-primary-100">Panel Interno</p>
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
on:click={() => (open = false)}
|
|
class="p-2 rounded-md text-primary-100 hover:text-white hover:bg-primary-500 lg:hidden"
|
|
>
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M6 18L18 6M6 6l12 12"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Navigation -->
|
|
<nav class="flex-1 px-4 py-6 space-y-2">
|
|
{#each navigation as item}
|
|
<a
|
|
href={item.href}
|
|
class="flex items-center space-x-3 px-3 py-2 rounded-md text-sm font-medium transition-colors {isCurrentPage(
|
|
item.href
|
|
)
|
|
? 'bg-primary-100 text-primary-700'
|
|
: 'text-gray-600 hover:bg-gray-100 hover:text-gray-900'}"
|
|
on:click={() => (open = false)}
|
|
>
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d={item.icon} />
|
|
</svg>
|
|
<span>{item.name}</span>
|
|
</a>
|
|
{/each}
|
|
</nav>
|
|
|
|
<!-- User info -->
|
|
<div class="px-4 py-4 border-t border-gray-200">
|
|
<div class="flex items-center space-x-3">
|
|
<div class="w-8 h-8 bg-primary-100 rounded-full flex items-center justify-center">
|
|
<span class="text-primary-600 text-sm font-medium">
|
|
{$auth.user?.first_name?.[0]}{$auth.user?.last_name?.[0]}
|
|
</span>
|
|
</div>
|
|
<div class="flex-1 min-w-0">
|
|
<p class="text-sm font-medium text-gray-900 truncate">
|
|
{$auth.user?.first_name}
|
|
{$auth.user?.last_name}
|
|
</p>
|
|
<p class="text-xs text-gray-500 truncate">
|
|
{$auth.user?.role === 'ADMIN'
|
|
? 'Administrador'
|
|
: $auth.user?.role === 'SUPPORT_MANAGER'
|
|
? 'Gerente de Soporte'
|
|
: $auth.user?.role === 'AGENT'
|
|
? 'Agente'
|
|
: 'Auditor'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Version info -->
|
|
<div class="px-4 py-2 border-t border-gray-100">
|
|
<p class="text-xs text-gray-400 text-center">v1.6.0</p>
|
|
</div>
|
|
</div>
|
|
</div>
|