@pixlcore/xycli 1.0.0
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/README.md +543 -0
- package/docs/help.md +2587 -0
- package/docs/screenshots.md +219 -0
- package/docs/sync.md +920 -0
- package/docs/tutorial.md +204 -0
- package/index.js +444 -0
- package/lib/alerts.js +735 -0
- package/lib/apikey.js +425 -0
- package/lib/buckets.js +607 -0
- package/lib/categories.js +323 -0
- package/lib/channels.js +290 -0
- package/lib/config.js +86 -0
- package/lib/dashboard.js +237 -0
- package/lib/doc.js +131 -0
- package/lib/events.js +1314 -0
- package/lib/groups.js +1038 -0
- package/lib/jobs.js +1254 -0
- package/lib/log.js +125 -0
- package/lib/marketplace.js +386 -0
- package/lib/monitors.js +412 -0
- package/lib/plugins.js +491 -0
- package/lib/secrets.js +441 -0
- package/lib/servers.js +1502 -0
- package/lib/snapshots.js +498 -0
- package/lib/sync.js +963 -0
- package/lib/system.js +612 -0
- package/lib/tags.js +250 -0
- package/lib/tickets.js +681 -0
- package/lib/transfer.js +447 -0
- package/lib/utils.js +2745 -0
- package/lib/webhook.js +414 -0
- package/logo.webp +0 -0
- package/package.json +38 -0
- package/test/README.md +97 -0
- package/test/api-validation.test.js +170 -0
- package/test/categories.test.js +246 -0
- package/test/channels.test.js +209 -0
- package/test/config.test.js +96 -0
- package/test/doc.test.js +84 -0
- package/test/groups.test.js +211 -0
- package/test/help.test.js +26 -0
- package/test/helpers/common.js +127 -0
- package/test/helpers/pagination-preload.cjs +18 -0
- package/test/log.test.js +103 -0
- package/test/markdown.test.js +61 -0
- package/test/marketplace.test.js +229 -0
- package/test/monitors.test.js +227 -0
- package/test/pagination.test.js +138 -0
- package/test/plugins.test.js +351 -0
- package/test/secrets.test.js +271 -0
- package/test/servers.test.js +303 -0
- package/test/snapshots.test.js +167 -0
- package/test/sync-unit.test.js +446 -0
- package/test/sync.test.js +542 -0
- package/test/system.test.js +94 -0
- package/test/tags.test.js +230 -0
- package/test/tickets.test.js +261 -0
- package/test/toast.test.js +73 -0
- package/test/transfer-apikey.test.js +96 -0
- package/test/transfer-workflow.test.js +122 -0
- package/test/transfer.test.js +314 -0
- package/test/webhook.test.js +332 -0
- package/test.js +49 -0
package/lib/monitors.js
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
// Server Monitors Layer
|
|
2
|
+
|
|
3
|
+
const cli = require('pixl-cli');
|
|
4
|
+
const Tools = cli.Tools;
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
|
|
8
|
+
async cmd_monitors() {
|
|
9
|
+
// The plural command lists monitor definitions, with optional filters.
|
|
10
|
+
await this.cmd_get_monitors();
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
async cmd_monitor() {
|
|
14
|
+
var cmd = this.args.other.shift();
|
|
15
|
+
if (!cmd) return this.dieUsage('monitor');
|
|
16
|
+
|
|
17
|
+
switch (cmd) {
|
|
18
|
+
case 'list': await this.cmd_get_monitors(); break;
|
|
19
|
+
case 'get': await this.cmd_get_monitor(); break;
|
|
20
|
+
case 'create': await this.cmd_create_monitor(); break;
|
|
21
|
+
case 'update': await this.cmd_update_monitor(); break;
|
|
22
|
+
case 'test': await this.cmd_test_monitor(); break;
|
|
23
|
+
case 'delete': await this.cmd_delete_monitor(); break;
|
|
24
|
+
|
|
25
|
+
default:
|
|
26
|
+
// A bare ID or title opens monitor details.
|
|
27
|
+
this.args.other.unshift(cmd);
|
|
28
|
+
await this.cmd_get_monitor();
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
async cmd_get_monitors() {
|
|
34
|
+
// Consume pagination before turning remaining options into filters.
|
|
35
|
+
this.prepSearchArgs();
|
|
36
|
+
await this.getMultiple();
|
|
37
|
+
|
|
38
|
+
var monitors = this.monitors.slice(0);
|
|
39
|
+
var is_filtered = !!this.args.other.length;
|
|
40
|
+
|
|
41
|
+
if (is_filtered) {
|
|
42
|
+
var search = this.args.other.join(' ');
|
|
43
|
+
monitors = this.findObjectsFuzzy(monitors, { id: search, title: search, source: search, notes: search }, 1);
|
|
44
|
+
}
|
|
45
|
+
delete this.args.other;
|
|
46
|
+
|
|
47
|
+
['display', 'delta', 'divide_by_delta'].forEach( key => {
|
|
48
|
+
if (!(key in this.args)) return;
|
|
49
|
+
|
|
50
|
+
var value = this.parseMonitorBoolean(this.args[key], key);
|
|
51
|
+
monitors = monitors.filter( monitor => !!monitor[key] === value );
|
|
52
|
+
delete this.args[key];
|
|
53
|
+
is_filtered = true;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if ('group' in this.args) {
|
|
57
|
+
var selector = this.args.group;
|
|
58
|
+
var group = Tools.findObject(this.groups, { id: selector }) || this.findObjectFuzzy(this.groups, { title: selector });
|
|
59
|
+
if (!group) return this.die("Could not find server group based on your criteria: " + selector);
|
|
60
|
+
|
|
61
|
+
// Include monitors scoped to all groups, since these apply here too.
|
|
62
|
+
monitors = monitors.filter( monitor => !(monitor.groups || []).length || monitor.groups.includes(group.id) );
|
|
63
|
+
delete this.args.group;
|
|
64
|
+
is_filtered = true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (Tools.numKeys(this.args)) {
|
|
68
|
+
monitors = this.findObjectsFuzzy(monitors, this.args);
|
|
69
|
+
is_filtered = true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
monitors.sort( (a, b) => ((a.sort_order || 0) - (b.sort_order || 0)) || String(a.title).localeCompare(String(b.title)) );
|
|
73
|
+
|
|
74
|
+
if (this.format.match(/json/)) return this.jsonOutput(monitors);
|
|
75
|
+
|
|
76
|
+
this.printPaginatedBoxTable({
|
|
77
|
+
title: is_filtered ? 'Filtered Monitors' : 'All Monitors',
|
|
78
|
+
header: ['Monitor ID', 'Title', 'Display', 'Data Type', 'Groups', 'Modified'],
|
|
79
|
+
rows: monitors.slice(this.offset, this.offset + this.limit),
|
|
80
|
+
list: { length: monitors.length },
|
|
81
|
+
offset: this.offset,
|
|
82
|
+
limit: this.limit
|
|
83
|
+
}, monitor => [
|
|
84
|
+
this.color('theme').bold(monitor.id),
|
|
85
|
+
bold(monitor.title),
|
|
86
|
+
monitor.display ? green('Visible') : gray('Hidden'),
|
|
87
|
+
monitor.data_type,
|
|
88
|
+
this.getNiceMonitorGroups(monitor.groups),
|
|
89
|
+
this.getRelativeDateTime(monitor.modified, true)
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
this.printSuggestedCommands({
|
|
93
|
+
"View monitor details": "xy monitor MONITOR_ID_OR_TITLE",
|
|
94
|
+
"Create a monitor": 'xy monitor create --title "CPU Usage" --source cpu.currentLoad --suffix %',
|
|
95
|
+
"List hidden monitors": "xy monitors --display false",
|
|
96
|
+
"Test a monitor": "xy monitor test MONITOR_ID --server SERVER_ID_OR_TITLE"
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
async cmd_get_monitor() {
|
|
101
|
+
await this.getMultiple();
|
|
102
|
+
|
|
103
|
+
var selector = this.args.other.join(' ') || this.args.id || this.args.title;
|
|
104
|
+
if (!selector) return this.dieUsage('monitor get');
|
|
105
|
+
|
|
106
|
+
var monitor = await this.findMonitor(selector);
|
|
107
|
+
|
|
108
|
+
if (this.format.match(/json/)) return this.jsonOutput(monitor);
|
|
109
|
+
|
|
110
|
+
this.printBoxList({
|
|
111
|
+
title: 'Monitor Summary',
|
|
112
|
+
rows: [
|
|
113
|
+
[ 'Monitor ID', gray(monitor.id) ],
|
|
114
|
+
[ 'Title', this.color('theme').bold(monitor.title) ],
|
|
115
|
+
[ 'Display', monitor.display ? green('Visible') : gray('Hidden') ],
|
|
116
|
+
[ 'Icon', monitor.icon || gray('(None)') ],
|
|
117
|
+
[ 'Groups', this.getNiceMonitorGroups(monitor.groups) ],
|
|
118
|
+
[ 'Source', monitor.source ],
|
|
119
|
+
[ 'Data Match', monitor.data_match || gray('(None)') ],
|
|
120
|
+
[ 'Data Type', monitor.data_type ],
|
|
121
|
+
[ 'Suffix', monitor.suffix || gray('(None)') ],
|
|
122
|
+
[ 'Delta', monitor.delta ? green('Yes') : gray('No') ],
|
|
123
|
+
[ 'Divide by Delta', monitor.divide_by_delta ? green('Yes') : gray('No') ],
|
|
124
|
+
[ 'Delta Min', typeof(monitor.delta_min_value) == 'number' ? monitor.delta_min_value : gray('(Disabled)') ],
|
|
125
|
+
[ 'Min Vert Scale', monitor.min_vert_scale || 0 ],
|
|
126
|
+
[ 'Sort Order', monitor.sort_order || 0 ],
|
|
127
|
+
[ 'Author', monitor.username || gray('(Unknown)') ],
|
|
128
|
+
[ 'Created', this.getNiceDateTime(monitor.created, true, true) ],
|
|
129
|
+
[ 'Modified', this.getNiceDateTime(monitor.modified, true, true) ],
|
|
130
|
+
[ 'Revision', monitor.revision || 1 ]
|
|
131
|
+
]
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// User notes may contain multiple lines, so keep them out of the summary box.
|
|
135
|
+
if (monitor.notes) {
|
|
136
|
+
this.printUserNotes('MONITOR NOTES', monitor.notes);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this.printSuggestedCommands({
|
|
140
|
+
"Test monitor": `xy monitor test ${monitor.id} --server SERVER_ID_OR_TITLE`,
|
|
141
|
+
"Export monitor": `xy monitor ${monitor.id} --export monitor.json`,
|
|
142
|
+
"Update monitor": `xy monitor update ${monitor.id} --notes "My notes"`,
|
|
143
|
+
"Hide monitor": `xy monitor update ${monitor.id} --display false`,
|
|
144
|
+
"Delete monitor": `xy monitor delete ${monitor.id}`
|
|
145
|
+
});
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
async cmd_create_monitor() {
|
|
149
|
+
// Defaults match the web editor. Collection continues when display is false.
|
|
150
|
+
if (this.args.other.length) return this.dieUsage('monitor create');
|
|
151
|
+
delete this.args.other;
|
|
152
|
+
|
|
153
|
+
var params = this.prepareMonitorParams({
|
|
154
|
+
data_type: 'float',
|
|
155
|
+
suffix: '',
|
|
156
|
+
groups: [],
|
|
157
|
+
display: true,
|
|
158
|
+
min_vert_scale: 0,
|
|
159
|
+
icon: '',
|
|
160
|
+
notes: '',
|
|
161
|
+
data_match: '',
|
|
162
|
+
delta: false,
|
|
163
|
+
divide_by_delta: false,
|
|
164
|
+
delta_min_value: false
|
|
165
|
+
}, this.args, true);
|
|
166
|
+
if (!params.title || !params.source) return this.dieUsage('monitor create');
|
|
167
|
+
|
|
168
|
+
var data = await this.callStandardAPI('createMonitor', params, { text: 'Creating monitor...' });
|
|
169
|
+
if (this.dry) return;
|
|
170
|
+
|
|
171
|
+
if (this.format.match(/json/)) return this.jsonOutput(data.monitor);
|
|
172
|
+
|
|
173
|
+
this.toast('✅', 'green', "Successfully created monitor: #" + data.monitor.id);
|
|
174
|
+
|
|
175
|
+
this.printSuggestedCommands({
|
|
176
|
+
"View monitor details": `xy monitor ${data.monitor.id}`,
|
|
177
|
+
"Test monitor": `xy monitor test ${data.monitor.id} --server SERVER_ID_OR_TITLE`,
|
|
178
|
+
"Update monitor": `xy monitor update ${data.monitor.id} --notes "My notes"`,
|
|
179
|
+
"List all monitors": "xy monitors"
|
|
180
|
+
});
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
async cmd_update_monitor() {
|
|
184
|
+
// Fetch the saved groups for indexed edits and appends; send sparse updates.
|
|
185
|
+
var id = this.consumeMonitorID();
|
|
186
|
+
var monitor = await this.fetchMonitor(id);
|
|
187
|
+
|
|
188
|
+
this.printMutationSummary({
|
|
189
|
+
title: 'Update Monitor',
|
|
190
|
+
rows: [
|
|
191
|
+
[ 'Monitor ID', gray(monitor.id) ],
|
|
192
|
+
[ 'Title', this.color('theme').bold(monitor.title) ]
|
|
193
|
+
]
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
if (!await this.confirmSyncUpdate('monitor', monitor.id, 'monitor')) return;
|
|
197
|
+
if (!Tools.numKeys(this.args)) return this.die("No updates specified for monitor.");
|
|
198
|
+
this.printUpdateData(this.args);
|
|
199
|
+
|
|
200
|
+
var params = this.prepareMonitorParams(monitor, this.args, false);
|
|
201
|
+
params.id = id;
|
|
202
|
+
|
|
203
|
+
var data = await this.callStandardAPI('updateMonitor', params, { text: 'Updating monitor...' });
|
|
204
|
+
if (this.dry) return;
|
|
205
|
+
|
|
206
|
+
if (this.format.match(/json/)) return this.jsonOutput(data);
|
|
207
|
+
|
|
208
|
+
this.toast('✅', 'green', "Successfully updated monitor: #" + id);
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
async cmd_test_monitor() {
|
|
212
|
+
// Test saved settings with optional overrides, or an unsaved expression.
|
|
213
|
+
await this.getMultiple();
|
|
214
|
+
|
|
215
|
+
var selector = this.args.other.join(' ') || this.args.id;
|
|
216
|
+
delete this.args.other;
|
|
217
|
+
delete this.args.id;
|
|
218
|
+
var monitor = selector ? await this.findMonitor(selector) : null;
|
|
219
|
+
|
|
220
|
+
var server_selector = this.args.server;
|
|
221
|
+
delete this.args.server;
|
|
222
|
+
if (!server_selector) return this.dieUsage('monitor test');
|
|
223
|
+
|
|
224
|
+
var servers = Object.values(this.servers);
|
|
225
|
+
var server = Tools.findObject(servers, { id: server_selector }) || this.findObjectFuzzy(servers, {
|
|
226
|
+
title: server_selector,
|
|
227
|
+
hostname: server_selector,
|
|
228
|
+
ip: server_selector
|
|
229
|
+
}, 1);
|
|
230
|
+
if (!server) return this.die("Could not find server based on your criteria: " + server_selector);
|
|
231
|
+
|
|
232
|
+
Object.keys(this.args).forEach( key => {
|
|
233
|
+
if (!['source', 'data_type', 'data_match'].includes(key)) this.die("Unsupported monitor test option: --" + key);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
var params = this.prepareMonitorParams({
|
|
237
|
+
source: monitor ? monitor.source : '',
|
|
238
|
+
data_type: monitor ? monitor.data_type : 'float',
|
|
239
|
+
data_match: (monitor && monitor.data_match) || ''
|
|
240
|
+
}, this.args, true);
|
|
241
|
+
params.server = server.id;
|
|
242
|
+
|
|
243
|
+
var data = await this.callStandardAPI('testMonitor', params, { text: 'Testing monitor...' });
|
|
244
|
+
if (this.dry) return;
|
|
245
|
+
|
|
246
|
+
if (this.format.match(/json/)) return this.jsonOutput(data);
|
|
247
|
+
|
|
248
|
+
// An unresolved expression is distinct from a valid zero result.
|
|
249
|
+
this.printBoxList({
|
|
250
|
+
title: 'Monitor Test Result',
|
|
251
|
+
rows: [
|
|
252
|
+
[ 'Monitor', monitor ? monitor.title + gray(' (' + monitor.id + ')') : gray('(Ad Hoc)') ],
|
|
253
|
+
[ 'Server', this.getNiceServer(server) + gray(' (' + server.id + ')') ],
|
|
254
|
+
[ 'Source', params.source ],
|
|
255
|
+
[ 'Data Type', params.data_type ],
|
|
256
|
+
[ 'Result', data.fail ? yellow('No Value') : green(String(data.value)) + ((params.data_type == 'bytes') ? ' (' + Tools.getTextFromBytes(data.value) + ')' : '') ]
|
|
257
|
+
]
|
|
258
|
+
});
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
async cmd_delete_monitor() {
|
|
262
|
+
var id = this.consumeMonitorID();
|
|
263
|
+
var monitor = await this.fetchMonitor(id);
|
|
264
|
+
|
|
265
|
+
this.printMutationSummary({
|
|
266
|
+
title: 'Delete Monitor',
|
|
267
|
+
rows: [
|
|
268
|
+
[ 'Monitor ID', gray(monitor.id) ],
|
|
269
|
+
[ 'Title', this.color('theme').bold(monitor.title) ]
|
|
270
|
+
]
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
if (this.args.confirm !== true) {
|
|
274
|
+
this.toast('⚠️', 'orange', "Please confirm the monitor delete by adding '--confirm'.");
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
delete this.args.confirm;
|
|
279
|
+
if (Tools.numKeys(this.args)) return this.die("Unsupported monitor delete option: --" + Tools.firstKey(this.args));
|
|
280
|
+
|
|
281
|
+
var data = await this.callStandardAPI('deleteMonitor', { id: id }, { text: 'Deleting monitor...' });
|
|
282
|
+
if (this.dry) return;
|
|
283
|
+
|
|
284
|
+
if (this.format.match(/json/)) return this.jsonOutput(data);
|
|
285
|
+
|
|
286
|
+
this.toast('✅', 'green', "Successfully deleted monitor: #" + id);
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
async findMonitor(selector) {
|
|
290
|
+
// Exact IDs take precedence over fuzzy titles for read-only commands.
|
|
291
|
+
var match = Tools.findObject(this.monitors, { id: selector }) || this.findObjectFuzzy(this.monitors, { title: selector });
|
|
292
|
+
if (!match) this.die("Could not find monitor based on your criteria: " + selector);
|
|
293
|
+
|
|
294
|
+
return await this.fetchMonitor(match.id);
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
async fetchMonitor(id) {
|
|
298
|
+
cli.progress.start({ amount: 1, pct: false, text: gray('→ Loading monitor...') });
|
|
299
|
+
|
|
300
|
+
var { err, data } = await this.api.getMonitor({ id: id });
|
|
301
|
+
cli.progress.end();
|
|
302
|
+
|
|
303
|
+
if (err) this.die(err);
|
|
304
|
+
|
|
305
|
+
return data.monitor;
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
consumeMonitorID() {
|
|
309
|
+
// Require an exact, unambiguous ID for changes to saved definitions.
|
|
310
|
+
var id = this.args.other.shift() || this.args.id;
|
|
311
|
+
if (!id) this.die("Missing required Monitor ID argument.");
|
|
312
|
+
if (this.args.other.length) this.die("Unexpected argument after Monitor ID: " + this.args.other[0]);
|
|
313
|
+
if (this.args.id && (this.args.id !== id)) this.die("Conflicting Monitor ID arguments.");
|
|
314
|
+
if ((typeof(id) != 'string') || !id.match(/^[a-z0-9_]+$/)) this.die("Invalid Monitor ID: " + id);
|
|
315
|
+
|
|
316
|
+
delete this.args.id;
|
|
317
|
+
delete this.args.other;
|
|
318
|
+
|
|
319
|
+
return id;
|
|
320
|
+
},
|
|
321
|
+
|
|
322
|
+
prepareMonitorParams(monitor, input, creating) {
|
|
323
|
+
// Leave audit fields and unrelated settings untouched. The API assigns
|
|
324
|
+
// sort_order on creation, but it can be explicitly changed on update.
|
|
325
|
+
var params = creating ? Tools.copyHash(monitor, true) : {};
|
|
326
|
+
var fields = ['title', 'source', 'data_type', 'data_match', 'display', 'icon', 'groups', 'group', 'suffix', 'min_vert_scale', 'delta', 'divide_by_delta', 'delta_min_value', 'notes'];
|
|
327
|
+
fields.push(creating ? 'id' : 'sort_order');
|
|
328
|
+
|
|
329
|
+
var dotted = {};
|
|
330
|
+
|
|
331
|
+
Object.keys(input).forEach( key => {
|
|
332
|
+
var root = key.split('.')[0];
|
|
333
|
+
if (!fields.includes(root)) this.die("Unsupported monitor option: --" + key);
|
|
334
|
+
|
|
335
|
+
if (key.includes('.')) {
|
|
336
|
+
if (root != 'groups') this.die("Invalid argument path: " + key);
|
|
337
|
+
dotted[key] = input[key];
|
|
338
|
+
}
|
|
339
|
+
else params[key] = input[key];
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// Replace groups first, edit existing indexes next, then append --group.
|
|
343
|
+
if (('groups' in input) || ('group' in input) || Tools.numKeys(dotted)) {
|
|
344
|
+
params.groups = this.parseMonitorGroups(('groups' in input) ? input.groups : (monitor.groups || []));
|
|
345
|
+
this.mergeDotArgs(params, dotted);
|
|
346
|
+
|
|
347
|
+
if ('group' in input) params.groups = params.groups.concat(this.parseMonitorGroups(input.group));
|
|
348
|
+
params.groups = Array.from(new Set(this.parseMonitorGroups(params.groups)));
|
|
349
|
+
}
|
|
350
|
+
delete params.group;
|
|
351
|
+
|
|
352
|
+
['title', 'source'].forEach( key => {
|
|
353
|
+
if (!(key in params)) return;
|
|
354
|
+
|
|
355
|
+
// Numeric constants are valid source expressions, even when parsed as numbers.
|
|
356
|
+
if ((key == 'source') && (typeof(params[key]) == 'number') && Number.isFinite(params[key])) params[key] = String(params[key]);
|
|
357
|
+
if ((typeof(params[key]) != 'string') || !params[key].trim()) this.die("Monitor " + key + " cannot be empty and must be a string.");
|
|
358
|
+
params[key] = params[key].trim();
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
['display', 'delta', 'divide_by_delta'].forEach( key => {
|
|
362
|
+
if (key in params) params[key] = this.parseMonitorBoolean(params[key], key);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
['icon', 'notes', 'suffix', 'data_match'].forEach( key => {
|
|
366
|
+
if ((key in params) && (typeof(params[key]) != 'string')) this.die("Monitor " + key + " must be a string.");
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
if (('id' in params) && ((typeof(params.id) != 'string') || !params.id.match(/^[a-z0-9_]+$/))) this.die("Invalid Monitor ID: " + params.id);
|
|
370
|
+
if (('data_type' in params) && !['integer', 'float', 'bytes', 'seconds', 'milliseconds'].includes(params.data_type)) this.die("Monitor data_type must be integer, float, bytes, seconds, or milliseconds.");
|
|
371
|
+
if (('min_vert_scale' in params) && (!Number.isFinite(params.min_vert_scale) || (params.min_vert_scale < 0))) this.die("Monitor min_vert_scale must be a non-negative number.");
|
|
372
|
+
|
|
373
|
+
// Match the editor's minimum chart range for integer monitors, without
|
|
374
|
+
// adding an unrelated field to sparse updates or test requests.
|
|
375
|
+
if (('min_vert_scale' in params) && ((params.data_type || monitor.data_type) == 'integer') && (params.min_vert_scale < 1)) params.min_vert_scale = 1;
|
|
376
|
+
|
|
377
|
+
if (('sort_order' in params) && !Number.isSafeInteger(params.sort_order)) this.die("Monitor sort_order must be an integer.");
|
|
378
|
+
|
|
379
|
+
// false disables the delta minimum; numeric zero is an enabled minimum.
|
|
380
|
+
if (('delta_min_value' in params) && (params.delta_min_value !== false) && !Number.isFinite(params.delta_min_value)) this.die("Monitor delta_min_value must be a number, or false to disable it.");
|
|
381
|
+
|
|
382
|
+
if (params.data_match) {
|
|
383
|
+
try { new RegExp(params.data_match); }
|
|
384
|
+
catch (err) { this.die("Invalid monitor data_match regular expression: " + err.message); }
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return params;
|
|
388
|
+
},
|
|
389
|
+
|
|
390
|
+
parseMonitorGroups(value) {
|
|
391
|
+
// Group assignments use IDs, as in alert definitions and event targets.
|
|
392
|
+
var groups = Array.isArray(value) ? value.slice(0) : ((typeof(value) == 'string') ? value.split(',') : null);
|
|
393
|
+
if (!groups) this.die("Monitor groups must be a JSON array or comma-separated group IDs.");
|
|
394
|
+
|
|
395
|
+
return groups.map( id => {
|
|
396
|
+
if ((typeof(id) != 'string') || !id.trim().match(/^[a-z0-9_]+$/)) this.die("Invalid server group ID: " + id);
|
|
397
|
+
return id.trim();
|
|
398
|
+
});
|
|
399
|
+
},
|
|
400
|
+
|
|
401
|
+
parseMonitorBoolean(value, key) {
|
|
402
|
+
if ([true, 1, '1', 'true', 'yes', 'on'].includes(value)) return true;
|
|
403
|
+
if ([false, 0, '0', 'false', 'no', 'off'].includes(value)) return false;
|
|
404
|
+
|
|
405
|
+
this.die("Invalid boolean value for '" + key + "': " + value);
|
|
406
|
+
},
|
|
407
|
+
|
|
408
|
+
getNiceMonitorGroups(groups) {
|
|
409
|
+
return (groups && groups.length) ? groups.map( id => this.getNiceGroup(id) ).join(', ') : gray('(All)');
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
}; // module.exports
|