@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
|
|
61
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
223
|
+
disks.push({ slot: i, descr: ds || undefined, tempC, status, smart: sm && sm !== "--" ? sm : undefined });
|
|
205
224
|
}
|
|
206
|
-
|
|
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))) {
|