@daemonitor/plugins 1.1.46 → 1.1.51
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 +2 -0
- package/dist/plugins/monitoring/Ga4Plugin.js +15 -1
- package/dist/plugins/monitoring/NetScanPlugin.js +58 -1
- package/dist/plugins/monitoring/OSPlugin.js +29 -0
- package/dist/plugins/monitoring/SupabasePlugin.js +151 -0
- package/dist/plugins/monitoring/SynologyPlugin.js +35 -19
- package/package.json +3 -7
package/dist/plugins/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import { createGa4Plugin } from "./monitoring/Ga4Plugin.js";
|
|
|
18
18
|
import { createNagiosPlugin } from "./monitoring/NagiosPlugin.js";
|
|
19
19
|
import { createTplinkSwitchPlugin } from "./monitoring/TplinkSwitchPlugin.js";
|
|
20
20
|
import { createCostPlugin } from "./monitoring/CostPlugin.js";
|
|
21
|
+
import { createSupabasePlugin } from "./monitoring/SupabasePlugin.js";
|
|
21
22
|
// In future, implement other plugin factory functions using the same pattern
|
|
22
23
|
// import { createOSPlugin } from "./monitoring/OSPlugin.js"
|
|
23
24
|
// import { createMongoDBPlugin } from "./monitoring/MongoDBPlugin.js"
|
|
@@ -47,6 +48,7 @@ export default {
|
|
|
47
48
|
Nagios: createNagiosPlugin,
|
|
48
49
|
TplinkSwitch: createTplinkSwitchPlugin,
|
|
49
50
|
Cost: createCostPlugin,
|
|
51
|
+
Supabase: createSupabasePlugin,
|
|
50
52
|
// CloudflarePlugin: createCloudflarePlugin,
|
|
51
53
|
// EwelinkPlugin: createEwelinkPlugin,
|
|
52
54
|
// MongoDBPlugin: createMongoDBPlugin,
|
|
@@ -175,6 +175,20 @@ async function inBatches(items, size, fn) {
|
|
|
175
175
|
out.push(...(await Promise.all(items.slice(i, i + size).map(fn))));
|
|
176
176
|
return out;
|
|
177
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* One more try after a timeout, so a slow moment doesn't warn for a whole poll.
|
|
180
|
+
* Only timeouts: a 403 or a quota error will say the same thing again.
|
|
181
|
+
*/
|
|
182
|
+
export async function retryTimeout(fn) {
|
|
183
|
+
try {
|
|
184
|
+
return await fn();
|
|
185
|
+
}
|
|
186
|
+
catch (e) {
|
|
187
|
+
if (e?.name !== "TimeoutError")
|
|
188
|
+
throw e;
|
|
189
|
+
return fn();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
178
192
|
export function createGa4Plugin() {
|
|
179
193
|
let refreshTimer = null;
|
|
180
194
|
const collect = async (cfg) => {
|
|
@@ -203,7 +217,7 @@ export function createGa4Plugin() {
|
|
|
203
217
|
// One property failing (no access to its data, quota) must not hide the rest.
|
|
204
218
|
try {
|
|
205
219
|
const [batch, realtime, streams] = await Promise.all([
|
|
206
|
-
post(`${DATA}/properties/${p.id}:batchRunReports`, batchBody(cfg.excludePages)),
|
|
220
|
+
retryTimeout(() => post(`${DATA}/properties/${p.id}:batchRunReports`, batchBody(cfg.excludePages))),
|
|
207
221
|
post(`${DATA}/properties/${p.id}:runRealtimeReport`, { metrics: [{ name: "activeUsers" }] }).catch(() => null),
|
|
208
222
|
get(`${ADMIN}/properties/${p.id}/dataStreams?pageSize=50`).catch(() => null),
|
|
209
223
|
]);
|
|
@@ -36,6 +36,57 @@ async function pingHost(ip, count, timeout) {
|
|
|
36
36
|
return { up: false };
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Read one neighbour-table entry from `ip -j neigh show <ip>` output.
|
|
41
|
+
* REACHABLE means the device answered ARP within the last few seconds.
|
|
42
|
+
*/
|
|
43
|
+
export function neighState(json) {
|
|
44
|
+
try {
|
|
45
|
+
const e = JSON.parse(json)?.[0];
|
|
46
|
+
if (!e)
|
|
47
|
+
return null;
|
|
48
|
+
const state = Array.isArray(e.state) ? e.state[0] : String(e.state || "");
|
|
49
|
+
return { state, ...(e.lladdr ? { mac: String(e.lladdr) } : {}) };
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
let ipMissing = false;
|
|
56
|
+
/**
|
|
57
|
+
* Is the device on the wire even though it ignored ping? The failed ping still
|
|
58
|
+
* made the kernel ARP for it, and a device that drops ICMP almost always
|
|
59
|
+
* answers ARP, leaving a REACHABLE neighbour entry. DELAY and PROBE mean the
|
|
60
|
+
* kernel is still checking a stale entry, so look again after it has had time.
|
|
61
|
+
* Linux only, and needs the `ip` command (iproute2); anything else says no.
|
|
62
|
+
*/
|
|
63
|
+
export async function arpReachable(ip) {
|
|
64
|
+
if (process.platform !== "linux" || ipMissing)
|
|
65
|
+
return null;
|
|
66
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
67
|
+
let stdout;
|
|
68
|
+
try {
|
|
69
|
+
;
|
|
70
|
+
({ stdout } = await pexec("ip", ["-j", "neigh", "show", ip], { timeout: 3000 }));
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
if (e?.code === "ENOENT") {
|
|
74
|
+
ipMissing = true;
|
|
75
|
+
console.warn("netscan: `ip` (iproute2) is not installed, so devices that ignore ping can't be confirmed by ARP");
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const n = neighState(stdout);
|
|
80
|
+
if (!n)
|
|
81
|
+
return null;
|
|
82
|
+
if (n.state === "REACHABLE")
|
|
83
|
+
return { mac: n.mac };
|
|
84
|
+
if (n.state !== "DELAY" && n.state !== "PROBE")
|
|
85
|
+
return null;
|
|
86
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
39
90
|
export function createNetScanPlugin() {
|
|
40
91
|
let refreshTimer = null;
|
|
41
92
|
const devicesOf = (plugin) => {
|
|
@@ -51,12 +102,18 @@ export function createNetScanPlugin() {
|
|
|
51
102
|
// refresh interval.
|
|
52
103
|
await Promise.all(devices.map(async (d) => {
|
|
53
104
|
const r = await pingHost(d.ip, count, timeout);
|
|
105
|
+
if (!r.up && d.arp !== false) {
|
|
106
|
+
const arp = await arpReachable(d.ip);
|
|
107
|
+
if (arp)
|
|
108
|
+
Object.assign(r, { up: true, via: "arp", ...(arp.mac ? { mac: arp.mac } : {}) });
|
|
109
|
+
}
|
|
54
110
|
await plugin.send({
|
|
55
111
|
name: d.name,
|
|
56
112
|
ip: d.ip,
|
|
57
113
|
type: d.type || "host",
|
|
58
|
-
mac: d.mac,
|
|
114
|
+
mac: d.mac || r.mac,
|
|
59
115
|
up: r.up,
|
|
116
|
+
...(r.via ? { via: r.via } : {}),
|
|
60
117
|
latency: r.latency,
|
|
61
118
|
loss: r.loss,
|
|
62
119
|
warnMs: d.warnMs ?? 200,
|
|
@@ -125,6 +125,34 @@ function availableMem() {
|
|
|
125
125
|
}
|
|
126
126
|
return freemem();
|
|
127
127
|
}
|
|
128
|
+
// Swap and page cache, in bytes. Swap in use on a box that still has RAM
|
|
129
|
+
// available is old pages pushed out, not pressure, so the card shows both.
|
|
130
|
+
// Linux reads /proc/meminfo; macOS has swap only (sysctl vm.swapusage).
|
|
131
|
+
function memDetail() {
|
|
132
|
+
if (platform() === "linux") {
|
|
133
|
+
try {
|
|
134
|
+
const info = readFileSync("/proc/meminfo", "utf8");
|
|
135
|
+
const kb = (key) => parseInt(info.match(new RegExp(`^${key}:\\s+(\\d+)\\s*kB`, "m"))?.[1] || "0", 10) * 1024;
|
|
136
|
+
const swapTotal = kb("SwapTotal");
|
|
137
|
+
return { swapTotal, swapUsed: swapTotal - kb("SwapFree"), cached: kb("Cached") + kb("Buffers") };
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (platform() === "darwin") {
|
|
144
|
+
try {
|
|
145
|
+
// "total = 2048.00M used = 1024.50M free = 1023.50M (encrypted)"
|
|
146
|
+
const out = execSync("sysctl -n vm.swapusage", { encoding: "utf8", timeout: 4000 });
|
|
147
|
+
const mb = (key) => parseFloat(out.match(new RegExp(`${key} = ([\\d.]+)M`))?.[1] || "0") * 1024 * 1024;
|
|
148
|
+
return { swapTotal: Math.round(mb("total")), swapUsed: Math.round(mb("used")) };
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
128
156
|
// Cumulative rx/tx byte counters summed across real interfaces (Linux
|
|
129
157
|
// /proc/net/dev). Loopback and virtual bridges (docker/veth/lxc/br-) are skipped
|
|
130
158
|
// so the figure reflects actual host network traffic, not container-to-host
|
|
@@ -220,6 +248,7 @@ export function createOSPlugin() {
|
|
|
220
248
|
totalmem: totalmem(),
|
|
221
249
|
freemem: freemem(),
|
|
222
250
|
available: availableMem(),
|
|
251
|
+
memory: memDetail(),
|
|
223
252
|
disks: collectDisks(),
|
|
224
253
|
processes: collectProcesses(),
|
|
225
254
|
network,
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { createMonitoringPlugin } from "../../lib/MonitoringPlugin.js";
|
|
2
|
+
const DEFAULTS = {
|
|
3
|
+
services: ["auth", "db", "pooler", "realtime", "rest", "storage"],
|
|
4
|
+
diskWarnPct: 80,
|
|
5
|
+
diskCritPct: 90,
|
|
6
|
+
connWarnPct: 85,
|
|
7
|
+
requestTimeout: 10000,
|
|
8
|
+
};
|
|
9
|
+
const MGMT = "https://api.supabase.com/v1";
|
|
10
|
+
/** Prometheus text exposition format, samples only. Comments and blanks are skipped. */
|
|
11
|
+
export function parseProm(text) {
|
|
12
|
+
const out = [];
|
|
13
|
+
for (const line of String(text || "").split("\n")) {
|
|
14
|
+
const m = /^([a-zA-Z_:][\w:]*)(?:\{(.*)\})?\s+(\S+)/.exec(line);
|
|
15
|
+
if (!m)
|
|
16
|
+
continue;
|
|
17
|
+
const value = Number(m[3]);
|
|
18
|
+
if (!Number.isFinite(value))
|
|
19
|
+
continue;
|
|
20
|
+
const labels = {};
|
|
21
|
+
for (const l of (m[2] || "").matchAll(/(\w+)="((?:[^"\\]|\\.)*)"/g))
|
|
22
|
+
labels[l[1]] = l[2];
|
|
23
|
+
out.push({ name: m[1], labels, value });
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The handful of figures worth a tile, from the ~200 series the endpoint
|
|
29
|
+
* serves. Every one is null when its series is absent, never 0: a missing
|
|
30
|
+
* series means "not measured", and 0% disk would read as healthy.
|
|
31
|
+
*/
|
|
32
|
+
export function pickMetrics(samples) {
|
|
33
|
+
const all = (name) => samples.filter((s) => s.name === name);
|
|
34
|
+
const sum = (name) => {
|
|
35
|
+
const rows = all(name);
|
|
36
|
+
return rows.length ? rows.reduce((n, s) => n + s.value, 0) : null;
|
|
37
|
+
};
|
|
38
|
+
// Supabase keeps Postgres on its own volume at /data. Root is the fallback
|
|
39
|
+
// for an image that does not split them.
|
|
40
|
+
const onMount = (name) => {
|
|
41
|
+
const rows = all(name);
|
|
42
|
+
return (rows.find((s) => s.labels.mountpoint === "/data") ?? rows.find((s) => s.labels.mountpoint === "/"))?.value ?? null;
|
|
43
|
+
};
|
|
44
|
+
const pct = (used, total) => used != null && total ? Math.round((used / total) * 1000) / 10 : null;
|
|
45
|
+
const diskTotal = onMount("node_filesystem_size_bytes");
|
|
46
|
+
const diskAvail = onMount("node_filesystem_avail_bytes");
|
|
47
|
+
const diskUsed = diskTotal != null && diskAvail != null ? diskTotal - diskAvail : null;
|
|
48
|
+
const memTotal = sum("node_memory_MemTotal_bytes");
|
|
49
|
+
const memAvail = sum("node_memory_MemAvailable_bytes");
|
|
50
|
+
return {
|
|
51
|
+
diskTotalBytes: diskTotal,
|
|
52
|
+
diskUsedBytes: diskUsed,
|
|
53
|
+
diskPct: pct(diskUsed, diskTotal),
|
|
54
|
+
memPct: pct(memTotal != null && memAvail != null ? memTotal - memAvail : null, memTotal),
|
|
55
|
+
load1: sum("node_load1"),
|
|
56
|
+
connections: sum("pg_stat_database_num_backends"),
|
|
57
|
+
maxConnections: sum("pg_settings_max_connections"),
|
|
58
|
+
dbSizeBytes: sum("pg_database_size_bytes"),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async function get(url, headers, timeout) {
|
|
62
|
+
const res = await fetch(url, { headers, signal: AbortSignal.timeout(timeout) });
|
|
63
|
+
if (!res.ok)
|
|
64
|
+
throw new Error(`HTTP ${res.status} from ${new URL(url).host}${new URL(url).pathname}`);
|
|
65
|
+
return res;
|
|
66
|
+
}
|
|
67
|
+
export async function collectProject(p, cfg) {
|
|
68
|
+
const token = p.accessToken || cfg.accessToken;
|
|
69
|
+
const timeout = cfg.requestTimeout ?? DEFAULTS.requestTimeout;
|
|
70
|
+
const base = { kind: "supabase", ref: p.ref, name: p.name || p.ref, timestamp: Date.now() };
|
|
71
|
+
if (!token && !p.serviceKey) {
|
|
72
|
+
return { ...base, reachable: false, error: "supabase project needs an accessToken or a serviceKey" };
|
|
73
|
+
}
|
|
74
|
+
const bearer = { Authorization: `Bearer ${token}`, Accept: "application/json" };
|
|
75
|
+
const services = (cfg.services?.length ? cfg.services : DEFAULTS.services).join(",");
|
|
76
|
+
const skipped = Promise.resolve(null);
|
|
77
|
+
// Asked together, and one failing must not hide the others: a token that has
|
|
78
|
+
// expired still leaves the disk figures worth reporting.
|
|
79
|
+
const [project, health, prom] = await Promise.allSettled([
|
|
80
|
+
token ? get(`${MGMT}/projects/${p.ref}`, bearer, timeout).then((r) => r.json()) : skipped,
|
|
81
|
+
token ? get(`${MGMT}/projects/${p.ref}/health?services=${services}`, bearer, timeout).then((r) => r.json()) : skipped,
|
|
82
|
+
p.serviceKey
|
|
83
|
+
? get(`https://${p.ref}.supabase.co/customer/v1/privileged/metrics`, { Authorization: `Basic ${Buffer.from(`service_role:${p.serviceKey}`).toString("base64")}` }, timeout).then((r) => r.text())
|
|
84
|
+
: skipped,
|
|
85
|
+
]);
|
|
86
|
+
const results = [project, health, prom];
|
|
87
|
+
const errors = results
|
|
88
|
+
.filter((r) => r.status === "rejected")
|
|
89
|
+
.map((r) => String(r.reason?.message || r.reason).slice(0, 160));
|
|
90
|
+
const value = (r) => (r.status === "fulfilled" ? r.value : null);
|
|
91
|
+
const proj = value(project);
|
|
92
|
+
const got = results.filter((r) => r.status === "fulfilled" && r.value != null).length;
|
|
93
|
+
if (!got)
|
|
94
|
+
return { ...base, reachable: false, error: errors[0] || "no response" };
|
|
95
|
+
const healthList = Array.isArray(value(health)) ? value(health) : [];
|
|
96
|
+
const promText = value(prom);
|
|
97
|
+
return {
|
|
98
|
+
...base,
|
|
99
|
+
name: p.name || proj?.name || p.ref,
|
|
100
|
+
reachable: true,
|
|
101
|
+
projectStatus: proj?.status ?? null,
|
|
102
|
+
region: proj?.region ?? null,
|
|
103
|
+
pgVersion: proj?.database?.version ?? null,
|
|
104
|
+
services: healthList.map((s) => ({
|
|
105
|
+
name: String(s?.name ?? "unknown"),
|
|
106
|
+
healthy: s?.healthy === true,
|
|
107
|
+
status: String(s?.status ?? "UNKNOWN"),
|
|
108
|
+
error: s?.error ? String(s.error).slice(0, 160) : undefined,
|
|
109
|
+
})),
|
|
110
|
+
metrics: promText ? pickMetrics(parseProm(promText)) : null,
|
|
111
|
+
// A source that was configured and failed. The report is partial, and the
|
|
112
|
+
// server says so instead of showing a green tile with half its figures.
|
|
113
|
+
errors,
|
|
114
|
+
diskWarnPct: cfg.diskWarnPct ?? DEFAULTS.diskWarnPct,
|
|
115
|
+
diskCritPct: cfg.diskCritPct ?? DEFAULTS.diskCritPct,
|
|
116
|
+
connWarnPct: cfg.connWarnPct ?? DEFAULTS.connWarnPct,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
export function createSupabasePlugin() {
|
|
120
|
+
let refreshTimer = null;
|
|
121
|
+
const refreshFn = async (plugin) => {
|
|
122
|
+
const cfg = (plugin.config || {});
|
|
123
|
+
const projects = (cfg.projects || []).filter((p) => p?.ref);
|
|
124
|
+
if (!projects.length) {
|
|
125
|
+
await plugin.send({ kind: "supabase", reachable: false, error: "supabase plugin needs at least one project ref" });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// Sequential: a handful of projects, and one slow account must not be able
|
|
129
|
+
// to starve the rest of their timeout.
|
|
130
|
+
for (const p of projects) {
|
|
131
|
+
const uid = `supabase-${p.ref}`;
|
|
132
|
+
try {
|
|
133
|
+
await plugin.send(await collectProject(p, cfg), uid);
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
await plugin.send({ kind: "supabase", ref: p.ref, name: p.name || p.ref, reachable: false, error: String(e?.message || e).slice(0, 200), timestamp: Date.now() }, uid);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const monitorFn = async (plugin) => {
|
|
141
|
+
await refreshFn(plugin);
|
|
142
|
+
refreshTimer = setInterval(() => refreshFn(plugin), plugin.config?.refreshInterval || 60000);
|
|
143
|
+
};
|
|
144
|
+
const teardownFn = async () => {
|
|
145
|
+
if (refreshTimer)
|
|
146
|
+
clearInterval(refreshTimer);
|
|
147
|
+
refreshTimer = null;
|
|
148
|
+
};
|
|
149
|
+
return createMonitoringPlugin("supabase", "supabase", "Supabase cloud project monitor (project status, service health, disk, connections)", async () => { }, monitorFn, refreshFn, teardownFn);
|
|
150
|
+
}
|
|
151
|
+
export default createSupabasePlugin;
|
|
@@ -12,6 +12,20 @@ const AUTH_ERRORS = {
|
|
|
12
12
|
/** A login DSM refused. Retrying every pass would trip its auto block. */
|
|
13
13
|
export const authErrorText = (code) => AUTH_ERRORS[Number(code)];
|
|
14
14
|
const apiErrorText = (api, code) => Number(code) === 105 ? `${api}: this account lacks permission, it may need to be an administrator` : `${api}: DSM error ${code}`;
|
|
15
|
+
// "SID not found". DSM drops about 1% of sessions it has just opened, on the
|
|
16
|
+
// first call after a successful login. Why isn't known; a new login works.
|
|
17
|
+
const SESSION_LOST = 119;
|
|
18
|
+
/** Run a whole login-to-logout pass, again if DSM lost the session. Once only. */
|
|
19
|
+
export async function retryLostSession(pass) {
|
|
20
|
+
try {
|
|
21
|
+
return await pass();
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
if (e?.dsmCode !== SESSION_LOST)
|
|
25
|
+
throw e;
|
|
26
|
+
return pass();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
15
29
|
function post(url, form, cfg) {
|
|
16
30
|
return new Promise((resolve, reject) => {
|
|
17
31
|
const body = new URLSearchParams(form).toString();
|
|
@@ -109,7 +123,7 @@ export function summarise(storage, usb, system, util, cfg = {}) {
|
|
|
109
123
|
async function collect(call, cfg) {
|
|
110
124
|
const storage = await call("SYNO.Storage.CGI.Storage", "load_info", 1);
|
|
111
125
|
if (!storage.success)
|
|
112
|
-
throw new Error(apiErrorText("storage", storage.error?.code));
|
|
126
|
+
throw Object.assign(new Error(apiErrorText("storage", storage.error?.code)), { dsmCode: storage.error?.code });
|
|
113
127
|
// Extras: the card is still worth drawing without them.
|
|
114
128
|
const [usb, system, util] = await Promise.all([
|
|
115
129
|
call("SYNO.Core.ExternalDevice.Storage.USB", "list", 1, { additional: '["all"]' }).catch(() => null),
|
|
@@ -136,25 +150,27 @@ export function createSynologyPlugin() {
|
|
|
136
150
|
throw new Error("config needs account and password");
|
|
137
151
|
if (Date.now() < lockedUntil)
|
|
138
152
|
throw new Error(lockedReason);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
153
|
+
await report(await retryLostSession(async () => {
|
|
154
|
+
const login = await post(cfg.url, { api: "SYNO.API.Auth", version: "6", method: "login", account: cfg.account, passwd: cfg.password, format: "sid" }, cfg);
|
|
155
|
+
if (!login.success) {
|
|
156
|
+
const code = login.error?.code;
|
|
157
|
+
const refused = authErrorText(code);
|
|
158
|
+
if (refused) {
|
|
159
|
+
lockedUntil = Date.now() + REAUTH_AFTER_MS;
|
|
160
|
+
lockedReason = `login refused: ${refused}. Next try within the hour, or restart the agent`;
|
|
161
|
+
throw new Error(lockedReason);
|
|
162
|
+
}
|
|
163
|
+
throw new Error(`login failed: DSM error ${code}`);
|
|
164
|
+
}
|
|
165
|
+
const sid = String(login.data?.sid);
|
|
166
|
+
const call = (api, method, version, extra = {}) => post(cfg.url, { api, method, version: String(version), _sid: sid, ...extra }, cfg);
|
|
167
|
+
try {
|
|
168
|
+
return await collect(call, cfg);
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
await call("SYNO.API.Auth", "logout", 6).catch(() => { });
|
|
147
172
|
}
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
const sid = String(login.data?.sid);
|
|
151
|
-
const call = (api, method, version, extra = {}) => post(cfg.url, { api, method, version: String(version), _sid: sid, ...extra }, cfg);
|
|
152
|
-
try {
|
|
153
|
-
await report(await collect(call, cfg));
|
|
154
|
-
}
|
|
155
|
-
finally {
|
|
156
|
-
await call("SYNO.API.Auth", "logout", 6).catch(() => { });
|
|
157
|
-
}
|
|
173
|
+
}));
|
|
158
174
|
}
|
|
159
175
|
catch (e) {
|
|
160
176
|
await report({ kind: "qnap", reachable: false, error: String(e?.message || e).slice(0, 120) });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daemonitor/plugins",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.51",
|
|
4
4
|
"description": "daemonitor plugins",
|
|
5
5
|
"types": "types/index.d.ts",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -30,18 +30,14 @@
|
|
|
30
30
|
"packageManager": "pnpm@9.15.9",
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^26.1.1",
|
|
33
|
-
"
|
|
34
|
-
"typescript": "^5.8.3",
|
|
35
|
-
"vue": "^3.5.13",
|
|
36
|
-
"vue-router": "^4.5.0"
|
|
33
|
+
"typescript": "^5.8.3"
|
|
37
34
|
},
|
|
38
35
|
"dependencies": {
|
|
39
36
|
"@daemonitor/common": "^1.0.0",
|
|
40
37
|
"dotenv": "^16.0.3",
|
|
41
38
|
"ewelink-api": "^3.1.1",
|
|
42
39
|
"node-machine-id": "^1.1.12",
|
|
43
|
-
"
|
|
44
|
-
"pm2": "^6.0.5",
|
|
40
|
+
"pm2": "^7.1.1",
|
|
45
41
|
"string-to-color": "^2.2.2",
|
|
46
42
|
"ts-node": "^10.9.1",
|
|
47
43
|
"tsconfig-paths": "^4.2.0"
|