import { subMinutes } from 'date-fns' import * as unifiController from './unifiController' import * as ciscoController from './ciscoController' import prisma from '../prisma' import { pubsub, USER_PRESENCE_UPDATED, ACCESS_POINTS_UPDATED } from '../pubsub' import { logError, logInfo, logLow, logSuccess } from './logger' import { performance } from 'perf_hooks' const RECENT_THRESHOLD_IN_MINUTES = 2 const OLD_DEVICES_THRESHOLD_IN_DAYS = 90 const TIMEOUT_IN_MILLISECONDS = process.env.TASK_TIMEOUT || 120000 let working = false const wifiControllers = [unifiController, ciscoController] async function getOnlineDevices() { const onlineDevicesPromises = wifiControllers.map(wifiController => wifiController.getOnlineWifiDevices() ) const onlineDevices = (await Promise.all(onlineDevicesPromises)).flat() return onlineDevices } async function updateDevicesStatus(onlineDevices) { const lastSeenThreshold = subMinutes(new Date(), RECENT_THRESHOLD_IN_MINUTES) const onlineDevicesMacs = onlineDevices.map(device => device.mac) const recent = prisma.wifiDevice.updateMany({ where: { lastSeen: { gt: lastSeenThreshold }, status: 'ONLINE', mac: { notIn: onlineDevicesMacs } }, data: { status: 'RECENT' } }) const offline = prisma.wifiDevice.updateMany({ where: { lastSeen: { lte: lastSeenThreshold }, status: { not: 'OFFLINE' }, mac: { notIn: onlineDevicesMacs } }, data: { status: 'OFFLINE' } }) return prisma.$transaction([recent, offline]) } async function forceUserDisconnect(mac) { try { await prisma.wifiDevice.update({ where: { mac }, data: { user: { disconnect: true } } }) } catch (e) { logError({ tags: ['wifiDevices'], message: `Erro tentando desconectar o usuário do dispositivo "${mac}".`, data: { error: e, mac } }) } } function mockHostName({ mac, oui }) { const shortOui = oui?.split(' ')[0] || 'desc' const clearMac = mac ? mac.replaceAll(':', '') : '' return `${shortOui}_${clearMac}` } async function updateDB(onlineDevices) { const devicesUpsertPromises = onlineDevices.map(async device => { if (!device.user) forceUserDisconnect(device.mac) const user = device.user ? { connect: { sAMAccountName: device.user.replace('IFMS\\', '').toLowerCase() } } : undefined const hostname = device.hostname || mockHostName(device) return prisma.wifiDevice .upsert({ where: { mac: device.mac }, create: { ...device, hostname, firstSeen: device.firstSeen || new Date(), user, accessPoint: { connect: { hostname: device.apName } } }, update: { ...device, hostname, user, accessPoint: { connect: { hostname: device.apName } } } }) .catch(async e => { // If is a binding problem, probably the device has an user outside of AD, so save it anyway... if (e.code == 'P2025') try { await forceUserDisconnect(device.mac) await prisma.wifiDevice.upsert({ where: { mac: device.mac }, create: { ...device, hostname, firstSeen: device.firstSeen || new Date(), user: undefined, accessPoint: { connect: { hostname: device.apName } } }, update: { ...device, hostname, user: undefined, accessPoint: { connect: { hostname: device.apName } } } }) } catch (e) { logError({ tags: ['wifiDevices'], message: `Erro ao adicionar o dispositivo "${device.mac}" AP: ${device.apMac}. Ele tinha um usuário fora do AD que foi ignorado, mas falhou mesmo assim. ${e.message} `, data: { error: e, device } }) } else logError({ tags: ['wifiDevices'], message: `Erro tentando adicionar o dispositivo "${device.mac}: ${e.message}".`, data: { error: e, device } }) }) }) return Promise.allSettled(devicesUpsertPromises) } function updateDevicesInfo() { return new Promise(async (resolve, reject) => { if (working) return reject('A última atualização ainda não terminou') working = true const updateTimeout = setTimeout(() => { reject('A função atingiu seu tempo limite.') working = false }, TIMEOUT_IN_MILLISECONDS) try { const startTime = performance.now() const onlineDevices = await getOnlineDevices() await updateDevicesStatus(onlineDevices) const endTime = performance.now() const startTimeDB = performance.now() await updateDB(onlineDevices) const endTimeDB = performance.now() onlineDevices.length > 0 ? resolve( `${onlineDevices.length} atualizados em ${Math.floor( endTime - startTime )}ms, DB em ${Math.floor(endTimeDB - startTimeDB)}ms` ) : reject('Não há dispositivos conectados no momento.') pubsub.publish(USER_PRESENCE_UPDATED, { userPresenceUpdated: onlineDevices.length }) const updatedDbAccessPoints = await prisma.accessPoint.findMany({ include: { wifiDevices: { where: { status: 'ONLINE' } } } }) pubsub.publish(ACCESS_POINTS_UPDATED, { accessPointsUpdated: updatedDbAccessPoints }) logSuccess({ tags: ['wifiDevices'], message: `${onlineDevices.length} dispositivos Wi-Fi atualizados em ${((endTime - startTime) / 1000).toFixed(2)}s. BD em ${( (endTimeDB - startTimeDB) / 1000 ).toFixed(2)}s` }) } catch (e) { logError({ tags: ['wifiDevices'], message: `Erro atualizando dispositivos Wi-Fi: ${e.message}`, data: e }) reject('Não foi possível atualizar as informações dos dispositivos.') } finally { working = false clearTimeout(updateTimeout) } }) } // Delete devices that are offline for more than OLD_DEVICES_THRESHOLD_IN_DAYS days async function deleteOldDevices() { const oldDevicesThresholdInMilliseconds = OLD_DEVICES_THRESHOLD_IN_DAYS * 24 * 60 * 60 * 1000 const oldDevices = await prisma.wifiDevice.deleteMany({ where: { lastSeen: { lt: new Date(Date.now() - oldDevicesThresholdInMilliseconds) }, status: 'OFFLINE' } }) if (oldDevices.count > 0) logInfo({ tags: ['wifiDevices', 'deleteOldDevices'], message: `${oldDevices.count} dispositivos Wi-Fi não vistos há mais de ${OLD_DEVICES_THRESHOLD_IN_DAYS} dias foram excluídos.`, data: { oldDevices } }) else logInfo({ tags: ['wifiDevices', 'deleteOldDevices'], message: `Nenhum dispositivo Wi-Fi não visto há mais de ${OLD_DEVICES_THRESHOLD_IN_DAYS} dias foi encontrado.` }) } export { updateDevicesInfo, deleteOldDevices }