@daemonitor/plugins 1.1.5 → 1.1.7

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.
@@ -54,11 +54,24 @@ function parseNum(s) {
54
54
  const m = /(-?\d+(?:\.\d+)?)/.exec(s || "");
55
55
  return m ? Number(m[1]) : undefined;
56
56
  }
57
+ // net-snmp renders octet strings that contain a NUL/non-printable byte (e.g. HP's
58
+ // NUL-terminated "Black Cartridge 81A HP CF281A\0") as space-separated hex.
59
+ // Decode that back to text, and strip control chars from any value.
60
+ function decodeSnmp(s) {
61
+ const t = s.trim();
62
+ if (/^([0-9A-Fa-f]{2})(\s+[0-9A-Fa-f]{2})+\s*$/.test(t)) {
63
+ const bytes = t.split(/\s+/).map((h) => parseInt(h, 16));
64
+ return Buffer.from(bytes).toString("latin1").replace(/[\x00-\x1f\x7f]+/g, "").trim();
65
+ }
66
+ return t.replace(/[\x00-\x1f\x7f]+/g, "").trim();
67
+ }
57
68
  async function snmpGet(t, oid, timeout = 8000) {
58
69
  try {
59
70
  const { stdout } = await pexec("snmpget", ["-v", t.version || "2c", "-c", t.community || "public", "-Ovq", "-t", "2", "-r", "1", t.ip, oid], { timeout });
60
- const v = stdout.trim().replace(/^"|"$/g, "");
61
- return v && !/No Such|no response|Timeout/i.test(v) ? v : null;
71
+ const raw = stdout.trim().replace(/^"|"$/g, "");
72
+ if (!raw || /No Such|no response|Timeout/i.test(raw))
73
+ return null;
74
+ return decodeSnmp(raw);
62
75
  }
63
76
  catch {
64
77
  return null;
@@ -67,7 +80,7 @@ async function snmpGet(t, oid, timeout = 8000) {
67
80
  async function snmpWalk(t, oid, timeout = 10000) {
68
81
  try {
69
82
  const { stdout } = await pexec("snmpwalk", ["-v", t.version || "2c", "-c", t.community || "public", "-Oqv", "-t", "2", "-r", "1", t.ip, oid], { timeout });
70
- return stdout.split("\n").map((l) => l.trim().replace(/^"|"$/g, "")).filter(Boolean);
83
+ return stdout.split("\n").map((l) => decodeSnmp(l.trim().replace(/^"|"$/g, ""))).filter(Boolean);
71
84
  }
72
85
  catch {
73
86
  return [];
@@ -164,7 +177,9 @@ async function snmpWalkIndexed(t, oid, timeout = 10000) {
164
177
  const m = /^(\S+)\s*=\s*[^:]+:\s*(.*)$/.exec(l.trim());
165
178
  if (!m)
166
179
  return null;
167
- const idx = m[1].startsWith(oid) ? m[1].slice(oid.length).replace(/^\./, "") : m[1];
180
+ // snmpwalk -On emits a leading dot (".1.3.6…") the bare OID lacks.
181
+ const clean = m[1].replace(/^\./, "");
182
+ const idx = clean.startsWith(oid) ? clean.slice(oid.length).replace(/^\./, "") : clean;
168
183
  return { idx, val: m[2].trim().replace(/^"|"$/g, "") };
169
184
  }).filter(Boolean);
170
185
  }
@@ -199,11 +214,19 @@ async function collectQnap(t) {
199
214
  const [st, tp, sm, ds] = await Promise.all([
200
215
  snmpGet(t, QNAP.hdStatus + i), snmpGet(t, QNAP.hdTemp + i), snmpGet(t, QNAP.hdSmart + i), snmpGet(t, QNAP.hdDescr + i),
201
216
  ]);
202
- if (st == null && tp == null)
217
+ const status = st != null ? Number(st) : undefined;
218
+ const tempC = parseC(tp);
219
+ // Empty bay: no readable temp and a non-ready (negative/absent, e.g. -5)
220
+ // status. Skip it — an empty slot isn't a failed disk.
221
+ if (tempC == null && (status == null || status < 0))
203
222
  continue;
204
- disks.push({ slot: i, descr: ds || undefined, tempC: parseC(tp), status: st != null ? Number(st) : undefined, smart: sm || undefined });
223
+ disks.push({ slot: i, descr: ds || undefined, tempC, status, smart: sm && sm !== "--" ? sm : undefined });
205
224
  }
206
- const badDisks = disks.filter((d) => (d.status != null && d.status !== 0) || (d.smart && !/good|normal/i.test(d.smart)) || (d.tempC != null && d.tempC >= tempWarn));
225
+ // A present disk is bad only on a real error status, an explicitly-bad SMART
226
+ // verdict, or overheating — not a blank "--" or an empty bay.
227
+ const badDisks = disks.filter((d) => (d.status != null && d.status !== 0) ||
228
+ (typeof d.smart === "string" && /warn|abnormal|fail|error|bad|caution/i.test(d.smart)) ||
229
+ (d.tempC != null && d.tempC >= tempWarn));
207
230
  // Volumes — the CACHEDEVn_DATA mounts from hrStorage.
208
231
  const volumes = [];
209
232
  for (const row of storDescrs.filter((r) => /CACHEDEV\d+_DATA$/.test(r.val))) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daemonitor/plugins",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
4
4
  "description": "daemonitor plugins",
5
5
  "types": "types/index.d.ts",
6
6
  "main": "dist/index.js",