import { z } from 'zod' import { TRPCError } from '@trpc/server' import { router, protectedProcedure } from '../trpc' import { LibreNMSClient } from '@/server/services/librenms/client' export const redRouter = router({ // Listar dispositivos de red list: protectedProcedure .input( z.object({ clienteId: z.string().optional(), tipo: z.enum(['ROUTER', 'SWITCH', 'FIREWALL', 'AP', 'IMPRESORA', 'OTRO']).optional(), estado: z.enum(['ONLINE', 'OFFLINE', 'ALERTA', 'MANTENIMIENTO', 'DESCONOCIDO']).optional(), search: z.string().optional(), page: z.number().default(1), limit: z.number().default(20), }).optional() ) .query(async ({ ctx, input }) => { const { clienteId, tipo, estado, search, page = 1, limit = 20 } = input || {} const tiposRed = ['ROUTER', 'SWITCH', 'FIREWALL', 'AP', 'IMPRESORA', 'OTRO'] as const const where = { tipo: tipo ? { equals: tipo } : { in: tiposRed }, ...(ctx.user.clienteId ? { clienteId: ctx.user.clienteId } : {}), ...(clienteId ? { clienteId } : {}), ...(estado ? { estado } : {}), ...(search ? { OR: [ { nombre: { contains: search, mode: 'insensitive' as const } }, { ip: { contains: search } }, { mac: { contains: search, mode: 'insensitive' as const } }, ], } : {}), } const [dispositivos, total] = await Promise.all([ ctx.prisma.dispositivo.findMany({ where, include: { cliente: { select: { id: true, nombre: true, codigo: true } }, ubicacion: { select: { id: true, nombre: true } }, }, orderBy: [{ estado: 'asc' }, { nombre: 'asc' }], skip: (page - 1) * limit, take: limit, }), ctx.prisma.dispositivo.count({ where }), ]) return { dispositivos, pagination: { total, page, limit, totalPages: Math.ceil(total / limit), }, } }), // Obtener dispositivo de red por ID byId: protectedProcedure .input(z.object({ id: z.string() })) .query(async ({ ctx, input }) => { const dispositivo = await ctx.prisma.dispositivo.findUnique({ where: { id: input.id }, include: { cliente: true, ubicacion: true, alertas: { where: { estado: 'ACTIVA' }, orderBy: { createdAt: 'desc' }, take: 10, }, }, }) if (!dispositivo) { throw new TRPCError({ code: 'NOT_FOUND', message: 'Dispositivo no encontrado' }) } if (ctx.user.clienteId && ctx.user.clienteId !== dispositivo.clienteId) { throw new TRPCError({ code: 'FORBIDDEN', message: 'Acceso denegado' }) } return dispositivo }), // Obtener interfaces de un dispositivo interfaces: protectedProcedure .input(z.object({ dispositivoId: z.string() })) .query(async ({ ctx, input }) => { const dispositivo = await ctx.prisma.dispositivo.findUnique({ where: { id: input.dispositivoId }, }) if (!dispositivo || !dispositivo.librenmsId) { throw new TRPCError({ code: 'BAD_REQUEST', message: 'Dispositivo no tiene LibreNMS', }) } if (ctx.user.clienteId && ctx.user.clienteId !== dispositivo.clienteId) { throw new TRPCError({ code: 'FORBIDDEN', message: 'Acceso denegado' }) } const librenms = new LibreNMSClient() return librenms.getDevicePorts(dispositivo.librenmsId) }), // Obtener grafico de trafico de una interfaz trafico: protectedProcedure .input( z.object({ dispositivoId: z.string(), portId: z.number(), periodo: z.enum(['1h', '6h', '24h', '7d', '30d']).default('24h'), }) ) .query(async ({ ctx, input }) => { const dispositivo = await ctx.prisma.dispositivo.findUnique({ where: { id: input.dispositivoId }, }) if (!dispositivo || !dispositivo.librenmsId) { throw new TRPCError({ code: 'BAD_REQUEST', message: 'Dispositivo no tiene LibreNMS', }) } if (ctx.user.clienteId && ctx.user.clienteId !== dispositivo.clienteId) { throw new TRPCError({ code: 'FORBIDDEN', message: 'Acceso denegado' }) } const librenms = new LibreNMSClient() // Calcular rango de tiempo const now = new Date() let desde: Date switch (input.periodo) { case '1h': desde = new Date(now.getTime() - 60 * 60 * 1000) break case '6h': desde = new Date(now.getTime() - 6 * 60 * 60 * 1000) break case '24h': desde = new Date(now.getTime() - 24 * 60 * 60 * 1000) break case '7d': desde = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000) break case '30d': desde = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000) break } return librenms.getPortStats(input.portId, desde, now) }), // Obtener topologia de red topologia: protectedProcedure .input(z.object({ clienteId: z.string().optional() })) .query(async ({ ctx, input }) => { const clienteId = ctx.user.clienteId || input.clienteId // Obtener dispositivos de red del cliente const dispositivos = await ctx.prisma.dispositivo.findMany({ where: { ...(clienteId ? { clienteId } : {}), tipo: { in: ['ROUTER', 'SWITCH', 'FIREWALL', 'AP'] }, librenmsId: { not: null }, }, select: { id: true, nombre: true, ip: true, tipo: true, estado: true, librenmsId: true, }, }) if (dispositivos.length === 0) { return { nodes: [], links: [] } } // Obtener enlaces de LibreNMS const librenms = new LibreNMSClient() const links = await librenms.getLinks() // Mapear a nodos y enlaces para visualizacion const librenmsIdToDevice = new Map( dispositivos .filter(d => d.librenmsId !== null) .map(d => [d.librenmsId!, d]) ) const nodes = dispositivos.map(d => ({ id: d.id, name: d.nombre, ip: d.ip, type: d.tipo, status: d.estado, })) const edges = links .filter( (l: { local_device_id: number; remote_device_id: number }) => librenmsIdToDevice.has(l.local_device_id) && librenmsIdToDevice.has(l.remote_device_id) ) .map((l: { local_device_id: number; remote_device_id: number; local_port: string; remote_port: string }) => ({ source: librenmsIdToDevice.get(l.local_device_id)!.id, target: librenmsIdToDevice.get(l.remote_device_id)!.id, localPort: l.local_port, remotePort: l.remote_port, })) return { nodes, links: edges } }), // Obtener alertas SNMP activas alertasSNMP: protectedProcedure .input(z.object({ dispositivoId: z.string().optional() })) .query(async ({ ctx, input }) => { const librenms = new LibreNMSClient() if (input.dispositivoId) { const dispositivo = await ctx.prisma.dispositivo.findUnique({ where: { id: input.dispositivoId }, }) if (!dispositivo || !dispositivo.librenmsId) { return [] } if (ctx.user.clienteId && ctx.user.clienteId !== dispositivo.clienteId) { throw new TRPCError({ code: 'FORBIDDEN', message: 'Acceso denegado' }) } return librenms.getDeviceAlerts(dispositivo.librenmsId) } return librenms.getAlerts() }), // Obtener datos de NetFlow netflow: protectedProcedure .input( z.object({ dispositivoId: z.string(), periodo: z.enum(['1h', '6h', '24h']).default('1h'), }) ) .query(async ({ ctx, input }) => { const dispositivo = await ctx.prisma.dispositivo.findUnique({ where: { id: input.dispositivoId }, }) if (!dispositivo || !dispositivo.librenmsId) { throw new TRPCError({ code: 'BAD_REQUEST', message: 'Dispositivo no tiene LibreNMS', }) } if (ctx.user.clienteId && ctx.user.clienteId !== dispositivo.clienteId) { throw new TRPCError({ code: 'FORBIDDEN', message: 'Acceso denegado' }) } // LibreNMS puede tener integracion con nfsen para netflow // Por ahora retornamos datos de ejemplo return { topTalkers: [], topProtocols: [], topPorts: [], } }), // Estadisticas de red por cliente stats: protectedProcedure .input(z.object({ clienteId: z.string().optional() })) .query(async ({ ctx, input }) => { const clienteId = ctx.user.clienteId || input.clienteId const where = { tipo: { in: ['ROUTER', 'SWITCH', 'FIREWALL', 'AP', 'IMPRESORA', 'OTRO'] as const }, ...(clienteId ? { clienteId } : {}), } const [total, online, offline, alertas] = await Promise.all([ ctx.prisma.dispositivo.count({ where }), ctx.prisma.dispositivo.count({ where: { ...where, estado: 'ONLINE' } }), ctx.prisma.dispositivo.count({ where: { ...where, estado: 'OFFLINE' } }), ctx.prisma.dispositivo.count({ where: { ...where, estado: 'ALERTA' } }), ]) return { total, online, offline, alertas } }), })