ifms-pti/server/src/lib/wifiDevices.js
Douglas Barone ad4dc8c0cf Ensure id
2022-08-08 18:45:48 -04:00

273 lines
7.3 KiB
JavaScript

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 }