diff --git a/globals.d.ts b/globals.d.ts new file mode 100644 index 0000000..3405069 --- /dev/null +++ b/globals.d.ts @@ -0,0 +1 @@ +declare module 'net-snmp' diff --git a/package-lock.json b/package-lock.json index 9c4696e..2f0295f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "ifms-healthtracker", + "name": "ifms-printer-manager", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "ifms-healthtracker", + "name": "ifms-printer-manager", "version": "1.0.0", "license": "ISC", "dependencies": { @@ -25,6 +25,7 @@ "nodemon": "^2.0.22", "prisma": "^4.15.0", "rimraf": "^5.0.1", + "ts-node": "^10.9.1", "tsx": "^3.12.7", "typescript": "^5.1.3" } @@ -48,6 +49,18 @@ "node": ">= 10" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild-kit/cjs-loader": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/@esbuild-kit/cjs-loader/-/cjs-loader-2.4.2.tgz", @@ -447,6 +460,31 @@ "node": ">=12" } }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -489,6 +527,30 @@ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.15.0-28.8fbc245156db7124f997f4cecdd8d1219e360944.tgz", "integrity": "sha512-sVOig4tjGxxlYaFcXgE71f/rtFhzyYrfyfNFUsxCIEJyVKU9rdOWIlIwQ2NQ7PntvGnn+x0XuFo4OC1jvPJKzg==" }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, "node_modules/@types/asn1": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@types/asn1/-/asn1-0.2.0.tgz", @@ -620,6 +682,27 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", + "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", @@ -657,6 +740,12 @@ "node": ">= 8" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -896,6 +985,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "node_modules/cron-validate": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/cron-validate/-/cron-validate-1.4.5.tgz", @@ -949,6 +1044,15 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dotenv": { "version": "16.1.4", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.1.4.tgz", @@ -1703,6 +1807,12 @@ "node": "14 || >=16.14" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -2427,6 +2537,49 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tsx": { "version": "3.12.7", "resolved": "https://registry.npmjs.org/tsx/-/tsx-3.12.7.tgz", @@ -2499,6 +2652,12 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -2618,6 +2777,15 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/yup": { "version": "0.32.9", "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.9.tgz", diff --git a/package.json b/package.json index 749a21c..662068e 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,13 @@ "main": "index.js", "type": "module", "scripts": { - "clean": "rimraf ./dist", - "build": "npm run clean && tsc", + "clean": "rimraf ./dist ./public", + "build:server": "npm run clean && tsc", + "build:web": "cd web && npm run build", + "build": "npm run build:server && npm run build:web", "start": "NODE_ENV=production node dist", - "dev": "nodemon --ext js,ts,mts,mjs,json,prisma --exec \"tsx src/index.ts\"" + "dev": "nodemon --ext js,ts,mts,mjs,json,prisma --exec \"tsx src/index.ts\"", + "devLegacy": "NODE_OPTIONS=\"--loader ts-node/esm\" node ./src/index.ts" }, "prisma": { "seed": "tsx prisma/seed.ts" @@ -23,6 +26,7 @@ "nodemon": "^2.0.22", "prisma": "^4.15.0", "rimraf": "^5.0.1", + "ts-node": "^10.9.1", "tsx": "^3.12.7", "typescript": "^5.1.3" }, diff --git a/prisma/migrations/20230620133458_init/migration.sql b/prisma/migrations/20230620175043_init/migration.sql similarity index 94% rename from prisma/migrations/20230620133458_init/migration.sql rename to prisma/migrations/20230620175043_init/migration.sql index 17cbf32..9d13ab9 100644 --- a/prisma/migrations/20230620133458_init/migration.sql +++ b/prisma/migrations/20230620175043_init/migration.sql @@ -23,7 +23,7 @@ CREATE TABLE "Printer" ( "serialNumber" TEXT, "hostname" TEXT, "ip" TEXT NOT NULL, - "model" TEXT, + "model" TEXT NOT NULL, "blackTonerModel" TEXT, "cyanTonerModel" TEXT, "magentaTonerModel" TEXT, @@ -54,5 +54,8 @@ CREATE UNIQUE INDEX "User_username_key" ON "User"("username"); -- CreateIndex CREATE UNIQUE INDEX "Printer_serialNumber_key" ON "Printer"("serialNumber"); +-- CreateIndex +CREATE UNIQUE INDEX "Printer_ip_key" ON "Printer"("ip"); + -- AddForeignKey ALTER TABLE "PrinterStatus" ADD CONSTRAINT "PrinterStatus_printerId_fkey" FOREIGN KEY ("printerId") REFERENCES "Printer"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20230620191614_cascade/migration.sql b/prisma/migrations/20230620191614_cascade/migration.sql new file mode 100644 index 0000000..e921b79 --- /dev/null +++ b/prisma/migrations/20230620191614_cascade/migration.sql @@ -0,0 +1,5 @@ +-- DropForeignKey +ALTER TABLE "PrinterStatus" DROP CONSTRAINT "PrinterStatus_printerId_fkey"; + +-- AddForeignKey +ALTER TABLE "PrinterStatus" ADD CONSTRAINT "PrinterStatus_printerId_fkey" FOREIGN KEY ("printerId") REFERENCES "Printer"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index c4c29dd..ef5d5bf 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -38,8 +38,8 @@ model Printer { serialNumber String? @unique hostname String? - ip String - model String? + ip String @unique + model String blackTonerModel String? cyanTonerModel String? @@ -49,7 +49,7 @@ model Printer { createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt - PrinterStatus PrinterStatus[] + status PrinterStatus[] } model PrinterStatus { @@ -65,5 +65,5 @@ model PrinterStatus { createdAt DateTime @default(now()) printerId Int - printer Printer @relation(fields: [printerId], references: [id]) + printer Printer @relation(fields: [printerId], references: [id], onDelete: Cascade) } diff --git a/src/controllers/PrinterController.ts b/src/controllers/PrinterController.ts index cc2f084..c6c5af2 100644 --- a/src/controllers/PrinterController.ts +++ b/src/controllers/PrinterController.ts @@ -2,6 +2,7 @@ import { Request, Response, Router } from 'express' import { hasRolesMiddleware } from '../middlewares/hasRolesMiddleware.js' import { prisma } from '../prisma.js' +import { PrinterStatusService } from '../services/PrinterStatusService.js' const router = Router() @@ -21,7 +22,7 @@ class PrinterController { const printer = await prisma.printer.findUnique({ where: { id: Number(id) }, include: { - PrinterStatus: { + status: { where: { createdAt: { gte @@ -35,27 +36,44 @@ class PrinterController { } static async create(req: Request, res: Response) { - const { friendlyName, ip, location } = req.body + const { friendlyName, ip } = req.body - const printer = await prisma.printer.create({ - data: { friendlyName, ip, location } - }) + try { + const model = await PrinterStatusService.getPrinterModel(ip) + const printer = await prisma.printer.create({ + data: { friendlyName, ip, model } + }) - // Run snmp here + new PrinterStatusService(printer) - res.json(printer) + res.json(printer) + } catch (e) { + res + .status(400) + .json({ error: 'Este endereço não é de uma impressora suportada.' }) + return + } } static async edit(req: Request, res: Response) { const { id } = req.params - const { friendlyName, ip, location } = req.body + const { friendlyName, ip } = req.body - const printer = await prisma.printer.update({ - where: { id: Number(id) }, - data: { friendlyName, ip, location } + // Verify if printer exists + const printerExists = await prisma.printer.findUnique({ + where: { id: Number(id) } }) - res.json(printer) + if (printerExists) { + const printer = await prisma.printer.update({ + where: { id: Number(id) }, + data: { friendlyName, ip } + }) + + res.json(printer) + } else { + res.status(400).json({ error: 'Printer not found' }) + } } static async delete(req: Request, res: Response) { @@ -70,7 +88,9 @@ class PrinterController { router.use(hasRolesMiddleware(['ADMIN', 'INSPECTOR'])) router.get('/', PrinterController.index) +router.post('/', PrinterController.create) router.get('/:id', PrinterController.show) router.put('/:id', PrinterController.edit) +router.delete('/:id', PrinterController.delete) export default router diff --git a/src/controllers/PrinterStatusController.ts b/src/controllers/PrinterStatusController.ts new file mode 100644 index 0000000..12b0242 --- /dev/null +++ b/src/controllers/PrinterStatusController.ts @@ -0,0 +1,20 @@ +import { Router, Request, Response } from 'express' +import { prisma } from '../prisma.js' +import { PrinterStatusService } from '../services/PrinterStatusService.js' + +const router = Router() + +class PrinterStatusController { + static async update(req: Request, res: Response) { + const printers = await prisma.printer.findMany() + + printers.forEach(async printer => { + new PrinterStatusService(printer) + }) + + res.json({ message: 'Updating printer status' }) + } +} + +router.get('/update', PrinterStatusController.update) +export default router diff --git a/src/index.ts b/src/index.ts index a849f75..a15b537 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,5 @@ import 'dotenv/config' import * as path from 'node:path' -import * as process from 'node:process' import { fileURLToPath } from 'node:url' import { app } from './server.js' @@ -13,7 +12,7 @@ app.listen(PORT, () => { console.log( `Running in ${ process.env.NODE_ENV == 'production' ? 'PRODUCTION' : 'DEVELOPMENT' - } mode. Server listening on port ${PORT}` + } mode. \nServer listening http://127.0.0.1:${PORT}` ) }) @@ -24,8 +23,9 @@ const bree = new Bree({ logger: false, jobs: [ { - name: 'printerStatus', - interval: '2s' + name: 'updatePrinterStatus', + interval: process.env.UPDATE_INTERVAL || '10m', + timeout: 0 } ] }) diff --git a/src/jobs/printerStatus.ts b/src/jobs/printerStatus.ts deleted file mode 100644 index 7af98bd..0000000 --- a/src/jobs/printerStatus.ts +++ /dev/null @@ -1,5 +0,0 @@ -export function snmpUpdate() { - console.log('snmpUpdate!!!!!') -} - -snmpUpdate() diff --git a/src/jobs/updatePrinterStatus.ts b/src/jobs/updatePrinterStatus.ts new file mode 100644 index 0000000..9ba5fa6 --- /dev/null +++ b/src/jobs/updatePrinterStatus.ts @@ -0,0 +1,14 @@ +import { prisma } from '../prisma.js' +import { PrinterStatusService } from '../services/PrinterStatusService.js' + +function updatePrinterStatus() { + console.log(`Updating printer status ${new Date().toISOString()}`) + + prisma.printer.findMany().then(printers => { + printers.forEach(async printer => { + new PrinterStatusService(printer) + }) + }) +} + +updatePrinterStatus() diff --git a/src/repositories/ObjectIDRepository.ts b/src/repositories/ObjectIDRepository.ts new file mode 100644 index 0000000..b35eb8b --- /dev/null +++ b/src/repositories/ObjectIDRepository.ts @@ -0,0 +1,109 @@ +export type PrinterObjectIds = { + model: string + objectIds: { + model: string + serialNumber: string + counter: string + location: string + toners: { + black: { + current: string + max: string + model: string + } + cyan?: { + current: string + max: string + model: string + } + magenta?: { + current: string + max: string + model: string + } + yellow?: { + current: string + max: string + model: string + } + } + } +} + +const objectIds: PrinterObjectIds[] = [ + { + model: 'ECOSYS M3655idn', + objectIds: { + model: '1.3.6.1.2.1.25.3.2.1.3.1', + serialNumber: '1.3.6.1.2.1.43.5.1.1.17.1', + counter: '1.3.6.1.4.1.1347.43.10.1.1.12.1.1', + location: '1.3.6.1.2.1.1.6.0', + toners: { + black: { + current: '1.3.6.1.2.1.43.11.1.1.9.1.1', + max: '1.3.6.1.2.1.43.11.1.1.8.1.1', + model: '1.3.6.1.2.1.43.11.1.1.6.1.1' + } + } + } + }, + { + model: 'ECOSYS P6235cdn', + objectIds: { + model: '1.3.6.1.2.1.25.3.2.1.3.1', + serialNumber: '1.3.6.1.2.1.43.5.1.1.17.1', + counter: '1.3.6.1.4.1.1347.43.10.1.1.12.1.1', + location: '1.3.6.1.2.1.1.6.0', + toners: { + black: { + current: '1.3.6.1.2.1.43.11.1.1.9.1.4', + max: '1.3.6.1.2.1.43.11.1.1.8.1.4', + model: '1.3.6.1.2.1.43.11.1.1.6.1.4' + }, + cyan: { + current: '1.3.6.1.2.1.43.11.1.1.9.1.1', + max: '1.3.6.1.2.1.43.11.1.1.8.1.1', + model: '1.3.6.1.2.1.43.11.1.1.6.1.1' + }, + magenta: { + current: '1.3.6.1.2.1.43.11.1.1.9.1.2', + max: '1.3.6.1.2.1.43.11.1.1.8.1.2', + model: '1.3.6.1.2.1.43.11.1.1.6.1.2' + }, + yellow: { + current: '1.3.6.1.2.1.43.11.1.1.9.1.3', + max: '1.3.6.1.2.1.43.11.1.1.8.1.3', + model: '1.3.6.1.2.1.43.11.1.1.6.1.3' + } + } + } + }, + { + model: 'ECOSYS M2040dn', + objectIds: { + model: '1.3.6.1.2.1.25.3.2.1.3.1', + serialNumber: '1.3.6.1.2.1.43.5.1.1.17.1', + counter: '1.3.6.1.4.1.1347.43.10.1.1.12.1.1', + location: '1.3.6.1.2.1.1.6.0', + toners: { + black: { + current: '1.3.6.1.2.1.43.11.1.1.9.1.1', + max: '1.3.6.1.2.1.43.11.1.1.8.1.1', + model: '1.3.6.1.2.1.43.11.1.1.6.1.1' + } + } + } + } +] + +export class objectIdsRepository { + private constructor() {} + + static getPrinterObjectIds(model: string): PrinterObjectIds { + const printerObjectIds = objectIds.find(printer => printer.model === model) + + if (!printerObjectIds) throw new Error('Model not found') + + return printerObjectIds + } +} diff --git a/src/server.ts b/src/server.ts index b9e36bf..5d2d25b 100644 --- a/src/server.ts +++ b/src/server.ts @@ -6,6 +6,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' export const app = express() @@ -16,6 +17,7 @@ app.use(populateUserMiddleware) app.use('/api/login', LoginRouter) app.use('/api/printer', PrinterRouter) +app.use('/api/printer-status', PrinterStatusRouter) app.get('/api/me', authMiddleware, async (req: Request, res: Response) => res.json(res.locals.user) diff --git a/src/services/PrinterStatusService.ts b/src/services/PrinterStatusService.ts new file mode 100644 index 0000000..f4e43b1 --- /dev/null +++ b/src/services/PrinterStatusService.ts @@ -0,0 +1,213 @@ +import snmp from 'net-snmp' +import { Printer, PrinterStatus } from '@prisma/client' +import { prisma } from '../prisma.js' +import { + objectIdsRepository, + PrinterObjectIds +} from '../repositories/ObjectIDRepository.js' + +type VarbindString = { + oid: string + type: number + value: string +} + +export type Varbind = { + oid: string + type: number + value: string | Buffer +} + +export type PrinterInfo = { + serialNumber: string + counter: number + location: string + toners: { + black: { + level: number + model: string + } + cyan?: { + level: number + model: string + } + magenta?: { + level: number + model: string + } + yellow?: { + level: number + model: string + } + } +} + +export class PrinterStatusService { + constructor(private printer: Printer) { + this.getPrinterInfo().then(async printerStatus => { + await prisma.printer.update({ + where: { id: this.printer.id }, + data: { + serialNumber: printerStatus.serialNumber, + location: printerStatus.location, + blackTonerModel: printerStatus.toners.black.model, + cyanTonerModel: printerStatus.toners.cyan?.model, + magentaTonerModel: printerStatus.toners.magenta?.model, + yellowTonerModel: printerStatus.toners.yellow?.model, + + status: { + create: { + counter: printerStatus.counter, + tonerBlackLevel: printerStatus.toners.black.level, + tonerCyanLevel: printerStatus.toners.cyan?.level, + tonerMagentaLevel: printerStatus.toners.magenta?.level, + tonerYellowLevel: printerStatus.toners.yellow?.level + } + } + } + }) + }) + } + + private objectIdsArray(): string[] { + const oIDsArray: string[] = [] + + function extractObjValues(obj: any) { + for (let key in obj) { + if (typeof obj[key] === 'object') { + extractObjValues(obj[key]) + } else { + const oID = obj[key] + oIDsArray.push(oID) + } + } + } + + extractObjValues( + objectIdsRepository.getPrinterObjectIds(this.printer.model).objectIds + ) + + return oIDsArray + } + + private deBufferizeVarbinds(varbinds: Varbind[]) { + const varbindsString: VarbindString[] = [] + + varbinds.forEach((varbind: Varbind) => { + if (varbind.value instanceof Buffer) + varbindsString.push({ ...varbind, value: varbind.value.toString() }) + else varbindsString.push({ ...varbind, value: varbind.value }) + }) + + return varbindsString + } + + static getPrinterModel(ip: string): Promise { + return new Promise((resolve, reject) => { + const snmpSession = snmp.createSession(ip, 'public') + snmpSession.get( + ['1.3.6.1.2.1.25.3.2.1.3.1'], + (error: any, varbinds: any) => { + if (error) { + reject(error) + } else { + resolve(varbinds[0].value.toString()) + } + snmpSession.close() + } + ) + }) + } + + async getPrinterInfo(): Promise { + return new Promise((resolve, reject) => { + const session = snmp.createSession(this.printer.ip, 'public') + + const oIDsArray = this.objectIdsArray() + + session.get(oIDsArray, (error: any, varbinds: Varbind[]) => { + if (error) reject(error) + + const varbindsString = this.deBufferizeVarbinds(varbinds) + const info = this.objectIDsToPrinterInfo(varbindsString) + + resolve(info) + session.close() + }) + }) + } + + private calcTonerLevelPercentage( + current: string | undefined, + max: string | undefined + ) { + if (typeof current === 'undefined' || typeof max === 'undefined') + throw new Error('current or max is undefined') + + return (+current! / +max!) * 100 + } + + private objectIDsToPrinterInfo(varbinds: Varbind[]): PrinterInfo { + const snmpInfo = this.deBufferizeVarbinds(varbinds) + + const { objectIds }: PrinterObjectIds = + objectIdsRepository.getPrinterObjectIds(this.printer.model) + + const printerInfo: PrinterInfo = { + serialNumber: snmpInfo.find(x => x.oid === objectIds.serialNumber) + ?.value as string, + counter: Number(snmpInfo.find(x => x.oid === objectIds.counter)?.value), + location: snmpInfo.find(x => x.oid === objectIds.location) + ?.value as string, + toners: { + black: { + level: this.calcTonerLevelPercentage( + snmpInfo.find(x => x.oid === objectIds.toners.black.current)?.value, + snmpInfo.find(x => x.oid === objectIds.toners.black.max)?.value + ), + model: snmpInfo.find(x => x.oid === objectIds.toners.black.model) + ?.value as string + }, + cyan: objectIds.toners.cyan + ? { + level: this.calcTonerLevelPercentage( + snmpInfo.find(x => x.oid === objectIds.toners.cyan?.current) + ?.value, + snmpInfo.find(x => x.oid === objectIds.toners.cyan?.max)?.value + ), + model: snmpInfo.find(x => x.oid === objectIds.toners.cyan?.model) + ?.value as string + } + : undefined, + magenta: objectIds.toners.magenta + ? { + level: this.calcTonerLevelPercentage( + snmpInfo.find(x => x.oid === objectIds.toners.magenta?.current) + ?.value, + snmpInfo.find(x => x.oid === objectIds.toners.magenta?.max) + ?.value + ), + model: snmpInfo.find( + x => x.oid === objectIds.toners.magenta?.model + )?.value as string + } + : undefined, + yellow: objectIds.toners.yellow + ? { + level: this.calcTonerLevelPercentage( + snmpInfo.find(x => x.oid === objectIds.toners.yellow?.current) + ?.value, + snmpInfo.find(x => x.oid === objectIds.toners.yellow?.max) + ?.value + ), + model: snmpInfo.find( + x => x.oid === objectIds.toners.yellow?.model + )?.value as string + } + : undefined + } + } + + return printerInfo + } +} diff --git a/src/services/UserService.ts b/src/services/UserService.ts index 5ff2389..b650c0a 100644 --- a/src/services/UserService.ts +++ b/src/services/UserService.ts @@ -14,12 +14,12 @@ export class UserService { if (!ldapUser) throw new Error('User not found!') - const user = { + const user: Omit = { username: ldapUser.username, displayName: ldapUser.displayName, mail: ldapUser.mail, thumbnailPhoto: ldapUser.thumbnailPhoto, - roles: [] as User['roles'] + roles: [] } ldapUser.groups?.forEach(group => { diff --git a/tsconfig.json b/tsconfig.json index fdba28f..b29f21a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -106,5 +106,5 @@ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ }, - "include": ["src/**/*", "jobs/**/*"] + "include": ["src/**/*", "jobs/**/*", "globals.d.ts"] }