Discovery OK
This commit is contained in:
parent
4225d51140
commit
8280b7c3f7
17
package-lock.json
generated
17
package-lock.json
generated
|
@ -10,13 +10,15 @@
|
|||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@prisma/client": "^4.15.0",
|
||||
"@types/netmask": "^2.0.1",
|
||||
"body-parser": "^1.20.2",
|
||||
"bree": "^9.1.3",
|
||||
"dotenv": "^16.1.4",
|
||||
"express": "^4.18.2",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"ldapts": "^4.2.6",
|
||||
"net-snmp": "^3.9.6"
|
||||
"net-snmp": "^3.9.6",
|
||||
"netmask": "^2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.17",
|
||||
|
@ -622,6 +624,11 @@
|
|||
"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/netmask": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/netmask/-/netmask-2.0.1.tgz",
|
||||
"integrity": "sha512-SSj+JNRxpaljp9Uy1td4MM6p6hJfmKk3Z/5h5MhJZbX7KI90DNvS8L3aNuevAOaGftaGAXLUrEd161Tk3iwaTg=="
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.1.tgz",
|
||||
|
@ -1912,6 +1919,14 @@
|
|||
"smart-buffer": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/netmask": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz",
|
||||
"integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==",
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nodemon": {
|
||||
"version": "2.0.22",
|
||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz",
|
||||
|
|
|
@ -32,12 +32,14 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^4.15.0",
|
||||
"@types/netmask": "^2.0.1",
|
||||
"body-parser": "^1.20.2",
|
||||
"bree": "^9.1.3",
|
||||
"dotenv": "^16.1.4",
|
||||
"express": "^4.18.2",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"ldapts": "^4.2.6",
|
||||
"net-snmp": "^3.9.6"
|
||||
"net-snmp": "^3.9.6",
|
||||
"netmask": "^2.0.2"
|
||||
}
|
||||
}
|
||||
|
|
38
prisma/migrations/20230621170219_/migration.sql
Normal file
38
prisma/migrations/20230621170219_/migration.sql
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `hostname` on the `Printer` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `createdAt` on the `PrinterStatus` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Printer" DROP COLUMN "hostname";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "PrinterStatus" DROP COLUMN "createdAt",
|
||||
ADD COLUMN "timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Network" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"shortName" TEXT NOT NULL,
|
||||
"cidr" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "Network_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Network_name_key" ON "Network"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Network_shortName_key" ON "Network"("shortName");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Network_cidr_key" ON "Network"("cidr");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Network_id_idx" ON "Network"("id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "PrinterStatus_timestamp_idx" ON "PrinterStatus"("timestamp");
|
|
@ -26,6 +26,7 @@ model User {
|
|||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
campus String?
|
||||
|
||||
roles Role[] @default([USER])
|
||||
}
|
||||
|
@ -37,7 +38,6 @@ model Printer {
|
|||
location String?
|
||||
|
||||
serialNumber String? @unique
|
||||
hostname String?
|
||||
ip String @unique
|
||||
model String
|
||||
|
||||
|
@ -49,7 +49,9 @@ model Printer {
|
|||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
|
||||
status PrinterStatus[]
|
||||
status PrinterStatus[]
|
||||
network Network @relation(fields: [networkId], references: [id])
|
||||
networkId Int
|
||||
}
|
||||
|
||||
model PrinterStatus {
|
||||
|
@ -62,8 +64,21 @@ model PrinterStatus {
|
|||
|
||||
counter Int
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
timestamp DateTime @default(now())
|
||||
|
||||
printerId Int
|
||||
printer Printer @relation(fields: [printerId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([timestamp])
|
||||
}
|
||||
|
||||
model Network {
|
||||
id Int @id @default(autoincrement())
|
||||
name String @unique
|
||||
shortName String @unique
|
||||
cidr String @unique
|
||||
|
||||
printers Printer[]
|
||||
|
||||
@@index([id])
|
||||
}
|
||||
|
|
|
@ -3,37 +3,119 @@ import { PrismaClient } from '@prisma/client'
|
|||
export const prisma = new PrismaClient()
|
||||
|
||||
async function main() {
|
||||
console.log('Seeding printers...')
|
||||
|
||||
console.log('Seeding subnets...')
|
||||
|
||||
await prisma.network.createMany({
|
||||
data: [
|
||||
{
|
||||
shortName: 'RT1',
|
||||
name: 'Reitoria',
|
||||
cidr: '10.0.0.0/21'
|
||||
},
|
||||
{
|
||||
shortName: 'RT2',
|
||||
name: 'Reitoria 2',
|
||||
cidr: '10.1.0.0/21'
|
||||
},
|
||||
{
|
||||
shortName: 'AQ',
|
||||
name: 'Aquidauana',
|
||||
cidr: '10.2.0.0/21'
|
||||
},
|
||||
{
|
||||
shortName: 'CG',
|
||||
name: 'Campo Grande',
|
||||
cidr: '10.3.0.0/21'
|
||||
},
|
||||
{
|
||||
shortName: 'CB',
|
||||
name: 'Corumbá',
|
||||
cidr: '10.4.0.0/21'
|
||||
},
|
||||
{
|
||||
shortName: 'CX',
|
||||
name: 'Coxim',
|
||||
cidr: '10.5.0.0/21'
|
||||
},
|
||||
{
|
||||
shortName: 'NA',
|
||||
name: 'Nova Andradina',
|
||||
cidr: '10.6.0.0/21'
|
||||
},
|
||||
{
|
||||
shortName: 'PP',
|
||||
name: 'Ponta Porã',
|
||||
cidr: '10.7.0.0/21'
|
||||
},
|
||||
{
|
||||
shortName: 'TL',
|
||||
name: 'Três Lagoas',
|
||||
cidr: '10.8.0.0/21'
|
||||
},
|
||||
{
|
||||
shortName: 'JD',
|
||||
name: 'Jardim',
|
||||
cidr: '10.9.0.0/21'
|
||||
},
|
||||
{
|
||||
shortName: 'NV',
|
||||
name: 'Naviraí',
|
||||
cidr: '10.10.0.0/21'
|
||||
},
|
||||
{
|
||||
shortName: 'DR',
|
||||
name: 'Dourados',
|
||||
cidr: '10.11.0.0/21'
|
||||
}
|
||||
],
|
||||
skipDuplicates: true
|
||||
})
|
||||
|
||||
await prisma.printer.createMany({
|
||||
data: [
|
||||
{
|
||||
friendlyName: 'P04',
|
||||
ip: '10.7.0.134',
|
||||
model: 'ECOSYS M3655idn',
|
||||
serialNumber: 'R4P1478461'
|
||||
serialNumber: 'R4P1478461',
|
||||
networkId: 8
|
||||
},
|
||||
{
|
||||
friendlyName: 'P05',
|
||||
ip: '10.7.0.135',
|
||||
model: 'ECOSYS M2040dn',
|
||||
serialNumber: 'VR91483974'
|
||||
serialNumber: 'VR91483974',
|
||||
networkId: 8
|
||||
},
|
||||
{
|
||||
friendlyName: 'P06',
|
||||
ip: '10.7.0.136',
|
||||
model: 'ECOSYS M2040dn',
|
||||
serialNumber: 'VR91586433'
|
||||
serialNumber: 'VR91586433',
|
||||
networkId: 8
|
||||
},
|
||||
{
|
||||
friendlyName: 'P07',
|
||||
ip: '10.7.0.137',
|
||||
model: 'ECOSYS M2040dn',
|
||||
serialNumber: 'VR91586432'
|
||||
serialNumber: 'VR91586432',
|
||||
networkId: 8
|
||||
},
|
||||
{
|
||||
friendlyName: 'P08',
|
||||
ip: '10.7.0.138',
|
||||
model: 'ECOSYS P6235cdn',
|
||||
serialNumber: 'RCG0304510'
|
||||
serialNumber: 'RCG0304510',
|
||||
networkId: 8
|
||||
},
|
||||
{
|
||||
friendlyName: 'PNA',
|
||||
ip: '10.6.0.32',
|
||||
model: 'ECOSYS M2040dn',
|
||||
serialNumber: 'VR91586430',
|
||||
networkId: 8
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
|
@ -4,6 +4,8 @@ import { hasRolesMiddleware } from '../middlewares/hasRolesMiddleware.js'
|
|||
import { prisma } from '../prisma.js'
|
||||
import { PrinterStatusService } from '../services/PrinterStatusService.js'
|
||||
import { distributedCopy } from '../utils/distributedCopy.js'
|
||||
import { Netmask } from 'netmask'
|
||||
import { isIPv4 } from 'net'
|
||||
|
||||
const router = Router()
|
||||
|
||||
|
@ -16,22 +18,22 @@ class PrinterController {
|
|||
|
||||
static async show(req: Request, res: Response) {
|
||||
const { id } = req.params
|
||||
const { take, minutes = 43200 } = req.query
|
||||
const { take = 64, days = 60 } = req.query
|
||||
|
||||
const gte = new Date(Date.now() - 1000 * 60 * Number(minutes))
|
||||
const gte = new Date(Date.now() - 1000 * 60 * 60 * 24 * Number(days))
|
||||
|
||||
const printer = await prisma.printer.findUnique({
|
||||
where: { id: Number(id) },
|
||||
include: {
|
||||
status: {
|
||||
where: {
|
||||
createdAt: {
|
||||
timestamp: {
|
||||
gte
|
||||
}
|
||||
},
|
||||
|
||||
orderBy: {
|
||||
createdAt: 'desc'
|
||||
timestamp: 'desc'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,10 +50,24 @@ class PrinterController {
|
|||
static async create(req: Request, res: Response) {
|
||||
const { friendlyName, ip } = req.body
|
||||
|
||||
const ipBlock = new Netmask(ip)
|
||||
|
||||
if (!isIPv4(ip)) {
|
||||
res.status(400).json({ error: 'Invalid IP' })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const model = await PrinterStatusService.getPrinterModel(ip)
|
||||
const printer = await prisma.printer.create({
|
||||
data: { friendlyName, ip, model }
|
||||
data: {
|
||||
friendlyName,
|
||||
ip,
|
||||
model,
|
||||
network: {
|
||||
connect: {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
new PrinterStatusService(printer)
|
||||
|
@ -60,7 +76,7 @@ class PrinterController {
|
|||
} catch (e) {
|
||||
res
|
||||
.status(400)
|
||||
.json({ error: 'Este endereço não é de uma impressora suportada.' })
|
||||
.json({ error: 'Este IP não é de uma impressora suportada.' })
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
58
src/controllers/PrinterDiscoveryController.ts
Normal file
58
src/controllers/PrinterDiscoveryController.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
import { Router, Request, Response } from 'express'
|
||||
|
||||
import { hasRolesMiddleware } from '../middlewares/hasRolesMiddleware.js'
|
||||
import { PrinterDiscoveryService } from '../services/PrinterDiscoveryService.js'
|
||||
import { prisma } from '../prisma.js'
|
||||
import { PrinterStatusService } from '../services/PrinterStatusService.js'
|
||||
import { Printer } from '@prisma/client'
|
||||
|
||||
const router = Router()
|
||||
|
||||
class PrinterDiscoveryController {
|
||||
static async discovery(req: Request, res: Response) {
|
||||
const networks = await prisma.network.findMany()
|
||||
|
||||
const newPrinters: Printer[] = []
|
||||
const discoveredPrintersIPs: string[] = []
|
||||
|
||||
for (const network of networks) {
|
||||
console.log('Discovering printers for network', network.cidr)
|
||||
|
||||
try {
|
||||
const discoveredPrintersIPsForNetwork =
|
||||
await PrinterDiscoveryService.discovery(network.cidr)
|
||||
|
||||
discoveredPrintersIPs.push(...discoveredPrintersIPsForNetwork)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
const printers = await prisma.printer.findMany()
|
||||
|
||||
const newPrintersIPs = discoveredPrintersIPs.filter(
|
||||
ip => !printers.find(printer => printer.ip === ip)
|
||||
)
|
||||
|
||||
await Promise.allSettled(
|
||||
newPrintersIPs.map(async ip => {
|
||||
const model = await PrinterStatusService.getPrinterModel(ip)
|
||||
const printer = await prisma.printer.create({
|
||||
data: { ip, model, networkId: network.id }
|
||||
})
|
||||
|
||||
new PrinterStatusService(printer)
|
||||
|
||||
newPrinters.push(printer)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
res.json({ discoveredPrintersIPs, newPrinters })
|
||||
}
|
||||
}
|
||||
|
||||
router.use(hasRolesMiddleware(['ADMIN', 'INSPECTOR']))
|
||||
|
||||
router.post('/', PrinterDiscoveryController.discovery)
|
||||
|
||||
export default router
|
|
@ -7,6 +7,7 @@ import { authMiddleware } from './middlewares/authMiddleware.js'
|
|||
import LoginRouter from './controllers/LoginController.js'
|
||||
import PrinterRouter from './controllers/PrinterController.js'
|
||||
import PrinterStatusRouter from './controllers/PrinterStatusController.js'
|
||||
import PrinterDiscoveryRouter from './controllers/PrinterDiscoveryController.js'
|
||||
|
||||
export const app = express()
|
||||
|
||||
|
@ -18,6 +19,7 @@ app.use(populateUserMiddleware)
|
|||
app.use('/api/login', LoginRouter)
|
||||
app.use('/api/printer', PrinterRouter)
|
||||
app.use('/api/printer-status', PrinterStatusRouter)
|
||||
app.use('/api/discovery', PrinterDiscoveryRouter)
|
||||
|
||||
app.get('/api/me', authMiddleware, async (req: Request, res: Response) =>
|
||||
res.json(res.locals.user)
|
||||
|
|
|
@ -16,6 +16,7 @@ type LdapUser = {
|
|||
displayName: string
|
||||
thumbnailPhoto: string | null
|
||||
groups?: string[]
|
||||
campus?: string
|
||||
}
|
||||
|
||||
export class LdapService extends Client implements LdapClientInterface {
|
||||
|
@ -56,7 +57,8 @@ export class LdapService extends Client implements LdapClientInterface {
|
|||
'sAMAccountName',
|
||||
'displayName',
|
||||
'thumbnailPhoto',
|
||||
'dn'
|
||||
'dn',
|
||||
'extensionAttribute1'
|
||||
],
|
||||
explicitBufferAttributes: ['thumbnailPhoto']
|
||||
})
|
||||
|
@ -64,8 +66,14 @@ export class LdapService extends Client implements LdapClientInterface {
|
|||
if (!searchEntries.length)
|
||||
throw new Error('User not found on LDAP server.')
|
||||
|
||||
const { sAMAccountName, displayName, mail, thumbnailPhoto, dn } =
|
||||
searchEntries[0]
|
||||
const {
|
||||
sAMAccountName,
|
||||
displayName,
|
||||
mail,
|
||||
thumbnailPhoto,
|
||||
dn,
|
||||
extensionAttribute1
|
||||
} = searchEntries[0]
|
||||
|
||||
const ldapUser: LdapUser = {
|
||||
username: sAMAccountName.toString(),
|
||||
|
@ -74,7 +82,8 @@ export class LdapService extends Client implements LdapClientInterface {
|
|||
thumbnailPhoto: `data:image/png;base64,${Buffer.from(
|
||||
thumbnailPhoto as Buffer
|
||||
).toString('base64')}`,
|
||||
groups: await this.getGroupsForUser(dn.toString())
|
||||
groups: await this.getGroupsForUser(dn.toString()),
|
||||
campus: extensionAttribute1?.toString().split('-')[0] || '--'
|
||||
}
|
||||
|
||||
return ldapUser
|
||||
|
|
68
src/services/PrinterDiscoveryService.ts
Normal file
68
src/services/PrinterDiscoveryService.ts
Normal file
|
@ -0,0 +1,68 @@
|
|||
import snmp from 'net-snmp'
|
||||
import netmask from 'netmask'
|
||||
|
||||
export class PrinterDiscoveryService {
|
||||
private static async isPrinter(ip: string) {
|
||||
if (ip == '10.7.0.51') return false
|
||||
return new Promise((resolve, reject) => {
|
||||
const session = snmp.createSession(ip, 'public', { timeout: 1000 })
|
||||
|
||||
const CHECK_OID = '1.3.6.1.2.1.1.1.0'
|
||||
const CHECK_STRING = 'KYOCERA Document Solutions Printing System'
|
||||
|
||||
session.get(
|
||||
[CHECK_OID],
|
||||
(error: any, varbinds: any) => {
|
||||
if (error) {
|
||||
resolve(false)
|
||||
} else {
|
||||
if (varbinds[0].value.toString() === CHECK_STRING) {
|
||||
resolve(true)
|
||||
} else {
|
||||
resolve(false)
|
||||
}
|
||||
}
|
||||
session.close()
|
||||
},
|
||||
{ timeout: 1000 }
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// Check every IP in the range for a printer and return an array of IPs that are printers
|
||||
static async discovery(cdir: string): Promise<string[]> {
|
||||
const printers: string[] = []
|
||||
const blockIPs: string[] = []
|
||||
|
||||
try {
|
||||
const block = new netmask.Netmask(cdir)
|
||||
|
||||
const BLACK_LISTED_IPS = ['10.7.1.1', '10.7.0.51']
|
||||
|
||||
block.forEach(ip => {
|
||||
if (!BLACK_LISTED_IPS.includes(ip)) blockIPs.push(ip)
|
||||
})
|
||||
} catch (err) {
|
||||
throw new Error('Invalid IP CIDR')
|
||||
}
|
||||
|
||||
try {
|
||||
await Promise.allSettled(
|
||||
blockIPs.map(async ip => {
|
||||
try {
|
||||
if (await PrinterDiscoveryService.isPrinter(ip)) {
|
||||
printers.push(ip)
|
||||
console.log(`Found printer at ${ip}!`)
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.log(`Error checking ${ip}: ${error.message}`)
|
||||
}
|
||||
})
|
||||
)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
|
||||
return printers
|
||||
}
|
||||
}
|
|
@ -93,7 +93,7 @@ export class PrinterStatusService {
|
|||
private deBufferizeVarbinds(varbinds: Varbind[]) {
|
||||
const varbindsString: VarbindString[] = []
|
||||
|
||||
varbinds.forEach((varbind: Varbind) => {
|
||||
varbinds?.forEach((varbind: Varbind) => {
|
||||
if (varbind.value instanceof Buffer)
|
||||
varbindsString.push({ ...varbind, value: varbind.value.toString() })
|
||||
else varbindsString.push({ ...varbind, value: varbind.value })
|
||||
|
@ -144,7 +144,7 @@ export class PrinterStatusService {
|
|||
if (typeof current === 'undefined' || typeof max === 'undefined')
|
||||
throw new Error('current or max is undefined')
|
||||
|
||||
return (+current! / +max!) * 100
|
||||
return Math.floor((+current! / +max!) * 100)
|
||||
}
|
||||
|
||||
private objectIDsToPrinterInfo(varbinds: Varbind[]): PrinterInfo {
|
||||
|
|
Loading…
Reference in New Issue
Block a user