@daemonitor/plugins 1.1.2 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/plugins/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { createWebsitePlugin } from "./monitoring/WebsitePlugin";
|
|
|
7
7
|
import { createWordpressPlugin } from "./monitoring/WordpressPlugin";
|
|
8
8
|
import { createLogWatchPlugin } from "./monitoring/LogWatchPlugin";
|
|
9
9
|
import { createNetScanPlugin } from "./monitoring/NetScanPlugin";
|
|
10
|
+
import { createSnmpPlugin } from "./monitoring/SnmpPlugin";
|
|
10
11
|
// In future, implement other plugin factory functions using the same pattern
|
|
11
12
|
// import { createOSPlugin } from "./monitoring/OSPlugin"
|
|
12
13
|
// import { createMongoDBPlugin } from "./monitoring/MongoDBPlugin"
|
|
@@ -25,6 +26,7 @@ export default {
|
|
|
25
26
|
Wordpress: createWordpressPlugin,
|
|
26
27
|
Logwatch: createLogWatchPlugin,
|
|
27
28
|
Netscan: createNetScanPlugin,
|
|
29
|
+
Snmp: createSnmpPlugin,
|
|
28
30
|
// CloudflarePlugin: createCloudflarePlugin,
|
|
29
31
|
// EwelinkPlugin: createEwelinkPlugin,
|
|
30
32
|
// MongoDBPlugin: createMongoDBPlugin,
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { createMonitoringPlugin } from "../../lib/MonitoringPlugin";
|
|
4
|
+
// SNMP monitor. First target class: printers (standard Printer-MIB) — model,
|
|
5
|
+
// serial, lifetime page count, and per-supply toner/consumable levels. Shells
|
|
6
|
+
// out to net-snmp's snmpget/snmpwalk (add `snmp` to the client image) rather
|
|
7
|
+
// than pulling an npm SNMP stack, matching the other CLI-backed plugins.
|
|
8
|
+
//
|
|
9
|
+
// One client_state per target (type "snmp-printer", uid snmp-<slug>). Severity
|
|
10
|
+
// is decided server-side (clientstate/update.put.ts): a supply at/under its
|
|
11
|
+
// warn threshold → warning; an error/offline printer status → error.
|
|
12
|
+
const pexec = promisify(execFile);
|
|
13
|
+
// Printer-MIB / HOST-RESOURCES OIDs
|
|
14
|
+
const OID = {
|
|
15
|
+
model: "1.3.6.1.2.1.25.3.2.1.3.1", // hrDeviceDescr
|
|
16
|
+
serial: "1.3.6.1.2.1.43.5.1.1.17.1", // prtGeneralSerialNumber
|
|
17
|
+
pages: "1.3.6.1.2.1.43.10.2.1.4.1.1", // prtMarkerLifeCount
|
|
18
|
+
status: "1.3.6.1.2.1.25.3.5.1.1.1", // hrPrinterStatus (3=idle,4=printing,5=warmup)
|
|
19
|
+
supplyDesc: "1.3.6.1.2.1.43.11.1.1.6", // prtMarkerSuppliesDescription (walk)
|
|
20
|
+
supplyMax: "1.3.6.1.2.1.43.11.1.1.8", // prtMarkerSuppliesMaxCapacity (walk)
|
|
21
|
+
supplyLevel: "1.3.6.1.2.1.43.11.1.1.9", // prtMarkerSuppliesLevel (walk)
|
|
22
|
+
};
|
|
23
|
+
const PRINTER_STATUS = { "1": "other", "2": "unknown", "3": "idle", "4": "printing", "5": "warmup" };
|
|
24
|
+
function slug(s) {
|
|
25
|
+
return s.replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
26
|
+
}
|
|
27
|
+
async function snmpGet(t, oid, timeout = 8000) {
|
|
28
|
+
try {
|
|
29
|
+
const { stdout } = await pexec("snmpget", ["-v", t.version || "2c", "-c", t.community || "public", "-Ovq", "-t", "2", "-r", "1", t.ip, oid], { timeout });
|
|
30
|
+
const v = stdout.trim().replace(/^"|"$/g, "");
|
|
31
|
+
return v && !/No Such|no response|Timeout/i.test(v) ? v : null;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function snmpWalk(t, oid, timeout = 10000) {
|
|
38
|
+
try {
|
|
39
|
+
const { stdout } = await pexec("snmpwalk", ["-v", t.version || "2c", "-c", t.community || "public", "-Oqv", "-t", "2", "-r", "1", t.ip, oid], { timeout });
|
|
40
|
+
return stdout.split("\n").map((l) => l.trim().replace(/^"|"$/g, "")).filter(Boolean);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function collectPrinter(t) {
|
|
47
|
+
const warnPct = t.tonerWarnPct ?? 10;
|
|
48
|
+
const [model, serial, pagesRaw, statusRaw, descs, maxes, levels] = await Promise.all([
|
|
49
|
+
snmpGet(t, OID.model),
|
|
50
|
+
snmpGet(t, OID.serial),
|
|
51
|
+
snmpGet(t, OID.pages),
|
|
52
|
+
snmpGet(t, OID.status),
|
|
53
|
+
snmpWalk(t, OID.supplyDesc),
|
|
54
|
+
snmpWalk(t, OID.supplyMax),
|
|
55
|
+
snmpWalk(t, OID.supplyLevel),
|
|
56
|
+
]);
|
|
57
|
+
// No response at all → the printer is unreachable / SNMP off.
|
|
58
|
+
if (model == null && serial == null && pagesRaw == null && !descs.length) {
|
|
59
|
+
return { name: t.name, ip: t.ip, kind: "printer", reachable: false };
|
|
60
|
+
}
|
|
61
|
+
// Pair descriptions with levels. prtMarkerSuppliesLevel semantics: -2 =
|
|
62
|
+
// unknown, -3 = "some remaining" (a level the device won't quantify); else a
|
|
63
|
+
// count out of MaxCapacity (which can itself be -2 = unknown).
|
|
64
|
+
const supplies = descs.map((desc, i) => {
|
|
65
|
+
const max = Number(maxes[i]);
|
|
66
|
+
const lvl = Number(levels[i]);
|
|
67
|
+
let pct = null;
|
|
68
|
+
if (lvl === -3)
|
|
69
|
+
pct = 100; // "ok / some remaining"
|
|
70
|
+
else if (lvl >= 0 && max > 0)
|
|
71
|
+
pct = Math.round((lvl / max) * 100);
|
|
72
|
+
return { name: desc, level: lvl, max, pct };
|
|
73
|
+
});
|
|
74
|
+
const low = supplies.filter((s) => s.pct != null && s.pct <= warnPct);
|
|
75
|
+
return {
|
|
76
|
+
name: t.name,
|
|
77
|
+
ip: t.ip,
|
|
78
|
+
kind: "printer",
|
|
79
|
+
reachable: true,
|
|
80
|
+
model: model || undefined,
|
|
81
|
+
serial: serial || undefined,
|
|
82
|
+
pages: pagesRaw != null ? Number(pagesRaw) : undefined,
|
|
83
|
+
status: statusRaw != null ? PRINTER_STATUS[statusRaw] || statusRaw : undefined,
|
|
84
|
+
supplies,
|
|
85
|
+
lowSupplies: low.map((s) => ({ name: s.name, pct: s.pct })),
|
|
86
|
+
warnPct,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export function createSnmpPlugin() {
|
|
90
|
+
let refreshTimer = null;
|
|
91
|
+
const targetsOf = (plugin) => {
|
|
92
|
+
const cfg = (plugin.config || {});
|
|
93
|
+
return (Array.isArray(cfg.targets) ? cfg.targets : [])
|
|
94
|
+
.filter((t) => t?.ip)
|
|
95
|
+
.map((t) => ({ ...t, community: t.community || cfg.community || "public" }));
|
|
96
|
+
};
|
|
97
|
+
const refreshFn = async (plugin) => {
|
|
98
|
+
await Promise.all(targetsOf(plugin).map(async (t) => {
|
|
99
|
+
try {
|
|
100
|
+
const data = t.kind === "printer" || !t.kind ? await collectPrinter(t) : { name: t.name, ip: t.ip, kind: t.kind };
|
|
101
|
+
await plugin.send(data, `snmp-${slug(t.name || t.ip)}`);
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
await plugin.send({ name: t.name, ip: t.ip, kind: t.kind || "printer", error: String(e?.message || e).slice(0, 160) }, `snmp-${slug(t.name || t.ip)}`);
|
|
105
|
+
}
|
|
106
|
+
}));
|
|
107
|
+
};
|
|
108
|
+
const monitorFn = async (plugin) => {
|
|
109
|
+
await refreshFn(plugin);
|
|
110
|
+
refreshTimer = setInterval(() => refreshFn(plugin), plugin.config?.refreshInterval || 300000);
|
|
111
|
+
};
|
|
112
|
+
const teardownFn = async () => {
|
|
113
|
+
if (refreshTimer)
|
|
114
|
+
clearInterval(refreshTimer);
|
|
115
|
+
refreshTimer = null;
|
|
116
|
+
};
|
|
117
|
+
return createMonitoringPlugin("snmp", "snmp", "SNMP monitor (printers: model, pages, toner levels)", async () => { }, monitorFn, refreshFn, teardownFn);
|
|
118
|
+
}
|
|
119
|
+
export default createSnmpPlugin;
|