Fixed notification translation.
Ylian Saint-Hilaire committed
Jan 11, 2021 at 11:56 UTC
21e6b0320bde75f94ca95ae499307b7118bfc73a
5 files changed
+1857
-3
db-good.js
new
+1826
@@ -0,0 +1,1826 @@
1
+/**
2
+* @description MeshCentral database module
3
+* @author Ylian Saint-Hilaire
4
+* @copyright Intel Corporation 2018-2020
5
+* @license Apache-2.0
6
+* @version v0.0.2
7
+*/
8
+
9
+/*xjslint node: true */
10
+/*xjslint plusplus: true */
11
+/*xjslint maxlen: 256 */
12
+/*jshint node: true */
13
+/*jshint strict: false */
14
+/*jshint esversion: 6 */
15
+"use strict";
16
+
17
+//
18
+// Construct Meshcentral database object
19
+//
20
+// The default database is NeDB
21
+// https://github.com/louischatriot/nedb
22
+//
23
+// Alternativety, MongoDB can be used
24
+// https://www.mongodb.com/
25
+// Just run with --mongodb [connectionstring], where the connection string is documented here: https://docs.mongodb.com/manual/reference/connection-string/
26
+// The default collection is "meshcentral", but you can override it using --mongodbcol [collection]
27
+//
28
+module.exports.CreateDB = function (parent, func) {
29
+ var obj = {};
30
+ var Datastore = null;
31
+ var expireEventsSeconds = (60 * 60 * 24 * 20); // By default, expire events after 20 days (1728000). (Seconds * Minutes * Hours * Days)
32
+ var expirePowerEventsSeconds = (60 * 60 * 24 * 10); // By default, expire power events after 10 days (864000). (Seconds * Minutes * Hours * Days)
33
+ var expireServerStatsSeconds = (60 * 60 * 24 * 30); // By default, expire power events after 30 days (2592000). (Seconds * Minutes * Hours * Days)
34
+ const common = require('./common.js');
35
+ obj.identifier = null;
36
+ obj.dbKey = null;
37
+ obj.dbRecordsEncryptKey = null;
38
+ obj.dbRecordsDecryptKey = null;
39
+ obj.changeStream = false;
40
+ obj.pluginsActive = ((parent.config) && (parent.config.settings) && (parent.config.settings.plugins != null) && (parent.config.settings.plugins != false) && ((typeof parent.config.settings.plugins != 'object') || (parent.config.settings.plugins.enabled != false)));
41
+
42
+ obj.SetupDatabase = function (func) {
43
+ // Check if the database unique identifier is present
44
+ // This is used to check that in server peering mode, everyone is using the same database.
45
+ obj.Get('DatabaseIdentifier', function (err, docs) {
46
+ if (err != null) { parent.debug('db', 'ERROR (Get DatabaseIdentifier): ' + err); }
47
+ if ((err == null) && (docs.length == 1) && (docs[0].value != null)) {
48
+ obj.identifier = docs[0].value;
49
+ } else {
50
+ obj.identifier = Buffer.from(require('crypto').randomBytes(48), 'binary').toString('hex');
51
+ obj.Set({ _id: 'DatabaseIdentifier', value: obj.identifier });
52
+ }
53
+ });
54
+
55
+ // Load database schema version and check if we need to update
56
+ obj.Get('SchemaVersion', function (err, docs) {
57
+ if (err != null) { parent.debug('db', 'ERROR (Get SchemaVersion): ' + err); }
58
+ var ver = 0;
59
+ if ((err == null) && (docs.length == 1)) { ver = docs[0].value; }
60
+ if (ver == 1) { console.log('This is an unsupported beta 1 database, delete it to create a new one.'); process.exit(0); }
61
+
62
+ // TODO: Any schema upgrades here...
63
+ obj.Set({ _id: 'SchemaVersion', value: 2 });
64
+
65
+ func(ver);
66
+ });
67
+ };
68
+
69
+ // Perform database maintenance
70
+ obj.maintenance = function () {
71
+ if (obj.databaseType == 1) { // NeDB will not remove expired records unless we try to access them. This will force the removal.
72
+ obj.eventsfile.remove({ time: { '$lt': new Date(Date.now() - (expireEventsSeconds * 1000)) } }, { multi: true }); // Force delete older events
73
+ obj.powerfile.remove({ time: { '$lt': new Date(Date.now() - (expirePowerEventsSeconds * 1000)) } }, { multi: true }); // Force delete older events
74
+ obj.serverstatsfile.remove({ time: { '$lt': new Date(Date.now() - (expireServerStatsSeconds * 1000)) } }, { multi: true }); // Force delete older events
75
+ }
76
+ }
77
+
78
+ obj.cleanup = function (func) {
79
+ // TODO: Remove all mesh links to invalid users
80
+ // TODO: Remove all meshes that dont have any links
81
+
82
+ // Remove all events, power events and SMBIOS data from the main collection. They are all in seperate collections now.
83
+ if ((obj.databaseType == 4) || (obj.databaseType == 5)) {
84
+ // MariaDB or MySQL
85
+ obj.RemoveAllOfType('event', function () { });
86
+ obj.RemoveAllOfType('power', function () { });
87
+ obj.RemoveAllOfType('smbios', function () { });
88
+ } else if (obj.databaseType == 3) {
89
+ // MongoDB
90
+ obj.file.deleteMany({ type: 'event' }, { multi: true });
91
+ obj.file.deleteMany({ type: 'power' }, { multi: true });
92
+ obj.file.deleteMany({ type: 'smbios' }, { multi: true });
93
+ } else {
94
+ // NeDB or MongoJS
95
+ obj.file.remove({ type: 'event' }, { multi: true });
96
+ obj.file.remove({ type: 'power' }, { multi: true });
97
+ obj.file.remove({ type: 'smbios' }, { multi: true });
98
+ }
99
+
100
+ // List of valid identifiers
101
+ var validIdentifiers = {}
102
+
103
+ // Load all user groups
104
+ obj.GetAllType('ugrp', function (err, docs) {
105
+ if (err != null) { parent.debug('db', 'ERROR (GetAll user): ' + err); }
106
+ if ((err == null) && (docs.length > 0)) {
107
+ for (var i in docs) {
108
+ // Add this as a valid user identifier
109
+ validIdentifiers[docs[i]._id] = 1;
110
+ }
111
+ }
112
+
113
+ // Fix all of the creating & login to ticks by seconds, not milliseconds.
114
+ obj.GetAllType('user', function (err, docs) {
115
+ if (err != null) { parent.debug('db', 'ERROR (GetAll user): ' + err); }
116
+ if ((err == null) && (docs.length > 0)) {
117
+ for (var i in docs) {
118
+ var fixed = false;
119
+
120
+ // Add this as a valid user identifier
121
+ validIdentifiers[docs[i]._id] = 1;
122
+
123
+ // Fix email address capitalization
124
+ if (docs[i].email && (docs[i].email != docs[i].email.toLowerCase())) {
125
+ docs[i].email = docs[i].email.toLowerCase(); fixed = true;
126
+ }
127
+
128
+ // Fix account creation
129
+ if (docs[i].creation) {
130
+ if (docs[i].creation > 1300000000000) { docs[i].creation = Math.floor(docs[i].creation / 1000); fixed = true; }
131
+ if ((docs[i].creation % 1) != 0) { docs[i].creation = Math.floor(docs[i].creation); fixed = true; }
132
+ }
133
+
134
+ // Fix last account login
135
+ if (docs[i].login) {
136
+ if (docs[i].login > 1300000000000) { docs[i].login = Math.floor(docs[i].login / 1000); fixed = true; }
137
+ if ((docs[i].login % 1) != 0) { docs[i].login = Math.floor(docs[i].login); fixed = true; }
138
+ }
139
+
140
+ // Fix last password change
141
+ if (docs[i].passchange) {
142
+ if (docs[i].passchange > 1300000000000) { docs[i].passchange = Math.floor(docs[i].passchange / 1000); fixed = true; }
143
+ if ((docs[i].passchange % 1) != 0) { docs[i].passchange = Math.floor(docs[i].passchange); fixed = true; }
144
+ }
145
+
146
+ // Fix subscriptions
147
+ if (docs[i].subscriptions != null) { delete docs[i].subscriptions; fixed = true; }
148
+
149
+ // Save the user if needed
150
+ if (fixed) { obj.Set(docs[i]); }
151
+ }
152
+
153
+ // Remove all objects that have a "meshid" that no longer points to a valid mesh.
154
+ // Fix any incorrectly escaped user identifiers
155
+ obj.GetAllType('mesh', function (err, docs) {
156
+ if (err != null) { parent.debug('db', 'ERROR (GetAll mesh): ' + err); }
157
+ var meshlist = [];
158
+ if ((err == null) && (docs.length > 0)) {
159
+ for (var i in docs) {
160
+ var meshChange = false;
161
+ docs[i] = common.unEscapeLinksFieldName(docs[i]);
162
+ meshlist.push(docs[i]._id);
163
+
164
+ // Make sure all mesh types are number type, if not, fix it.
165
+ if (typeof docs[i].mtype == 'string') { docs[i].mtype = parseInt(docs[i].mtype); meshChange = true; }
166
+
167
+ // Take a look at the links
168
+ if (docs[i].links != null) {
169
+ for (var j in docs[i].links) {
170
+ if (validIdentifiers[j] == null) {
171
+ // This identifier is not known, let see if we can fix it.
172
+ var xid = j, xid2 = common.unEscapeFieldName(xid);
173
+ while ((xid != xid2) && (validIdentifiers[xid2] == null)) { xid = xid2; xid2 = common.unEscapeFieldName(xid2); }
174
+ if (validIdentifiers[xid2] == 1) {
175
+ //console.log('Fixing id: ' + j + ' to ' + xid2);
176
+ docs[i].links[xid2] = docs[i].links[j];
177
+ delete docs[i].links[j];
178
+ meshChange = true;
179
+ } else {
180
+ // TODO: here, we may want to clean up links to users and user groups that do not exist anymore.
181
+ //console.log('Unknown id: ' + j);
182
+ }
183
+ }
184
+ }
185
+ }
186
+
187
+ // Save the updated device group if needed
188
+ if (meshChange) { obj.Set(docs[i]); }
189
+ }
190
+ }
191
+ if ((obj.databaseType == 4) || (obj.databaseType == 5)) {
192
+ // MariaDB
193
+ sqlDbQuery('DELETE FROM MeshCentral.Main WHERE (extra LIKE ("mesh/%") AND (extra NOT IN ?)', [meshlist], func);
194
+ } else if (obj.databaseType == 3) {
195
+ // MongoDB
196
+ obj.file.deleteMany({ meshid: { $exists: true, $nin: meshlist } }, { multi: true });
197
+ } else {
198
+ // NeDB or MongoJS
199
+ obj.file.remove({ meshid: { $exists: true, $nin: meshlist } }, { multi: true });
200
+ }
201
+
202
+ // We are done
203
+ validIdentifiers = null;
204
+ if (func) { func(); }
205
+ });
206
+ }
207
+ });
208
+ });
209
+ };
210
+
211
+ // Get encryption key
212
+ obj.getEncryptDataKey = function (password) {
213
+ if (typeof password != 'string') return null;
214
+ return parent.crypto.createHash('sha384').update(password).digest("raw").slice(0, 32);
215
+ }
216
+
217
+ // Encrypt data
218
+ obj.encryptData = function (password, plaintext) {
219
+ var key = obj.getEncryptDataKey(password);
220
+ if (key == null) return null;
221
+ const iv = parent.crypto.randomBytes(16);
222
+ const aes = parent.crypto.createCipheriv('aes-256-cbc', key, iv);
223
+ var ciphertext = aes.update(plaintext);
224
+ ciphertext = Buffer.concat([iv, ciphertext, aes.final()]);
225
+ return ciphertext.toString('base64');
226
+ }
227
+
228
+ // Decrypt data
229
+ obj.decryptData = function (password, ciphertext) {
230
+ try {
231
+ var key = obj.getEncryptDataKey(password);
232
+ if (key == null) return null;
233
+ const ciphertextBytes = Buffer.from(ciphertext, 'base64');
234
+ const iv = ciphertextBytes.slice(0, 16);
235
+ const data = ciphertextBytes.slice(16);
236
+ const aes = parent.crypto.createDecipheriv('aes-256-cbc', key, iv);
237
+ var plaintextBytes = Buffer.from(aes.update(data));
238
+ plaintextBytes = Buffer.concat([plaintextBytes, aes.final()]);
239
+ return plaintextBytes;
240
+ } catch (ex) { return null; }
241
+ }
242
+
243
+ // Get the number of records in the database for various types, this is the slow NeDB way.
244
+ // WARNING: This is a terrible query for database performance. Only do this when needed. This query will look at almost every document in the database.
245
+ obj.getStats = function (func) {
246
+ if (obj.databaseType == 3) {
247
+ // MongoDB
248
+ obj.file.aggregate([{ "$group": { _id: "$type", count: { $sum: 1 } } }]).toArray(function (err, docs) {
249
+ var counters = {}, totalCount = 0;
250
+ if (err == null) { for (var i in docs) { if (docs[i]._id != null) { counters[docs[i]._id] = docs[i].count; totalCount += docs[i].count; } } }
251
+ func(counters);
252
+ });
253
+ } else if (obj.databaseType == 2) {
254
+ // MongoJS
255
+ obj.file.aggregate([{ "$group": { _id: "$type", count: { $sum: 1 } } }], function (err, docs) {
256
+ var counters = {}, totalCount = 0;
257
+ if (err == null) { for (var i in docs) { if (docs[i]._id != null) { counters[docs[i]._id] = docs[i].count; totalCount += docs[i].count; } } }
258
+ func(counters);
259
+ });
260
+ } else if (obj.databaseType == 1) {
261
+ // NeDB version
262
+ obj.file.count({ type: 'node' }, function (err, nodeCount) {
263
+ obj.file.count({ type: 'mesh' }, function (err, meshCount) {
264
+ obj.file.count({ type: 'user' }, function (err, userCount) {
265
+ obj.file.count({ type: 'sysinfo' }, function (err, sysinfoCount) {
266
+ obj.file.count({ type: 'note' }, function (err, noteCount) {
267
+ obj.file.count({ type: 'iploc' }, function (err, iplocCount) {
268
+ obj.file.count({ type: 'ifinfo' }, function (err, ifinfoCount) {
269
+ obj.file.count({ type: 'cfile' }, function (err, cfileCount) {
270
+ obj.file.count({ type: 'lastconnect' }, function (err, lastconnectCount) {
271
+ obj.file.count({}, function (err, totalCount) {
272
+ func({ node: nodeCount, mesh: meshCount, user: userCount, sysinfo: sysinfoCount, iploc: iplocCount, note: noteCount, ifinfo: ifinfoCount, cfile: cfileCount, lastconnect: lastconnectCount, total: totalCount });
273
+ });
274
+ });
275
+ });
276
+ });
277
+ });
278
+ });
279
+ });
280
+ });
281
+ });
282
+ });
283
+ }
284
+ }
285
+
286
+ // This is used to rate limit a number of operation per day. Returns a startValue each new days, but you can substract it and save the value in the db.
287
+ obj.getValueOfTheDay = function (id, startValue, func) { obj.Get(id, function (err, docs) { var date = new Date(), t = date.toLocaleDateString(); if ((err == null) && (docs.length == 1)) { var r = docs[0]; if (r.day == t) { func({ _id: id, value: r.value, day: t }); return; } } func({ _id: id, value: startValue, day: t }); }); };
288
+ obj.escapeBase64 = function escapeBase64(val) { return (val.replace(/\+/g, '@').replace(/\//g, '$')); }
289
+
290
+ // Encrypt an database object
291
+ obj.performRecordEncryptionRecode = function (func) {
292
+ var count = 0;
293
+ obj.GetAllType('user', function (err, docs) {
294
+ if (err != null) { parent.debug('db', 'ERROR (performRecordEncryptionRecode): ' + err); }
295
+ if (err == null) { for (var i in docs) { count++; obj.Set(docs[i]); } }
296
+ obj.GetAllType('node', function (err, docs) {
297
+ if (err == null) { for (var i in docs) { count++; obj.Set(docs[i]); } }
298
+ obj.GetAllType('mesh', function (err, docs) {
299
+ if (err == null) { for (var i in docs) { count++; obj.Set(docs[i]); } }
300
+ if (obj.databaseType == 1) { // If we are using NeDB, compact the database.
301
+ obj.file.persistence.compactDatafile();
302
+ obj.file.on('compaction.done', function () { func(count); }); // It's important to wait for compaction to finish before exit, otherwise NeDB may corrupt.
303
+ } else {
304
+ func(count); // For all other databases, normal exit.
305
+ }
306
+ });
307
+ });
308
+ });
309
+ }
310
+
311
+ // Encrypt an database object
312
+ function performTypedRecordDecrypt(data) {
313
+ if ((data == null) || (obj.dbRecordsDecryptKey == null) || (typeof data != 'object')) return data;
314
+ for (var i in data) {
315
+ if (data[i] == null) continue;
316
+ if (data[i].type == 'user') {
317
+ data[i] = performPartialRecordDecrypt(data[i]);
318
+ } else if ((data[i].type == 'node') && (data[i].intelamt != null)) {
319
+ data[i].intelamt = performPartialRecordDecrypt(data[i].intelamt);
320
+ } else if ((data[i].type == 'mesh') && (data[i].amt != null)) {
321
+ data[i].amt = performPartialRecordDecrypt(data[i].amt);
322
+ }
323
+ }
324
+ return data;
325
+ }
326
+
327
+ // Encrypt an database object
328
+ function performTypedRecordEncrypt(data) {
329
+ if (obj.dbRecordsEncryptKey == null) return data;
330
+ if (data.type == 'user') { return performPartialRecordEncrypt(Clone(data), ['otpkeys', 'otphkeys', 'otpsecret', 'salt', 'hash', 'oldpasswords']); }
331
+ else if ((data.type == 'node') && (data.intelamt != null)) { var xdata = Clone(data); xdata.intelamt = performPartialRecordEncrypt(xdata.intelamt, ['user', 'pass', 'mpspass']); return xdata; }
332
+ else if ((data.type == 'mesh') && (data.amt != null)) { var xdata = Clone(data); xdata.amt = performPartialRecordEncrypt(xdata.amt, ['password']); return xdata; }
333
+ return data;
334
+ }
335
+
336
+ // Encrypt an object and return a buffer.
337
+ function performPartialRecordEncrypt(plainobj, encryptNames) {
338
+ if (typeof plainobj != 'object') return plainobj;
339
+ var enc = {}, enclen = 0;
340
+ for (var i in encryptNames) { if (plainobj[encryptNames[i]] != null) { enclen++; enc[encryptNames[i]] = plainobj[encryptNames[i]]; delete plainobj[encryptNames[i]]; } }
341
+ if (enclen > 0) { plainobj._CRYPT = performRecordEncrypt(enc); } else { delete plainobj._CRYPT; }
342
+ return plainobj;
343
+ }
344
+
345
+ // Encrypt an object and return a buffer.
346
+ function performPartialRecordDecrypt(plainobj) {
347
+ if ((typeof plainobj != 'object') || (plainobj._CRYPT == null)) return plainobj;
348
+ var enc = performRecordDecrypt(plainobj._CRYPT);
349
+ if (enc != null) { for (var i in enc) { plainobj[i] = enc[i]; } }
350
+ delete plainobj._CRYPT;
351
+ return plainobj;
352
+ }
353
+
354
+ // Encrypt an object and return a base64.
355
+ function performRecordEncrypt(plainobj) {
356
+ if (obj.dbRecordsEncryptKey == null) return null;
357
+ const iv = parent.crypto.randomBytes(12);
358
+ const aes = parent.crypto.createCipheriv('aes-256-gcm', obj.dbRecordsEncryptKey, iv);
359
+ var ciphertext = aes.update(JSON.stringify(plainobj));
360
+ var cipherfinal = aes.final();
361
+ ciphertext = Buffer.concat([iv, aes.getAuthTag(), ciphertext, cipherfinal]);
362
+ return ciphertext.toString('base64');
363
+ }
364
+
365
+ // Takes a base64 and return an object.
366
+ function performRecordDecrypt(ciphertext) {
367
+ if (obj.dbRecordsDecryptKey == null) return null;
368
+ const ciphertextBytes = Buffer.from(ciphertext, 'base64');
369
+ const iv = ciphertextBytes.slice(0, 12);
370
+ const data = ciphertextBytes.slice(28);
371
+ const aes = parent.crypto.createDecipheriv('aes-256-gcm', obj.dbRecordsDecryptKey, iv);
372
+ aes.setAuthTag(ciphertextBytes.slice(12, 28));
373
+ var plaintextBytes, r;
374
+ try {
375
+ plaintextBytes = Buffer.from(aes.update(data));
376
+ plaintextBytes = Buffer.concat([plaintextBytes, aes.final()]);
377
+ r = JSON.parse(plaintextBytes.toString());
378
+ } catch (e) { throw "Incorrect DbRecordsDecryptKey/DbRecordsEncryptKey or invalid database _CRYPT data: " + e; }
379
+ return r;
380
+ }
381
+
382
+ // Clone an object (TODO: Make this more efficient)
383
+ function Clone(v) { return JSON.parse(JSON.stringify(v)); }
384
+
385
+ // Read expiration time from configuration file
386
+ if (typeof parent.args.dbexpire == 'object') {
387
+ if (typeof parent.args.dbexpire.events == 'number') { expireEventsSeconds = parent.args.dbexpire.events; }
388
+ if (typeof parent.args.dbexpire.powerevents == 'number') { expirePowerEventsSeconds = parent.args.dbexpire.powerevents; }
389
+ if (typeof parent.args.dbexpire.statsevents == 'number') { expireServerStatsSeconds = parent.args.dbexpire.statsevents; }
390
+ }
391
+
392
+ // If a DB record encryption key is provided, perform database record encryption
393
+ if ((typeof parent.args.dbrecordsencryptkey == 'string') && (parent.args.dbrecordsencryptkey.length != 0)) {
394
+ // Hash the database password into a AES256 key and setup encryption and decryption.
395
+ obj.dbRecordsEncryptKey = obj.dbRecordsDecryptKey = parent.crypto.createHash('sha384').update(parent.args.dbrecordsencryptkey).digest('raw').slice(0, 32);
396
+ }
397
+
398
+ // If a DB record decryption key is provided, perform database record decryption
399
+ if ((typeof parent.args.dbrecordsdecryptkey == 'string') && (parent.args.dbrecordsdecryptkey.length != 0)) {
400
+ // Hash the database password into a AES256 key and setup encryption and decryption.
401
+ obj.dbRecordsDecryptKey = parent.crypto.createHash('sha384').update(parent.args.dbrecordsdecryptkey).digest('raw').slice(0, 32);
402
+ }
403
+
404
+ if (parent.args.mariadb || parent.args.mysql) {
405
+ if (parent.args.mariadb) {
406
+ // Use MariaDB
407
+ obj.databaseType = 4;
408
+ Datastore = require('mariadb').createPool(parent.args.mariadb);
409
+ } else if (parent.args.mysql) {
410
+ // Use MySQL
411
+ Datastore = require('mysql').createConnection(parent.args.mysql);
412
+ obj.databaseType = 5;
413
+ }
414
+ //sqlDbQuery('DROP DATABASE MeshCentral', null, function (err, docs) { console.log('DROP'); }); return;
415
+ sqlDbQuery('USE meshcentral', null, function (err, docs) {
416
+ if (err != null) { parent.debug('db', 'ERROR: USE meshcentral: ' + err); }
417
+ if (err == null) { setupFunctions(func); } else {
418
+ parent.debug('db', 'Creating database...');
419
+ sqlDbBatchExec([
420
+ 'CREATE DATABASE meshcentral',
421
+ // Main table
422
+ 'CREATE TABLE meshcentral.main (id VARCHAR(256) NOT NULL, type CHAR(32), domain CHAR(64), extra CHAR(255), extraex CHAR(255), doc JSON, PRIMARY KEY(id), CHECK (json_valid(doc)))',
423
+ 'CREATE INDEX ndxtypedomainextra ON meshcentral.main (type, domain, extra)',
424
+ 'CREATE INDEX ndxextra ON meshcentral.main (extra)',
425
+ 'CREATE INDEX ndxextraex ON meshcentral.main (extraex)',
426
+ // Events table
427
+ 'CREATE TABLE meshcentral.events(id INT NOT NULL AUTO_INCREMENT, time DATETIME, domain CHAR(64), action CHAR(255), nodeid CHAR(255), userid CHAR(255), doc JSON, PRIMARY KEY(id), CHECK(json_valid(doc)))',
428
+ 'CREATE INDEX ndxeventstime ON meshcentral.events(time)',
429
+ 'CREATE INDEX ndxeventsusername ON meshcentral.events(domain, userid, time)',
430
+ 'CREATE INDEX ndxeventsdomainnodeidtime ON meshcentral.events(domain, nodeid, time)',
431
+ // Events ID table
432
+ 'CREATE TABLE meshcentral.eventids(fkid INT NOT NULL, target CHAR(255), CONSTRAINT fk_eventid FOREIGN KEY (fkid) REFERENCES events (id) ON DELETE CASCADE ON UPDATE RESTRICT)',
433
+ 'CREATE INDEX ndxeventids ON meshcentral.eventids(target)',
434
+ // Server stats table
435
+ 'CREATE TABLE meshcentral.serverstats (time DATETIME, expire DATETIME, doc JSON, PRIMARY KEY(time), CHECK (json_valid(doc)))',
436
+ 'CREATE INDEX ndxserverstattime ON meshcentral.serverstats (time)',
437
+ 'CREATE INDEX ndxserverstatexpire ON meshcentral.serverstats (expire)',
438
+ // Power events table
439
+ 'CREATE TABLE meshcentral.power (id INT NOT NULL AUTO_INCREMENT, time DATETIME, nodeid CHAR(255), doc JSON, PRIMARY KEY(id), CHECK (json_valid(doc)))',
440
+ 'CREATE INDEX ndxpowernodeidtime ON meshcentral.power (nodeid, time)',
441
+ // SMBIOS table
442
+ 'CREATE TABLE meshcentral.smbios (id CHAR(255), time DATETIME, expire DATETIME, doc JSON, PRIMARY KEY(id), CHECK (json_valid(doc)))',
443
+ 'CREATE INDEX ndxsmbiostime ON meshcentral.smbios (time)',
444
+ 'CREATE INDEX ndxsmbiosexpire ON meshcentral.smbios (expire)',
445
+ // Plugins table
446
+ 'CREATE TABLE meshcentral.plugin (id INT NOT NULL AUTO_INCREMENT, doc JSON, PRIMARY KEY(id), CHECK (json_valid(doc)))'
447
+ ], function (err) {
448
+ if (err != null) { parent.debug('db', 'BatchSetupDb: ' + err); }
449
+ setupFunctions(func);
450
+ });
451
+ }
452
+ });
453
+ } else if (parent.args.mongodb) {
454
+ // Use MongoDB
455
+ obj.databaseType = 3;
456
+ require('mongodb').MongoClient.connect(parent.args.mongodb, { useNewUrlParser: true, useUnifiedTopology: true }, function (err, client) {
457
+ if (err != null) { console.log("Unable to connect to database: " + err); process.exit(); return; }
458
+ Datastore = client;
459
+ parent.debug('db', 'Connected to MongoDB database...');
460
+
461
+ // Get the database name and setup the database client
462
+ var dbname = 'meshcentral';
463
+ if (parent.args.mongodbname) { dbname = parent.args.mongodbname; }
464
+ const dbcollectionname = (parent.args.mongodbcol) ? (parent.args.mongodbcol) : 'meshcentral';
465
+ const db = client.db(dbname);
466
+
467
+ // Check the database version
468
+ db.admin().serverInfo(function (err, info) {
469
+ if ((err != null) || (info == null) || (info.versionArray == null) || (Array.isArray(info.versionArray) == false) || (info.versionArray.length < 2) || (typeof info.versionArray[0] != 'number') || (typeof info.versionArray[1] != 'number')) {
470
+ console.log('WARNING: Unable to check MongoDB version.');
471
+ } else {
472
+ if ((info.versionArray[0] < 3) || ((info.versionArray[0] == 3) && (info.versionArray[1] < 6))) {
473
+ // We are running with mongoDB older than 3.6, this is not good.
474
+ parent.addServerWarning("Current version of MongoDB (" + info.version + ") is too old, please upgrade to MongoDB 3.6 or better.");
475
+ }
476
+ }
477
+ });
478
+
479
+ // Setup MongoDB main collection and indexes
480
+ obj.file = db.collection(dbcollectionname);
481
+ obj.file.indexes(function (err, indexes) {
482
+ // Check if we need to reset indexes
483
+ var indexesByName = {}, indexCount = 0;
484
+ for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
485
+ if ((indexCount != 4) || (indexesByName['TypeDomainMesh1'] == null) || (indexesByName['Email1'] == null) || (indexesByName['Mesh1'] == null)) {
486
+ console.log('Resetting main indexes...');
487
+ obj.file.dropIndexes(function (err) {
488
+ obj.file.createIndex({ type: 1, domain: 1, meshid: 1 }, { sparse: 1, name: 'TypeDomainMesh1' }); // Speeds up GetAllTypeNoTypeField() and GetAllTypeNoTypeFieldMeshFiltered()
489
+ obj.file.createIndex({ email: 1 }, { sparse: 1, name: 'Email1' }); // Speeds up GetUserWithEmail() and GetUserWithVerifiedEmail()
490
+ obj.file.createIndex({ meshid: 1 }, { sparse: 1, name: 'Mesh1' }); // Speeds up RemoveMesh()
491
+ });
492
+ }
493
+ });
494
+
495
+ // Setup the changeStream on the MongoDB main collection if possible
496
+ if (parent.args.mongodbchangestream == true) {
497
+ if (typeof obj.file.watch != 'function') {
498
+ console.log('WARNING: watch() is not a function, MongoDB ChangeStream not supported.');
499
+ } else {
500
+ obj.fileChangeStream = obj.file.watch([{ $match: { $or: [{ 'fullDocument.type': { $in: ['node', 'mesh', 'user', 'ugrp'] } }, { 'operationType': 'delete' }] } }], { fullDocument: 'updateLookup' });
501
+ obj.fileChangeStream.on('change', function (change) {
502
+ if ((change.operationType == 'update') || (change.operationType == 'replace')) {
503
+ switch (change.fullDocument.type) {
504
+ case 'node': { dbNodeChange(change, false); break; } // A node has changed
505
+ case 'mesh': { dbMeshChange(change, false); break; } // A device group has changed
506
+ case 'user': { dbUserChange(change, false); break; } // A user account has changed
507
+ case 'ugrp': { dbUGrpChange(change, false); break; } // A user account has changed
508
+ }
509
+ } else if (change.operationType == 'insert') {
510
+ switch (change.fullDocument.type) {
511
+ case 'node': { dbNodeChange(change, true); break; } // A node has added
512
+ case 'mesh': { dbMeshChange(change, true); break; } // A device group has created
513
+ case 'user': { dbUserChange(change, true); break; } // A user account has created
514
+ case 'ugrp': { dbUGrpChange(change, true); break; } // A user account has created
515
+ }
516
+ } else if (change.operationType == 'delete') {
517
+ if ((change.documentKey == null) || (change.documentKey._id == null)) return;
518
+ var splitId = change.documentKey._id.split('/');
519
+ switch (splitId[0]) {
520
+ case 'node': {
521
+ //Not Good: Problem here is that we don't know what meshid the node belonged to before the delete.
522
+ //parent.DispatchEvent(['*', node.meshid], obj, { etype: 'node', action: 'removenode', nodeid: change.documentKey._id, domain: splitId[1] });
523
+ break;
524
+ }
525
+ case 'mesh': {
526
+ parent.DispatchEvent(['*', change.documentKey._id], obj, { etype: 'mesh', action: 'deletemesh', meshid: change.documentKey._id, domain: splitId[1] });
527
+ break;
528
+ }
529
+ case 'user': {
530
+ //Not Good: This is not a perfect user removal because we don't know what groups the user was in.
531
+ //parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', action: 'accountremove', userid: change.documentKey._id, domain: splitId[1], username: splitId[2] });
532
+ break;
533
+ }
534
+ case 'ugrp': {
535
+ parent.DispatchEvent(['*', change.documentKey._id], obj, { etype: 'ugrp', action: 'deleteusergroup', ugrpid: change.documentKey._id, domain: splitId[1] });
536
+ break;
537
+ }
538
+ }
539
+ }
540
+ });
541
+ obj.changeStream = true;
542
+ }
543
+ }
544
+
545
+ // Setup MongoDB events collection and indexes
546
+ obj.eventsfile = db.collection('events'); // Collection containing all events
547
+ obj.eventsfile.indexes(function (err, indexes) {
548
+ // Check if we need to reset indexes
549
+ var indexesByName = {}, indexCount = 0;
550
+ for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
551
+ if ((indexCount != 5) || (indexesByName['Username1'] == null) || (indexesByName['DomainNodeTime1'] == null) || (indexesByName['IdsAndTime1'] == null) || (indexesByName['ExpireTime1'] == null)) {
552
+ // Reset all indexes
553
+ console.log("Resetting events indexes...");
554
+ obj.eventsfile.dropIndexes(function (err) {
555
+ obj.eventsfile.createIndex({ username: 1 }, { sparse: 1, name: 'Username1' });
556
+ obj.eventsfile.createIndex({ domain: 1, nodeid: 1, time: -1 }, { sparse: 1, name: 'DomainNodeTime1' });
557
+ obj.eventsfile.createIndex({ ids: 1, time: -1 }, { sparse: 1, name: 'IdsAndTime1' });
558
+ obj.eventsfile.createIndex({ time: 1 }, { expireAfterSeconds: expireEventsSeconds, name: 'ExpireTime1' });
559
+ });
560
+ } else if (indexesByName['ExpireTime1'].expireAfterSeconds != expireEventsSeconds) {
561
+ // Reset the timeout index
562
+ console.log("Resetting events expire index...");
563
+ obj.eventsfile.dropIndex('ExpireTime1', function (err) {
564
+ obj.eventsfile.createIndex({ time: 1 }, { expireAfterSeconds: expireEventsSeconds, name: 'ExpireTime1' });
565
+ });
566
+ }
567
+ });
568
+
569
+ // Setup MongoDB power events collection and indexes
570
+ obj.powerfile = db.collection('power'); // Collection containing all power events
571
+ obj.powerfile.indexes(function (err, indexes) {
572
+ // Check if we need to reset indexes
573
+ var indexesByName = {}, indexCount = 0;
574
+ for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
575
+ if ((indexCount != 3) || (indexesByName['NodeIdAndTime1'] == null) || (indexesByName['ExpireTime1'] == null)) {
576
+ // Reset all indexes
577
+ console.log("Resetting power events indexes...");
578
+ obj.powerfile.dropIndexes(function (err) {
579
+ // Create all indexes
580
+ obj.powerfile.createIndex({ nodeid: 1, time: 1 }, { sparse: 1, name: 'NodeIdAndTime1' });
581
+ obj.powerfile.createIndex({ 'time': 1 }, { expireAfterSeconds: expirePowerEventsSeconds, name: 'ExpireTime1' });
582
+ });
583
+ } else if (indexesByName['ExpireTime1'].expireAfterSeconds != expirePowerEventsSeconds) {
584
+ // Reset the timeout index
585
+ console.log("Resetting power events expire index...");
586
+ obj.powerfile.dropIndex('ExpireTime1', function (err) {
587
+ // Reset the expire power events index
588
+ obj.powerfile.createIndex({ 'time': 1 }, { expireAfterSeconds: expirePowerEventsSeconds, name: 'ExpireTime1' });
589
+ });
590
+ }
591
+ });
592
+
593
+ // Setup MongoDB smbios collection, no indexes needed
594
+ obj.smbiosfile = db.collection('smbios'); // Collection containing all smbios information
595
+
596
+ // Setup MongoDB server stats collection
597
+ obj.serverstatsfile = db.collection('serverstats'); // Collection of server stats
598
+ obj.serverstatsfile.indexes(function (err, indexes) {
599
+ // Check if we need to reset indexes
600
+ var indexesByName = {}, indexCount = 0;
601
+ for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
602
+ if ((indexCount != 3) || (indexesByName['ExpireTime1'] == null)) {
603
+ // Reset all indexes
604
+ console.log("Resetting server stats indexes...");
605
+ obj.serverstatsfile.dropIndexes(function (err) {
606
+ // Create all indexes
607
+ obj.serverstatsfile.createIndex({ 'time': 1 }, { expireAfterSeconds: expireServerStatsSeconds, name: 'ExpireTime1' });
608
+ obj.serverstatsfile.createIndex({ 'expire': 1 }, { expireAfterSeconds: 0, name: 'ExpireTime2' }); // Auto-expire events
609
+ });
610
+ } else if (indexesByName['ExpireTime1'].expireAfterSeconds != expireServerStatsSeconds) {
611
+ // Reset the timeout index
612
+ console.log("Resetting server stats expire index...");
613
+ obj.serverstatsfile.dropIndex('ExpireTime1', function (err) {
614
+ // Reset the expire server stats index
615
+ obj.serverstatsfile.createIndex({ 'time': 1 }, { expireAfterSeconds: expireServerStatsSeconds, name: 'ExpireTime1' });
616
+ });
617
+ }
618
+ });
619
+
620
+ // Setup plugin info collection
621
+ if (obj.pluginsActive) { obj.pluginsfile = db.collection('plugins'); }
622
+
623
+ setupFunctions(func); // Completed setup of MongoDB
624
+ });
625
+ } else if (parent.args.xmongodb) {
626
+ // Use MongoJS, this is the old system.
627
+ obj.databaseType = 2;
628
+ Datastore = require('mongojs');
629
+ var db = Datastore(parent.args.xmongodb);
630
+ var dbcollection = 'meshcentral';
631
+ if (parent.args.mongodbcol) { dbcollection = parent.args.mongodbcol; }
632
+
633
+ // Setup MongoDB main collection and indexes
634
+ obj.file = db.collection(dbcollection);
635
+ obj.file.getIndexes(function (err, indexes) {
636
+ // Check if we need to reset indexes
637
+ var indexesByName = {}, indexCount = 0;
638
+ for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
639
+ if ((indexCount != 4) || (indexesByName['TypeDomainMesh1'] == null) || (indexesByName['Email1'] == null) || (indexesByName['Mesh1'] == null)) {
640
+ console.log("Resetting main indexes...");
641
+ obj.file.dropIndexes(function (err) {
642
+ obj.file.createIndex({ type: 1, domain: 1, meshid: 1 }, { sparse: 1, name: 'TypeDomainMesh1' }); // Speeds up GetAllTypeNoTypeField() and GetAllTypeNoTypeFieldMeshFiltered()
643
+ obj.file.createIndex({ email: 1 }, { sparse: 1, name: 'Email1' }); // Speeds up GetUserWithEmail() and GetUserWithVerifiedEmail()
644
+ obj.file.createIndex({ meshid: 1 }, { sparse: 1, name: 'Mesh1' }); // Speeds up RemoveMesh()
645
+ });
646
+ }
647
+ });
648
+
649
+ // Setup MongoDB events collection and indexes
650
+ obj.eventsfile = db.collection('events'); // Collection containing all events
651
+ obj.eventsfile.getIndexes(function (err, indexes) {
652
+ // Check if we need to reset indexes
653
+ var indexesByName = {}, indexCount = 0;
654
+ for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
655
+ if ((indexCount != 5) || (indexesByName['Username1'] == null) || (indexesByName['DomainNodeTime1'] == null) || (indexesByName['IdsAndTime1'] == null) || (indexesByName['ExpireTime1'] == null)) {
656
+ // Reset all indexes
657
+ console.log("Resetting events indexes...");
658
+ obj.eventsfile.dropIndexes(function (err) {
659
+ obj.eventsfile.createIndex({ username: 1 }, { sparse: 1, name: 'Username1' });
660
+ obj.eventsfile.createIndex({ domain: 1, nodeid: 1, time: -1 }, { sparse: 1, name: 'DomainNodeTime1' });
661
+ obj.eventsfile.createIndex({ ids: 1, time: -1 }, { sparse: 1, name: 'IdsAndTime1' });
662
+ obj.eventsfile.createIndex({ time: 1 }, { expireAfterSeconds: expireEventsSeconds, name: 'ExpireTime1' });
663
+ });
664
+ } else if (indexesByName['ExpireTime1'].expireAfterSeconds != expireEventsSeconds) {
665
+ // Reset the timeout index
666
+ console.log("Resetting events expire index...");
667
+ obj.eventsfile.dropIndex('ExpireTime1', function (err) {
668
+ obj.eventsfile.createIndex({ time: 1 }, { expireAfterSeconds: expireEventsSeconds, name: 'ExpireTime1' });
669
+ });
670
+ }
671
+ });
672
+
673
+ // Setup MongoDB power events collection and indexes
674
+ obj.powerfile = db.collection('power'); // Collection containing all power events
675
+ obj.powerfile.getIndexes(function (err, indexes) {
676
+ // Check if we need to reset indexes
677
+ var indexesByName = {}, indexCount = 0;
678
+ for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
679
+ if ((indexCount != 3) || (indexesByName['NodeIdAndTime1'] == null) || (indexesByName['ExpireTime1'] == null)) {
680
+ // Reset all indexes
681
+ console.log("Resetting power events indexes...");
682
+ obj.powerfile.dropIndexes(function (err) {
683
+ // Create all indexes
684
+ obj.powerfile.createIndex({ nodeid: 1, time: 1 }, { sparse: 1, name: 'NodeIdAndTime1' });
685
+ obj.powerfile.createIndex({ 'time': 1 }, { expireAfterSeconds: expirePowerEventsSeconds, name: 'ExpireTime1' });
686
+ });
687
+ } else if (indexesByName['ExpireTime1'].expireAfterSeconds != expirePowerEventsSeconds) {
688
+ // Reset the timeout index
689
+ console.log("Resetting power events expire index...");
690
+ obj.powerfile.dropIndex('ExpireTime1', function (err) {
691
+ // Reset the expire power events index
692
+ obj.powerfile.createIndex({ 'time': 1 }, { expireAfterSeconds: expirePowerEventsSeconds, name: 'ExpireTime1' });
693
+ });
694
+ }
695
+ });
696
+
697
+ // Setup MongoDB smbios collection, no indexes needed
698
+ obj.smbiosfile = db.collection('smbios'); // Collection containing all smbios information
699
+
700
+ // Setup MongoDB server stats collection
701
+ obj.serverstatsfile = db.collection('serverstats'); // Collection of server stats
702
+ obj.serverstatsfile.getIndexes(function (err, indexes) {
703
+ // Check if we need to reset indexes
704
+ var indexesByName = {}, indexCount = 0;
705
+ for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; }
706
+ if ((indexCount != 3) || (indexesByName['ExpireTime1'] == null)) {
707
+ // Reset all indexes
708
+ console.log("Resetting server stats indexes...");
709
+ obj.serverstatsfile.dropIndexes(function (err) {
710
+ // Create all indexes
711
+ obj.serverstatsfile.createIndex({ 'time': 1 }, { expireAfterSeconds: expireServerStatsSeconds, name: 'ExpireTime1' });
712
+ obj.serverstatsfile.createIndex({ 'expire': 1 }, { expireAfterSeconds: 0, name: 'ExpireTime2' }); // Auto-expire events
713
+ });
714
+ } else if (indexesByName['ExpireTime1'].expireAfterSeconds != expireServerStatsSeconds) {
715
+ // Reset the timeout index
716
+ console.log("Resetting server stats expire index...");
717
+ obj.serverstatsfile.dropIndex('ExpireTime1', function (err) {
718
+ // Reset the expire server stats index
719
+ obj.serverstatsfile.createIndex({ 'time': 1 }, { expireAfterSeconds: expireServerStatsSeconds, name: 'ExpireTime1' });
720
+ });
721
+ }
722
+ });
723
+
724
+ // Setup plugin info collection
725
+ if (obj.pluginsActive) { obj.pluginsfile = db.collection('plugins'); }
726
+
727
+ setupFunctions(func); // Completed setup of MongoJS
728
+ } else {
729
+ // Use NeDB (The default)
730
+ obj.databaseType = 1;
731
+ Datastore = require('nedb');
732
+ var datastoreOptions = { filename: parent.getConfigFilePath('meshcentral.db'), autoload: true };
733
+
734
+ // If a DB encryption key is provided, perform database encryption
735
+ if ((typeof parent.args.dbencryptkey == 'string') && (parent.args.dbencryptkey.length != 0)) {
736
+ // Hash the database password into a AES256 key and setup encryption and decryption.
737
+ obj.dbKey = parent.crypto.createHash('sha384').update(parent.args.dbencryptkey).digest('raw').slice(0, 32);
738
+ datastoreOptions.afterSerialization = function (plaintext) {
739
+ const iv = parent.crypto.randomBytes(16);
740
+ const aes = parent.crypto.createCipheriv('aes-256-cbc', obj.dbKey, iv);
741
+ var ciphertext = aes.update(plaintext);
742
+ ciphertext = Buffer.concat([iv, ciphertext, aes.final()]);
743
+ return ciphertext.toString('base64');
744
+ }
745
+ datastoreOptions.beforeDeserialization = function (ciphertext) {
746
+ const ciphertextBytes = Buffer.from(ciphertext, 'base64');
747
+ const iv = ciphertextBytes.slice(0, 16);
748
+ const data = ciphertextBytes.slice(16);
749
+ const aes = parent.crypto.createDecipheriv('aes-256-cbc', obj.dbKey, iv);
750
+ var plaintextBytes = Buffer.from(aes.update(data));
751
+ plaintextBytes = Buffer.concat([plaintextBytes, aes.final()]);
752
+ return plaintextBytes.toString();
753
+ }
754
+ }
755
+
756
+ // Start NeDB main collection and setup indexes
757
+ obj.file = new Datastore(datastoreOptions);
758
+ obj.file.persistence.setAutocompactionInterval(86400000); // Compact once a day
759
+ obj.file.ensureIndex({ fieldName: 'type' });
760
+ obj.file.ensureIndex({ fieldName: 'domain' });
761
+ obj.file.ensureIndex({ fieldName: 'meshid', sparse: true });
762
+ obj.file.ensureIndex({ fieldName: 'nodeid', sparse: true });
763
+ obj.file.ensureIndex({ fieldName: 'email', sparse: true });
764
+
765
+ // Setup the events collection and setup indexes
766
+ obj.eventsfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-events.db'), autoload: true, corruptAlertThreshold: 1 });
767
+ obj.eventsfile.persistence.setAutocompactionInterval(86400000); // Compact once a day
768
+ obj.eventsfile.ensureIndex({ fieldName: 'ids' }); // TODO: Not sure if this is a good index, this is a array field.
769
+ obj.eventsfile.ensureIndex({ fieldName: 'nodeid', sparse: true });
770
+ obj.eventsfile.ensureIndex({ fieldName: 'time', expireAfterSeconds: expireEventsSeconds });
771
+ obj.eventsfile.remove({ time: { '$lt': new Date(Date.now() - (expireEventsSeconds * 1000)) } }, { multi: true }); // Force delete older events
772
+
773
+ // Setup the power collection and setup indexes
774
+ obj.powerfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-power.db'), autoload: true, corruptAlertThreshold: 1 });
775
+ obj.powerfile.persistence.setAutocompactionInterval(86400000); // Compact once a day
776
+ obj.powerfile.ensureIndex({ fieldName: 'nodeid' });
777
+ obj.powerfile.ensureIndex({ fieldName: 'time', expireAfterSeconds: expirePowerEventsSeconds });
778
+ obj.powerfile.remove({ time: { '$lt': new Date(Date.now() - (expirePowerEventsSeconds * 1000)) } }, { multi: true }); // Force delete older events
779
+
780
+ // Setup the SMBIOS collection, for NeDB we don't setup SMBIOS since NeDB will corrupt the database. Remove any existing ones.
781
+ //obj.smbiosfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-smbios.db'), autoload: true, corruptAlertThreshold: 1 });
782
+ parent.fs.unlink(parent.getConfigFilePath('meshcentral-smbios.db'), function () { });
783
+
784
+ // Setup the server stats collection and setup indexes
785
+ obj.serverstatsfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-stats.db'), autoload: true, corruptAlertThreshold: 1 });
786
+ obj.serverstatsfile.persistence.setAutocompactionInterval(86400000); // Compact once a day
787
+ obj.serverstatsfile.ensureIndex({ fieldName: 'time', expireAfterSeconds: expireServerStatsSeconds });
788
+ obj.serverstatsfile.ensureIndex({ fieldName: 'expire', expireAfterSeconds: 0 }); // Auto-expire events
789
+ obj.serverstatsfile.remove({ time: { '$lt': new Date(Date.now() - (expireServerStatsSeconds * 1000)) } }, { multi: true }); // Force delete older events
790
+
791
+ // Setup plugin info collection
792
+ if (obj.pluginsActive) {
793
+ obj.pluginsfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-plugins.db'), autoload: true });
794
+ obj.pluginsfile.persistence.setAutocompactionInterval(86400000); // Compact once a day
795
+ }
796
+
797
+ setupFunctions(func); // Completed setup of NeDB
798
+ }
799
+
800
+ // Check the object names for a "."
801
+ function checkObjectNames(r, tag) {
802
+ if (typeof r != 'object') return;
803
+ for (var i in r) {
804
+ if (i.indexOf('.') >= 0) { throw ('BadDbName (' + tag + '): ' + JSON.stringify(r)); }
805
+ checkObjectNames(r[i], tag);
806
+ }
807
+ }
808
+
809
+ // Query the database
810
+ function sqlDbQuery(query, args, func) {
811
+ if (obj.databaseType == 4) { // MariaDB
812
+ Datastore.getConnection()
813
+ .then(function (conn) {
814
+ conn.query(query, args)
815
+ .then(function (rows) {
816
+ conn.release();
817
+ const docs = [];
818
+ for (var i in rows) { if (rows[i].doc) { docs.push(performTypedRecordDecrypt((typeof rows[i].doc == 'object')? rows[i].doc : JSON.parse(rows[i].doc))); } }
819
+ if (func) try { func(null, docs); } catch (ex) { console.log('SQLERR1', ex); }
820
+ })
821
+ .catch(function (err) { conn.release(); if (func) try { func(err); } catch (ex) { console.log('SQLERR2', ex); } });
822
+ }).catch(function (err) { if (func) { try { func(err); } catch (ex) { console.log('SQLERR3', ex); } } });
823
+ } else if (obj.databaseType == 5) { // MySQL
824
+ Datastore.query(query, args, function (error, results, fields) {
825
+ if (error != null) {
826
+ if (func) try { func(error); } catch (ex) { console.log('SQLERR4', ex); }
827
+ } else {
828
+ var docs = [];
829
+ for (var i in results) { if (results[i].doc) { docs.push(JSON.parse(results[i].doc)); } }
830
+ //console.log(docs);
831
+ if (func) { try { func(null, docs); } catch (ex) { console.log('SQLERR5', ex); } }
832
+ }
833
+ });
834
+ }
835
+ }
836
+
837
+ // Exec on the database
838
+ function sqlDbExec(query, args, func) {
839
+ if (obj.databaseType == 4) { // MariaDB
840
+ Datastore.getConnection()
841
+ .then(function (conn) {
842
+ conn.query(query, args)
843
+ .then(function (rows) {
844
+ conn.release();
845
+ if (func) try { func(null, rows[0]); } catch (ex) { console.log(ex); }
846
+ })
847
+ .catch(function (err) { conn.release(); if (func) try { func(err); } catch (ex) { console.log(ex); } });
848
+ }).catch(function (err) { if (func) { try { func(err); } catch (ex) { console.log(ex); } } });
849
+ } else if (obj.databaseType == 5) { // MySQL
850
+ Datastore.query(query, args, function (error, results, fields) {
851
+ if (func) try { func(error, results[0]); } catch (ex) { console.log(ex); }
852
+ });
853
+ }
854
+ }
855
+
856
+ // Execute a batch of commands on the database
857
+ function sqlDbBatchExec(queries, func) {
858
+ if (obj.databaseType == 4) { // MariaDB
859
+ Datastore.getConnection()
860
+ .then(function (conn) {
861
+ var Promises = [];
862
+ for (var i in queries) { if (typeof queries[i] == 'string') { Promises.push(conn.query(queries[i])); } else { Promises.push(conn.query(queries[i][0], queries[i][1])); } }
863
+ Promise.all(Promises)
864
+ .then(function (rows) { conn.release(); if (func) { try { func(null); } catch (ex) { console.log(ex); } } })
865
+ .catch(function (err) { conn.release(); if (func) { try { func(err); } catch (ex) { console.log(ex); } } });
866
+ })
867
+ .catch(function (err) { if (func) { try { func(err); } catch (ex) { console.log(ex); } } });
868
+ } else if (obj.databaseType == 5) { // MySQL
869
+ var Promises = [];
870
+ for (var i in queries) { if (typeof queries[i] == 'string') { Promises.push(Datastore.query(queries[i])); } else { Promises.push(Datastore.query(queries[i][0], queries[i][1])); } }
871
+ Promise.all(Promises)
872
+ .then(function (error, results, fields) { if (func) { try { func(error, results); } catch (ex) { console.log(ex); } } })
873
+ .catch(function (error, results, fields) { if (func) { try { func(error); } catch (ex) { console.log(ex); } } });
874
+ }
875
+ }
876
+
877
+ function setupFunctions(func) {
878
+ if ((obj.databaseType == 4) || (obj.databaseType == 5)) {
879
+ // Database actions on the main collection (MariaDB or MySQL)
880
+ obj.Set = function (value, func) {
881
+ var extra = null, extraex = null;
882
+ value = common.escapeLinksFieldNameEx(value);
883
+ if (value.meshid) { extra = value.meshid; } else if (value.email) { extra = 'email/' + value.email; } else if (value.nodeid) { extra = value.nodeid; }
884
+ if ((value.type == 'node') && (value.intelamt != null) && (value.intelamt.uuid != null)) { extraex = 'uuid/' + value.intelamt.uuid; }
885
+ if (value._id == null) { value._id = require('crypto').randomBytes(16).toString('hex'); }
886
+ sqlDbQuery('REPLACE INTO meshcentral.main VALUE (?, ?, ?, ?, ?, ?)', [value._id, (value.type ? value.type : null), ((value.domain != null) ? value.domain : null), extra, extraex, JSON.stringify(performTypedRecordEncrypt(value))], func);
887
+ }
888
+ obj.Get = function (_id, func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE id = ?', [_id], function (err, docs) { if ((docs != null) && (docs.length > 0) && (docs[0].links != null)) { docs[0] = common.unEscapeLinksFieldName(docs[0]); } func(err, docs); }); }
889
+ obj.GetAll = function (func) { sqlDbQuery('SELECT domain, doc FROM meshcentral.main', null, func); }
890
+ obj.GetHash = function (id, func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE id = ?', [id], func); }
891
+ obj.GetAllTypeNoTypeField = function (type, domain, func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE type = ? AND domain = ?', [type, domain], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, docs); }); };
892
+ obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, func) {
893
+ if (id && (id != '')) {
894
+ sqlDbQuery('SELECT doc FROM meshcentral.main WHERE id = ? AND type = ? AND domain = ? AND extra IN (?)', [id, type, domain, meshes], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, docs); });
895
+ } else {
896
+ if (extrasids == null) {
897
+ sqlDbQuery('SELECT doc FROM meshcentral.main WHERE type = ? AND domain = ? AND extra IN (?)', [type, domain, meshes], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, docs); });
898
+ } else {
899
+ sqlDbQuery('SELECT doc FROM meshcentral.main WHERE type = ? AND domain = ? AND (extra IN (?) OR id IN (?))', [type, domain, meshes, extrasids], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, docs); });
900
+ }
901
+ }
902
+ };
903
+ obj.GetAllTypeNodeFiltered = function (nodes, domain, type, id, func) {
904
+ if (id && (id != '')) {
905
+ sqlDbQuery('SELECT doc FROM meshcentral.main WHERE id = ? AND type = ? AND domain = ? AND extra IN (?)', [id, type, domain, nodes], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, docs); });
906
+ } else {
907
+ sqlDbQuery('SELECT doc FROM meshcentral.main WHERE type = ? AND domain = ? AND extra IN (?)', [type, domain, nodes], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, docs); });
908
+ }
909
+ };
910
+ obj.GetAllType = function (type, func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE type = ?', [type], func); }
911
+ obj.GetAllIdsOfType = function (ids, domain, type, func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE id IN (?) AND domain = ? AND type = ?', [ids, domain, type], func); }
912
+ obj.GetUserWithEmail = function (domain, email, func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE domain = ? AND extra = ?', [domain, 'email/' + email], func); }
913
+ obj.GetUserWithVerifiedEmail = function (domain, email, func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE domain = ? AND extra = ?', [domain, 'email/' + email], func); }
914
+ obj.Remove = function (id, func) { sqlDbQuery('DELETE FROM meshcentral.main WHERE id = ?', [id], func); };
915
+ obj.RemoveAll = function (func) { sqlDbQuery('DELETE FROM meshcentral.main', null, func); };
916
+ obj.RemoveAllOfType = function (type, func) { sqlDbQuery('DELETE FROM meshcentral.main WHERE type = ?', [type], func); };
917
+ obj.InsertMany = function (data, func) { var pendingOps = 0; for (var i in data) { pendingOps++; obj.Set(data[i], function () { if (--pendingOps == 0) { func(); } }); } };
918
+ obj.RemoveMeshDocuments = function (id) { sqlDbQuery('DELETE FROM meshcentral.main WHERE extra = ?', [id], function () { sqlDbQuery('DELETE FROM meshcentral.main WHERE id = ?', ['nt' + id], func); } ); };
919
+ obj.MakeSiteAdmin = function (username, domain) { obj.Get('user/' + domain + '/' + username, function (err, docs) { if ((err == null) && (docs.length == 1)) { docs[0].siteadmin = 0xFFFFFFFF; obj.Set(docs[0]); } }); };
920
+ obj.DeleteDomain = function (domain, func) { sqlDbQuery('DELETE FROM meshcentral.main WHERE domain = ?', [domain], func); };
921
+ obj.SetUser = function (user) { if (user.subscriptions != null) { var u = Clone(user); if (u.subscriptions) { delete u.subscriptions; } obj.Set(u); } else { obj.Set(user); } };
922
+ obj.dispose = function () { for (var x in obj) { if (obj[x].close) { obj[x].close(); } delete obj[x]; } };
923
+ obj.getLocalAmtNodes = function (func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE (type = "node") AND (extraex IS NOT NULL)', null, function (err, docs) { var r = []; if (err == null) { for (var i in docs) { if (docs[i].host != null) { r.push(docs[i]); } } } func(err, r); }); };
924
+ obj.getAmtUuidMeshNode = function (meshid, uuid, func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE meshid = ? AND extraex = ?', [meshid, 'uuid/' + uuid], func); };
925
+ obj.getAmtUuidNode = function (uuid, func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE type = "node" AND extraex = ?', ['uuid/' + uuid], func); };
926
+ obj.isMaxType = function (max, type, domainid, func) { if (max == null) { func(false); } else { sqlDbExec('SELECT COUNT(id) FROM meshcentral.main WHERE domain = ? AND type = ?', [domainid, type], function (err, response) { func((response['COUNT(id)'] == null) || (response['COUNT(id)'] > max), response['COUNT(id)']) }); } }
927
+
928
+ // Database actions on the events collection
929
+ obj.GetAllEvents = function (func) { sqlDbQuery('SELECT doc FROM meshcentral.events', null, func); };
930
+ obj.StoreEvent = function (event, func) {
931
+ var batchQuery = [['INSERT INTO meshcentral.events VALUE (?, ?, ?, ?, ?, ?, ?)', [null, event.time, ((typeof event.domain == 'string') ? event.domain : null), event.action, event.nodeid ? event.nodeid : null, event.userid ? event.userid : null, JSON.stringify(event)]]];
932
+ for (var i in event.ids) { if (event.ids[i] != '*') { batchQuery.push(['INSERT INTO meshcentral.eventids VALUE (LAST_INSERT_ID(), ?)', [event.ids[i]]]); } }
933
+ sqlDbBatchExec(batchQuery, function (err, docs) { if (func != null) { func(err, docs); } });
934
+ };
935
+ obj.GetEvents = function (ids, domain, func) {
936
+ if (ids.indexOf('*') >= 0) {
937
+ sqlDbQuery('SELECT doc FROM meshcentral.events WHERE (domain = ?) ORDER BY time DESC', [domain], func);
938
+ } else {
939
+ sqlDbQuery('SELECT doc FROM meshcentral.events JOIN meshcentral.eventids ON id = fkid WHERE (domain = ? AND target IN (?)) GROUP BY id ORDER BY time DESC', [domain, ids], func);
940
+ }
941
+ };
942
+ obj.GetEventsWithLimit = function (ids, domain, limit, func) {
943
+ if (ids.indexOf('*') >= 0) {
944
+ sqlDbQuery('SELECT doc FROM meshcentral.events WHERE (domain = ?) ORDER BY time DESC LIMIT ?', [domain, limit], func);
945
+ } else {
946
+ sqlDbQuery('SELECT doc FROM meshcentral.events JOIN meshcentral.eventids ON id = fkid WHERE (domain = ? AND target IN (?)) GROUP BY id ORDER BY time DESC LIMIT ?', [domain, ids, limit], func);
947
+ }
948
+ };
949
+ obj.GetUserEvents = function (ids, domain, username, func) {
950
+ const userid = 'user/' + domain + '/' + username.toLowerCase();
951
+ if (ids.indexOf('*') >= 0) {
952
+ sqlDbQuery('SELECT doc FROM meshcentral.events WHERE (domain = ? AND userid = ?) ORDER BY time DESC', [domain, userid], func);
953
+ } else {
954
+ sqlDbQuery('SELECT doc FROM meshcentral.events JOIN meshcentral.eventids ON id = fkid WHERE (domain = ? AND userid = ? AND target IN (?)) GROUP BY id ORDER BY time DESC', [domain, userid, ids, limit], func);
955
+ }
956
+ };
957
+ obj.GetUserEventsWithLimit = function (ids, domain, username, limit, func) {
958
+ const userid = 'user/' + domain + '/' + username.toLowerCase();
959
+ if (ids.indexOf('*') >= 0) {
960
+ sqlDbQuery('SELECT doc FROM meshcentral.events WHERE (domain = ? AND userid = ?) ORDER BY time DESC LIMIT ?', [domain, userid, limit], func);
961
+ } else {
962
+ sqlDbQuery('SELECT doc FROM meshcentral.events JOIN meshcentral.eventids ON id = fkid WHERE (domain = ? AND userid = ? AND target IN (?)) GROUP BY id ORDER BY time DESC LIMIT ?', [domain, userid, ids, limit], func);
963
+ }
964
+ };
965
+ obj.GetNodeEventsWithLimit = function (nodeid, domain, limit, func) { sqlDbQuery('SELECT doc FROM meshcentral.events WHERE (nodeid = ?) AND (domain = ?) ORDER BY time DESC LIMIT ?', [nodeid, domain, limit], func); };
966
+ obj.GetNodeEventsSelfWithLimit = function (nodeid, domain, userid, limit, func) { sqlDbQuery('SELECT doc FROM meshcentral.events WHERE (nodeid = ?) AND (domain = ?) AND ((userid = ?) OR (userid IS NULL)) ORDER BY time DESC LIMIT ?', [nodeid, domain, userid, limit], func); };
967
+ obj.RemoveAllEvents = function (domain) { sqlDbQuery('DELETE FROM meshcentral.events', null, function (err, docs) { }); };
968
+ obj.RemoveAllNodeEvents = function (domain, nodeid) { sqlDbQuery('DELETE FROM meshcentral.events WHERE domain = ? AND nodeid = ?', [domain, nodeid], function (err, docs) { }); };
969
+ obj.RemoveAllUserEvents = function (domain, userid) { sqlDbQuery('DELETE FROM meshcentral.events WHERE domain = ? AND userid = ?', [domain, userid], function (err, docs) { }); };
970
+ obj.GetFailedLoginCount = function (username, domainid, lastlogin, func) { sqlDbExec('SELECT COUNT(id) FROM meshcentral.events WHERE action = "authfail" AND domain = ? AND userid = ? AND time > ?', [domainid, 'user/' + domainid + '/' + username.toLowerCase(), lastlogin], function (err, response) { func(err == null ? response['COUNT(id)'] : 0); }); }
971
+
972
+ // Database actions on the power collection
973
+ obj.getAllPower = function (func) { sqlDbQuery('SELECT doc FROM meshcentral.power', null, func); };
974
+ obj.storePowerEvent = function (event, multiServer, func) { if (multiServer != null) { event.server = multiServer.serverid; } sqlDbQuery('INSERT INTO meshcentral.power VALUE (?, ?, ?, ?)', [null, event.time, event.nodeid ? event.nodeid : null, JSON.stringify(event)], func); };
975
+ obj.getPowerTimeline = function (nodeid, func) { sqlDbQuery('SELECT doc FROM meshcentral.power WHERE ((nodeid = ?) OR (nodeid = "*")) ORDER BY time DESC', [nodeid], func); };
976
+ obj.removeAllPowerEvents = function () { sqlDbQuery('DELETE FROM meshcentral.power', null, function (err, docs) { }); };
977
+ obj.removeAllPowerEventsForNode = function (nodeid) { sqlDbQuery('DELETE FROM meshcentral.power WHERE nodeid = ?', [nodeid], function (err, docs) { }); };
978
+
979
+ // Database actions on the SMBIOS collection
980
+ obj.GetAllSMBIOS = function (func) { sqlDbQuery('SELECT doc FROM meshcentral.smbios', null, func); };
981
+ obj.SetSMBIOS = function (smbios, func) { var expire = new Date(smbios.time); expire.setMonth(expire.getMonth() + 6); sqlDbQuery('REPLACE INTO meshcentral.smbios VALUE (?, ?, ?, ?)', [smbios._id, smbios.time, expire, JSON.stringify(smbios)], func); };
982
+ obj.RemoveSMBIOS = function (id) { sqlDbQuery('DELETE FROM meshcentral.smbios WHERE id = ?', [id], function (err, docs) { }); };
983
+ obj.GetSMBIOS = function (id, func) { sqlDbQuery('SELECT doc FROM meshcentral.smbios WHERE id = ?', [id], func); };
984
+
985
+ // Database actions on the Server Stats collection
986
+ obj.SetServerStats = function (data, func) { sqlDbQuery('REPLACE INTO meshcentral.serverstats VALUE (?, ?, ?)', [data.time, data.expire, JSON.stringify(data)], func); };
987
+ obj.GetServerStats = function (hours, func) { var t = new Date(); t.setTime(t.getTime() - (60 * 60 * 1000 * hours)); sqlDbQuery('SELECT doc FROM meshcentral.main WHERE time < ?', [t], func); }; // TODO: Expire old entries
988
+
989
+ // Read a configuration file from the database
990
+ obj.getConfigFile = function (path, func) { obj.Get('cfile/' + path, func); }
991
+
992
+ // Write a configuration file to the database
993
+ obj.setConfigFile = function (path, data, func) { obj.Set({ _id: 'cfile/' + path, type: 'cfile', data: data.toString('base64') }, func); }
994
+
995
+ // List all configuration files
996
+ obj.listConfigFiles = function (func) { sqlDbQuery('SELECT doc FROM meshcentral.main WHERE type = "cfile" ORDER BY id', func); }
997
+
998
+ // Get all configuration files
999
+ obj.getAllConfigFiles = function (password, func) {
1000
+ obj.file.find({ type: 'cfile' }).toArray(function (err, docs) {
1001
+ if (err != null) { func(null); return; }
1002
+ var r = null;
1003
+ for (var i = 0; i < docs.length; i++) {
1004
+ var name = docs[i]._id.split('/')[1];
1005
+ var data = obj.decryptData(password, docs[i].data);
1006
+ if (data != null) { if (r == null) { r = {}; } r[name] = data; }
1007
+ }
1008
+ func(r);
1009
+ });
1010
+ }
1011
+
1012
+ // Get database information (TODO: Complete this)
1013
+ obj.getDbStats = function (func) {
1014
+ obj.stats = { c: 4 };
1015
+ sqlDbExec('SELECT COUNT(id) FROM meshcentral.main', null, function (err, response) { obj.stats.meshcentral = response['COUNT(id)']; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1016
+ sqlDbExec('SELECT COUNT(time) FROM meshcentral.serverstats', null, function (err, response) { obj.stats.serverstats = response['COUNT(time)']; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1017
+ sqlDbExec('SELECT COUNT(id) FROM meshcentral.power', null, function (err, response) { obj.stats.power = response['COUNT(id)']; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1018
+ sqlDbExec('SELECT COUNT(id) FROM meshcentral.smbios', null, function (err, response) { obj.stats.smbios = response['COUNT(id)']; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1019
+ }
1020
+
1021
+ // Plugin operations
1022
+ if (obj.pluginsActive) {
1023
+ obj.addPlugin = function (plugin, func) { sqlDbQuery('INSERT INTO meshcentral.plugin VALUE (?, ?)', [null, JSON.stringify(value)], func); }; // Add a plugin
1024
+ obj.getPlugins = function (func) { sqlDbQuery('SELECT doc FROM meshcentral.plugin', null, func); }; // Get all plugins
1025
+ obj.getPlugin = function (id, func) { sqlDbQuery('SELECT doc FROM meshcentral.plugin WHERE id = ?', [id], func); }; // Get plugin
1026
+ obj.deletePlugin = function (id, func) { sqlDbQuery('DELETE FROM meshcentral.plugin WHERE id = ?', [id], func); }; // Delete plugin
1027
+ obj.setPluginStatus = function (id, status, func) { obj.getPlugin(id, function (err, docs) { if ((err == null) && (docs.length == 1)) { docs[0].status = status; obj.updatePlugin(id, docs[0], func); } }); };
1028
+ obj.updatePlugin = function (id, args, func) { delete args._id; sqlDbQuery('REPLACE INTO meshcentral.plugin VALUE (?, ?)', [id, JSON.stringify(args)], func); };
1029
+ }
1030
+ } else if (obj.databaseType == 3) {
1031
+ // Database actions on the main collection (MongoDB)
1032
+ obj.Set = function (data, func) { data = common.escapeLinksFieldNameEx(data); obj.file.replaceOne({ _id: data._id }, performTypedRecordEncrypt(data), { upsert: true }, func); };
1033
+ obj.Get = function (id, func) {
1034
+ if (arguments.length > 2) {
1035
+ var parms = [func];
1036
+ for (var parmx = 2; parmx < arguments.length; ++parmx) { parms.push(arguments[parmx]); }
1037
+ var func2 = function _func2(arg1, arg2) {
1038
+ var userCallback = _func2.userArgs.shift();
1039
+ _func2.userArgs.unshift(arg2);
1040
+ _func2.userArgs.unshift(arg1);
1041
+ userCallback.apply(obj, _func2.userArgs);
1042
+ };
1043
+ func2.userArgs = parms;
1044
+ obj.file.find({ _id: id }).toArray(function (err, docs) {
1045
+ if ((docs != null) && (docs.length > 0) && (docs[0].links != null)) { docs[0] = common.unEscapeLinksFieldName(docs[0]); }
1046
+ func2(err, performTypedRecordDecrypt(docs));
1047
+ });
1048
+ } else {
1049
+ obj.file.find({ _id: id }).toArray(function (err, docs) {
1050
+ if ((docs != null) && (docs.length > 0) && (docs[0].links != null)) { docs[0] = common.unEscapeLinksFieldName(docs[0]); }
1051
+ func(err, performTypedRecordDecrypt(docs));
1052
+ });
1053
+ }
1054
+ };
1055
+ obj.GetAll = function (func) { obj.file.find({}).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1056
+ obj.GetHash = function (id, func) { obj.file.find({ _id: id }).project({ _id: 0, hash: 1 }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1057
+ obj.GetAllTypeNoTypeField = function (type, domain, func) { obj.file.find({ type: type, domain: domain }).project({ type: 0 }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1058
+ obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, func) {
1059
+ if (extrasids == null) {
1060
+ var x = { type: type, domain: domain, meshid: { $in: meshes } };
1061
+ if (id) { x._id = id; }
1062
+ obj.file.find(x, { type: 0 }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
1063
+ } else {
1064
+ var x = { type: type, domain: domain, $or: [ { meshid: { $in: meshes } }, { _id: { $in: extrasids } } ] };
1065
+ if (id) { x._id = id; }
1066
+ obj.file.find(x, { type: 0 }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
1067
+ }
1068
+ };
1069
+ obj.GetAllTypeNodeFiltered = function (nodes, domain, type, id, func) {
1070
+ var x = { type: type, domain: domain, nodeid: { $in: nodes } };
1071
+ if (id) { x._id = id; }
1072
+ obj.file.find(x, { type: 0 }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
1073
+ };
1074
+ obj.GetAllType = function (type, func) { obj.file.find({ type: type }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1075
+ obj.GetAllIdsOfType = function (ids, domain, type, func) { obj.file.find({ type: type, domain: domain, _id: { $in: ids } }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1076
+ obj.GetUserWithEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1077
+ obj.GetUserWithVerifiedEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email, emailVerified: true }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1078
+ obj.Remove = function (id, func) { obj.file.deleteOne({ _id: id }, func); };
1079
+ obj.RemoveAll = function (func) { obj.file.deleteMany({}, { multi: true }, func); };
1080
+ obj.RemoveAllOfType = function (type, func) { obj.file.deleteMany({ type: type }, { multi: true }, func); };
1081
+ obj.InsertMany = function (data, func) { obj.file.insertMany(data, func); };
1082
+ obj.RemoveMeshDocuments = function (id) { obj.file.deleteMany({ meshid: id }, { multi: true }); obj.file.deleteOne({ _id: 'nt' + id }); };
1083
+ obj.MakeSiteAdmin = function (username, domain) { obj.Get('user/' + domain + '/' + username, function (err, docs) { if ((err == null) && (docs.length == 1)) { docs[0].siteadmin = 0xFFFFFFFF; obj.Set(docs[0]); } }); };
1084
+ obj.DeleteDomain = function (domain, func) { obj.file.deleteMany({ domain: domain }, { multi: true }, func); };
1085
+ obj.SetUser = function (user) { if (user.subscriptions != null) { var u = Clone(user); if (u.subscriptions) { delete u.subscriptions; } obj.Set(u); } else { obj.Set(user); } };
1086
+ obj.dispose = function () { for (var x in obj) { if (obj[x].close) { obj[x].close(); } delete obj[x]; } };
1087
+ obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }).toArray(func); };
1088
+ obj.getAmtUuidMeshNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }).toArray(func); };
1089
+ obj.getAmtUuidNode = function (uuid, func) { obj.file.find({ type: 'node', 'intelamt.uuid': uuid }).toArray(func); };
1090
+
1091
+ // TODO: Starting in MongoDB 4.0.3, you should use countDocuments() instead of count() that is deprecated. We should detect MongoDB version and switch.
1092
+ // https://docs.mongodb.com/manual/reference/method/db.collection.countDocuments/
1093
+ //obj.isMaxType = function (max, type, domainid, func) { if (max == null) { func(false); } else { obj.file.countDocuments({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max)); }); } }
1094
+ obj.isMaxType = function (max, type, domainid, func) {
1095
+ if (obj.file.countDocuments) {
1096
+ if (max == null) { func(false); } else { obj.file.countDocuments({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max), count); }); }
1097
+ } else {
1098
+ if (max == null) { func(false); } else { obj.file.count({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max), count); }); }
1099
+ }
1100
+ }
1101
+
1102
+ // Database actions on the events collection
1103
+ obj.GetAllEvents = function (func) { obj.eventsfile.find({}).toArray(func); };
1104
+ obj.StoreEvent = function (event, func) { obj.eventsfile.insertOne(event, func); };
1105
+ obj.GetEvents = function (ids, domain, func) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }).project({ type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).toArray(func); };
1106
+ obj.GetEventsWithLimit = function (ids, domain, limit, func) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }).project({ type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).toArray(func); };
1107
+ obj.GetUserEvents = function (ids, domain, username, func) { obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }).project({ type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).toArray(func); };
1108
+ obj.GetUserEventsWithLimit = function (ids, domain, username, limit, func) { obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }).project({ type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).toArray(func); };
1109
+ obj.GetNodeEventsWithLimit = function (nodeid, domain, limit, func) { obj.eventsfile.find({ domain: domain, nodeid: nodeid }).project({ type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).toArray(func); };
1110
+ obj.GetNodeEventsSelfWithLimit = function (nodeid, domain, userid, limit, func) { obj.eventsfile.find({ domain: domain, nodeid: nodeid, userid: { $in: [userid, null] } }).project({ type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).toArray(func); };
1111
+ obj.RemoveAllEvents = function (domain) { obj.eventsfile.deleteMany({ domain: domain }, { multi: true }); };
1112
+ obj.RemoveAllNodeEvents = function (domain, nodeid) { obj.eventsfile.deleteMany({ domain: domain, nodeid: nodeid }, { multi: true }); };
1113
+ obj.RemoveAllUserEvents = function (domain, userid) { obj.eventsfile.deleteMany({ domain: domain, userid: userid }, { multi: true }); };
1114
+ obj.GetFailedLoginCount = function (username, domainid, lastlogin, func) {
1115
+ if (obj.eventsfile.countDocuments) {
1116
+ obj.eventsfile.countDocuments({ action: 'authfail', username: username, domain: domainid, time: { "$gte": lastlogin } }, function (err, count) { func((err == null) ? count : 0); });
1117
+ } else {
1118
+ obj.eventsfile.count({ action: 'authfail', username: username, domain: domainid, time: { "$gte": lastlogin } }, function (err, count) { func((err == null) ? count : 0); });
1119
+ }
1120
+ }
1121
+
1122
+ // Database actions on the power collection
1123
+ obj.getAllPower = function (func) { obj.powerfile.find({}).toArray(func); };
1124
+ obj.storePowerEvent = function (event, multiServer, func) { if (multiServer != null) { event.server = multiServer.serverid; } obj.powerfile.insertOne(event, func); };
1125
+ obj.getPowerTimeline = function (nodeid, func) { obj.powerfile.find({ nodeid: { $in: ['*', nodeid] } }).project({ _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }).toArray(func); };
1126
+ obj.removeAllPowerEvents = function () { obj.powerfile.deleteMany({}, { multi: true }); };
1127
+ obj.removeAllPowerEventsForNode = function (nodeid) { obj.powerfile.deleteMany({ nodeid: nodeid }, { multi: true }); };
1128
+
1129
+ // Database actions on the SMBIOS collection
1130
+ obj.GetAllSMBIOS = function (func) { obj.smbiosfile.find({}).toArray(func); };
1131
+ obj.SetSMBIOS = function (smbios, func) { obj.smbiosfile.updateOne({ _id: smbios._id }, { $set: smbios }, { upsert: true }, func); };
1132
+ obj.RemoveSMBIOS = function (id) { obj.smbiosfile.deleteOne({ _id: id }); };
1133
+ obj.GetSMBIOS = function (id, func) { obj.smbiosfile.find({ _id: id }).toArray(func); };
1134
+
1135
+ // Database actions on the Server Stats collection
1136
+ obj.SetServerStats = function (data, func) { obj.serverstatsfile.insertOne(data, func); };
1137
+ obj.GetServerStats = function (hours, func) { var t = new Date(); t.setTime(t.getTime() - (60 * 60 * 1000 * hours)); obj.serverstatsfile.find({ time: { $gt: t } }, { _id: 0, cpu: 0 }).toArray(func); };
1138
+
1139
+ // Read a configuration file from the database
1140
+ obj.getConfigFile = function (path, func) { obj.Get('cfile/' + path, func); }
1141
+
1142
+ // Write a configuration file to the database
1143
+ obj.setConfigFile = function (path, data, func) { obj.Set({ _id: 'cfile/' + path, type: 'cfile', data: data.toString('base64') }, func); }
1144
+
1145
+ // List all configuration files
1146
+ obj.listConfigFiles = function (func) { obj.file.find({ type: 'cfile' }).sort({ _id: 1 }).toArray(func); }
1147
+
1148
+ // Get all configuration files
1149
+ obj.getAllConfigFiles = function (password, func) {
1150
+ obj.file.find({ type: 'cfile' }).toArray(function (err, docs) {
1151
+ if (err != null) { func(null); return; }
1152
+ var r = null;
1153
+ for (var i = 0; i < docs.length; i++) {
1154
+ var name = docs[i]._id.split('/')[1];
1155
+ var data = obj.decryptData(password, docs[i].data);
1156
+ if (data != null) { if (r == null) { r = {}; } r[name] = data; }
1157
+ }
1158
+ func(r);
1159
+ });
1160
+ }
1161
+
1162
+ // Get database information
1163
+ obj.getDbStats = function (func) {
1164
+ obj.stats = { c: 6 };
1165
+ obj.getStats(function (r) { obj.stats.recordTypes = r; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } })
1166
+ obj.file.stats().then(function (stats) { obj.stats[stats.ns] = { size: stats.size, count: stats.count, avgObjSize: stats.avgObjSize, capped: stats.capped }; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } }, function () { if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1167
+ obj.eventsfile.stats().then(function (stats) { obj.stats[stats.ns] = { size: stats.size, count: stats.count, avgObjSize: stats.avgObjSize, capped: stats.capped }; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } }, function () { if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1168
+ obj.powerfile.stats().then(function (stats) { obj.stats[stats.ns] = { size: stats.size, count: stats.count, avgObjSize: stats.avgObjSize, capped: stats.capped }; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } }, function () { if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1169
+ obj.smbiosfile.stats().then(function (stats) { obj.stats[stats.ns] = { size: stats.size, count: stats.count, avgObjSize: stats.avgObjSize, capped: stats.capped }; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } }, function () { if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1170
+ obj.serverstatsfile.stats().then(function (stats) { obj.stats[stats.ns] = { size: stats.size, count: stats.count, avgObjSize: stats.avgObjSize, capped: stats.capped }; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } }, function () { if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1171
+ }
1172
+
1173
+ // Plugin operations
1174
+ if (obj.pluginsActive) {
1175
+ obj.addPlugin = function (plugin, func) { plugin.type = 'plugin'; obj.pluginsfile.insertOne(plugin, func); }; // Add a plugin
1176
+ obj.getPlugins = function (func) { obj.pluginsfile.find({ type: 'plugin' }).project({ type: 0 }).sort({ name: 1 }).toArray(func); }; // Get all plugins
1177
+ obj.getPlugin = function (id, func) { id = require('mongodb').ObjectID(id); obj.pluginsfile.find({ _id: id }).sort({ name: 1 }).toArray(func); }; // Get plugin
1178
+ obj.deletePlugin = function (id, func) { id = require('mongodb').ObjectID(id); obj.pluginsfile.deleteOne({ _id: id }, func); }; // Delete plugin
1179
+ obj.setPluginStatus = function (id, status, func) { id = require('mongodb').ObjectID(id); obj.pluginsfile.updateOne({ _id: id }, { $set: { status: status } }, func); };
1180
+ obj.updatePlugin = function (id, args, func) { delete args._id; id = require('mongodb').ObjectID(id); obj.pluginsfile.updateOne({ _id: id }, { $set: args }, func); };
1181
+ }
1182
+
1183
+ } else {
1184
+ // Database actions on the main collection (NeDB and MongoJS)
1185
+ obj.Set = function (data, func) { data = common.escapeLinksFieldNameEx(data); var xdata = performTypedRecordEncrypt(data); obj.file.update({ _id: xdata._id }, xdata, { upsert: true }, func); };
1186
+ obj.Get = function (id, func) {
1187
+ if (arguments.length > 2) {
1188
+ var parms = [func];
1189
+ for (var parmx = 2; parmx < arguments.length; ++parmx) { parms.push(arguments[parmx]); }
1190
+ var func2 = function _func2(arg1, arg2) {
1191
+ var userCallback = _func2.userArgs.shift();
1192
+ _func2.userArgs.unshift(arg2);
1193
+ _func2.userArgs.unshift(arg1);
1194
+ userCallback.apply(obj, _func2.userArgs);
1195
+ };
1196
+ func2.userArgs = parms;
1197
+ obj.file.find({ _id: id }, function (err, docs) {
1198
+ if ((docs != null) && (docs.length > 0) && (docs[0].links != null)) { docs[0] = common.unEscapeLinksFieldName(docs[0]); }
1199
+ func2(err, performTypedRecordDecrypt(docs));
1200
+ });
1201
+ } else {
1202
+ obj.file.find({ _id: id }, function (err, docs) {
1203
+ if ((docs != null) && (docs.length > 0) && (docs[0].links != null)) { docs[0] = common.unEscapeLinksFieldName(docs[0]); }
1204
+ func(err, performTypedRecordDecrypt(docs));
1205
+ });
1206
+ }
1207
+ };
1208
+ obj.GetAll = function (func) { obj.file.find({}, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1209
+ obj.GetHash = function (id, func) { obj.file.find({ _id: id }, { _id: 0, hash: 1 }, func); };
1210
+ obj.GetAllTypeNoTypeField = function (type, domain, func) { obj.file.find({ type: type, domain: domain }, { type: 0 }, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1211
+ //obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, domain, type, id, func) {
1212
+ //var x = { type: type, domain: domain, meshid: { $in: meshes } };
1213
+ //if (id) { x._id = id; }
1214
+ //obj.file.find(x, { type: 0 }, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
1215
+ //};
1216
+ obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, func) {
1217
+ if (extrasids == null) {
1218
+ var x = { type: type, domain: domain, meshid: { $in: meshes } };
1219
+ if (id) { x._id = id; }
1220
+ obj.file.find(x, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
1221
+ } else {
1222
+ var x = { type: type, domain: domain, $or: [{ meshid: { $in: meshes } }, { _id: { $in: extrasids } }] };
1223
+ if (id) { x._id = id; }
1224
+ obj.file.find(x, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
1225
+ }
1226
+ };
1227
+ obj.GetAllTypeNodeFiltered = function (nodes, domain, type, id, func) {
1228
+ var x = { type: type, domain: domain, nodeid: { $in: nodes } };
1229
+ if (id) { x._id = id; }
1230
+ obj.file.find(x, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
1231
+ };
1232
+ obj.GetAllType = function (type, func) { obj.file.find({ type: type }, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1233
+ obj.GetAllIdsOfType = function (ids, domain, type, func) { obj.file.find({ type: type, domain: domain, _id: { $in: ids } }, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1234
+ obj.GetUserWithEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email }, { type: 0 }, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1235
+ obj.GetUserWithVerifiedEmail = function (domain, email, func) { obj.file.find({ type: 'user', domain: domain, email: email, emailVerified: true }, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1236
+ obj.Remove = function (id, func) { obj.file.remove({ _id: id }, func); };
1237
+ obj.RemoveAll = function (func) { obj.file.remove({}, { multi: true }, func); };
1238
+ obj.RemoveAllOfType = function (type, func) { obj.file.remove({ type: type }, { multi: true }, func); };
1239
+ obj.InsertMany = function (data, func) { obj.file.insert(data, func); };
1240
+ obj.RemoveMeshDocuments = function (id) { obj.file.remove({ meshid: id }, { multi: true }); obj.file.remove({ _id: 'nt' + id }); };
1241
+ obj.MakeSiteAdmin = function (username, domain) { obj.Get('user/' + domain + '/' + username, function (err, docs) { if ((err == null) && (docs.length == 1)) { docs[0].siteadmin = 0xFFFFFFFF; obj.Set(docs[0]); } }); };
1242
+ obj.DeleteDomain = function (domain, func) { obj.file.remove({ domain: domain }, { multi: true }, func); };
1243
+ obj.SetUser = function (user) { if (user.subscriptions != null) { var u = Clone(user); if (u.subscriptions) { delete u.subscriptions; } obj.Set(u); } else { obj.Set(user); } };
1244
+ obj.dispose = function () { for (var x in obj) { if (obj[x].close) { obj[x].close(); } delete obj[x]; } };
1245
+ obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }, func); };
1246
+ obj.getAmtUuidMeshNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }, func); };
1247
+ obj.getAmtUuidNode = function (uuid, func) { obj.file.find({ type: 'node', 'intelamt.uuid': uuid }, func); };
1248
+ obj.isMaxType = function (max, type, domainid, func) { if (max == null) { func(false); } else { obj.file.count({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max), count); }); } }
1249
+
1250
+ // Database actions on the events collection
1251
+ obj.GetAllEvents = function (func) { obj.eventsfile.find({}, func); };
1252
+ obj.StoreEvent = function (event, func) { obj.eventsfile.insert(event, func); };
1253
+ obj.GetEvents = function (ids, domain, func) { if (obj.databaseType == 1) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).exec(func); } else { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }, func); } };
1254
+ obj.GetEventsWithLimit = function (ids, domain, limit, func) { if (obj.databaseType == 1) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).exec(func); } else { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit, func); } };
1255
+ obj.GetUserEvents = function (ids, domain, username, func) {
1256
+ if (obj.databaseType == 1) {
1257
+ obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).exec(func);
1258
+ } else {
1259
+ obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }, func);
1260
+ }
1261
+ };
1262
+ obj.GetUserEventsWithLimit = function (ids, domain, username, limit, func) {
1263
+ if (obj.databaseType == 1) {
1264
+ obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).exec(func);
1265
+ } else {
1266
+ obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit, func);
1267
+ }
1268
+ };
1269
+ obj.GetNodeEventsWithLimit = function (nodeid, domain, limit, func) { if (obj.databaseType == 1) { obj.eventsfile.find({ domain: domain, nodeid: nodeid }, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).exec(func); } else { obj.eventsfile.find({ domain: domain, nodeid: nodeid }, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit, func); } };
1270
+ obj.GetNodeEventsSelfWithLimit = function (nodeid, domain, userid, limit, func) { if (obj.databaseType == 1) { obj.eventsfile.find({ domain: domain, nodeid: nodeid, userid: { $in: [userid, null] } }, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).exec(func); } else { obj.eventsfile.find({ domain: domain, nodeid: nodeid }, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit, func); } };
1271
+ obj.RemoveAllEvents = function (domain) { obj.eventsfile.remove({ domain: domain }, { multi: true }); };
1272
+ obj.RemoveAllNodeEvents = function (domain, nodeid) { obj.eventsfile.remove({ domain: domain, nodeid: nodeid }, { multi: true }); };
1273
+ obj.RemoveAllUserEvents = function (domain, userid) { obj.eventsfile.remove({ domain: domain, userid: userid }, { multi: true }); };
1274
+ obj.GetFailedLoginCount = function (username, domainid, lastlogin, func) { obj.eventsfile.count({ action: 'authfail', username: username, domain: domainid, time: { "$gte": lastlogin } }, function (err, count) { func((err == null) ? count : 0); }); }
1275
+
1276
+ // Database actions on the power collection
1277
+ obj.getAllPower = function (func) { obj.powerfile.find({}, func); };
1278
+ obj.storePowerEvent = function (event, multiServer, func) { if (multiServer != null) { event.server = multiServer.serverid; } obj.powerfile.insert(event, func); };
1279
+ obj.getPowerTimeline = function (nodeid, func) { if (obj.databaseType == 1) { obj.powerfile.find({ nodeid: { $in: ['*', nodeid] } }, { _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }).exec(func); } else { obj.powerfile.find({ nodeid: { $in: ['*', nodeid] } }, { _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }, func); } };
1280
+ obj.removeAllPowerEvents = function () { obj.powerfile.remove({}, { multi: true }); };
1281
+ obj.removeAllPowerEventsForNode = function (nodeid) { obj.powerfile.remove({ nodeid: nodeid }, { multi: true }); };
1282
+
1283
+ // Database actions on the SMBIOS collection
1284
+ if (obj.smbiosfile != null) {
1285
+ obj.GetAllSMBIOS = function (func) { obj.smbiosfile.find({}, func); };
1286
+ obj.SetSMBIOS = function (smbios, func) { obj.smbiosfile.update({ _id: smbios._id }, smbios, { upsert: true }, func); };
1287
+ obj.RemoveSMBIOS = function (id) { obj.smbiosfile.remove({ _id: id }); };
1288
+ obj.GetSMBIOS = function (id, func) { obj.smbiosfile.find({ _id: id }, func); };
1289
+ }
1290
+
1291
+ // Database actions on the Server Stats collection
1292
+ obj.SetServerStats = function (data, func) { obj.serverstatsfile.insert(data, func); };
1293
+ obj.GetServerStats = function (hours, func) { var t = new Date(); t.setTime(t.getTime() - (60 * 60 * 1000 * hours)); obj.serverstatsfile.find({ time: { $gt: t } }, { _id: 0, cpu: 0 }, func); };
1294
+
1295
+ // Read a configuration file from the database
1296
+ obj.getConfigFile = function (path, func) { obj.Get('cfile/' + path, func); }
1297
+
1298
+ // Write a configuration file to the database
1299
+ obj.setConfigFile = function (path, data, func) { obj.Set({ _id: 'cfile/' + path, type: 'cfile', data: data.toString('base64') }, func); }
1300
+
1301
+ // List all configuration files
1302
+ obj.listConfigFiles = function (func) { obj.file.find({ type: 'cfile' }).sort({ _id: 1 }).exec(func); }
1303
+
1304
+ // Get all configuration files
1305
+ obj.getAllConfigFiles = function (password, func) {
1306
+ obj.file.find({ type: 'cfile' }, function (err, docs) {
1307
+ if (err != null) { func(null); return; }
1308
+ var r = null;
1309
+ for (var i = 0; i < docs.length; i++) {
1310
+ var name = docs[i]._id.split('/')[1];
1311
+ var data = obj.decryptData(password, docs[i].data);
1312
+ if (data != null) { if (r == null) { r = {}; } r[name] = data; }
1313
+ }
1314
+ func(r);
1315
+ });
1316
+ }
1317
+
1318
+ // Get database information
1319
+ obj.getDbStats = function (func) {
1320
+ obj.stats = { c: 5 };
1321
+ obj.getStats(function (r) { obj.stats.recordTypes = r; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } })
1322
+ obj.file.count({}, function (err, count) { obj.stats.meshcentral = { count: count }; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1323
+ obj.eventsfile.count({}, function (err, count) { obj.stats.events = { count: count }; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1324
+ obj.powerfile.count({}, function (err, count) { obj.stats.power = { count: count }; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1325
+ obj.serverstatsfile.count({}, function (err, count) { obj.stats.serverstats = { count: count }; if (--obj.stats.c == 0) { delete obj.stats.c; func(obj.stats); } });
1326
+ }
1327
+
1328
+ // Plugin operations
1329
+ if (obj.pluginsActive) {
1330
+ obj.addPlugin = function (plugin, func) { plugin.type = 'plugin'; obj.pluginsfile.insert(plugin, func); }; // Add a plugin
1331
+ obj.getPlugins = function (func) { obj.pluginsfile.find({ 'type': 'plugin' }, { 'type': 0 }).sort({ name: 1 }).exec(func); }; // Get all plugins
1332
+ obj.getPlugin = function (id, func) { obj.pluginsfile.find({ _id: id }).sort({ name: 1 }).exec(func); }; // Get plugin
1333
+ obj.deletePlugin = function (id, func) { obj.pluginsfile.remove({ _id: id }, func); }; // Delete plugin
1334
+ obj.setPluginStatus = function (id, status, func) { obj.pluginsfile.update({ _id: id }, { $set: { status: status } }, func); };
1335
+ obj.updatePlugin = function (id, args, func) { delete args._id; obj.pluginsfile.update({ _id: id }, { $set: args }, func); };
1336
+ }
1337
+
1338
+ }
1339
+
1340
+ func(obj); // Completed function setup
1341
+ }
1342
+
1343
+ // Return a human readable string with current backup configuration
1344
+ obj.getBackupConfig = function () {
1345
+ var r = '', backupPath = parent.backuppath;
1346
+ if (parent.config.settings.autobackup && parent.config.settings.autobackup.backuppath) { backupPath = parent.config.settings.autobackup.backuppath; }
1347
+ const dbname = (parent.args.mongodbname) ? (parent.args.mongodbname) : 'meshcentral';
1348
+ const currentDate = new Date();
1349
+ const fileSuffix = currentDate.getFullYear() + '-' + padNumber(currentDate.getMonth() + 1, 2) + '-' + padNumber(currentDate.getDate(), 2) + '-' + padNumber(currentDate.getHours(), 2) + '-' + padNumber(currentDate.getMinutes(), 2);
1350
+ const newAutoBackupFile = 'meshcentral-autobackup-' + fileSuffix;
1351
+ const newAutoBackupPath = parent.path.join(backupPath, newAutoBackupFile);
1352
+
1353
+ r += 'DB Name: ' + dbname + '\r\n';
1354
+ r += 'DB Type: ' + ['None', 'NeDB', 'MongoJS', 'MongoDB'][obj.databaseType] + '\r\n';
1355
+ r += 'BackupPath: ' + backupPath + '\r\n';
1356
+ r += 'newAutoBackupFile: ' + newAutoBackupFile + '\r\n';
1357
+ r += 'newAutoBackupPath: ' + newAutoBackupPath + '\r\n';
1358
+
1359
+ if (parent.config.settings.autobackup == null) {
1360
+ r += 'No Settings/AutoBackup\r\n';
1361
+ } else {
1362
+ if (parent.config.settings.autobackup.backupintervalhours != null) {
1363
+ if (typeof parent.config.settings.autobackup.backupintervalhours != 'number') { r += 'Bad backupintervalhours type\r\n'; }
1364
+ else { r += 'Backup Interval (Hours): ' + parent.config.settings.autobackup.backupintervalhours + '\r\n'; }
1365
+ }
1366
+ if (parent.config.settings.autobackup.keeplastdaysbackup != null) {
1367
+ if (typeof parent.config.settings.autobackup.keeplastdaysbackup != 'number') { r += 'Bad keeplastdaysbackup type\r\n'; }
1368
+ else { r += 'Keep Last Backups (Days): ' + parent.config.settings.autobackup.keeplastdaysbackup + '\r\n'; }
1369
+ }
1370
+ if (parent.config.settings.autobackup.zippassword != null) {
1371
+ if (typeof parent.config.settings.autobackup.zippassword != 'string') { r += 'Bad zippassword type\r\n'; }
1372
+ else { r += 'ZIP Password Set\r\n'; }
1373
+ }
1374
+ if (parent.config.settings.autobackup.mongodumppath != null) {
1375
+ if (typeof parent.config.settings.autobackup.mongodumppath != 'string') { r += 'Bad mongodumppath type\r\n'; }
1376
+ else { r += 'MongoDump Path: ' + parent.config.settings.autobackup.mongodumppath + '\r\n'; }
1377
+ }
1378
+ }
1379
+
1380
+ return r;
1381
+ }
1382
+
1383
+ // Check that the server is capable of performing a backup
1384
+ obj.checkBackupCapability = function (func) {
1385
+ if ((parent.config.settings.autobackup == null) || (parent.config.settings.autobackup == false)) { func(); }
1386
+ if ((obj.databaseType == 2) || (obj.databaseType == 3)) {
1387
+ // Check that we have access to MongoDump
1388
+ var backupPath = parent.backuppath;
1389
+ if (parent.config.settings.autobackup && parent.config.settings.autobackup.backuppath) { backupPath = parent.config.settings.autobackup.backuppath; }
1390
+ var mongoDumpPath = 'mongodump';
1391
+ if (parent.config.settings.autobackup && parent.config.settings.autobackup.mongodumppath) { mongoDumpPath = parent.config.settings.autobackup.mongodumppath; }
1392
+ const child_process = require('child_process');
1393
+ child_process.exec('"' + mongoDumpPath + '"', { cwd: backupPath }, function (error, stdout, stderr) {
1394
+ try {
1395
+ if ((error != null) && (error != '')) {
1396
+ func(1, "Unable to find mongodump.exe, MongoDB database auto-backup will not be performed.");
1397
+ } else {
1398
+ func();
1399
+ }
1400
+ } catch (ex) { console.log(ex); }
1401
+ });
1402
+ } else {
1403
+ func();
1404
+ }
1405
+ }
1406
+
1407
+ // Perform a server backup
1408
+ obj.performingBackup = false;
1409
+ obj.performBackup = function (func) {
1410
+ try {
1411
+ if (obj.performingBackup) return 1;
1412
+ obj.performingBackup = true;
1413
+ //console.log('Performing backup...');
1414
+
1415
+ var backupPath = parent.backuppath;
1416
+ if (parent.config.settings.autobackup && parent.config.settings.autobackup.backuppath) { backupPath = parent.config.settings.autobackup.backuppath; }
1417
+ try { parent.fs.mkdirSync(backupPath); } catch (e) { }
1418
+ const dbname = (parent.args.mongodbname) ? (parent.args.mongodbname) : 'meshcentral';
1419
+ const dburl = parent.args.mongodb;
1420
+ const currentDate = new Date();
1421
+ const fileSuffix = currentDate.getFullYear() + '-' + padNumber(currentDate.getMonth() + 1, 2) + '-' + padNumber(currentDate.getDate(), 2) + '-' + padNumber(currentDate.getHours(), 2) + '-' + padNumber(currentDate.getMinutes(), 2);
1422
+ const newAutoBackupFile = 'meshcentral-autobackup-' + fileSuffix;
1423
+ const newAutoBackupPath = parent.path.join(backupPath, newAutoBackupFile);
1424
+
1425
+ if ((obj.databaseType == 2) || (obj.databaseType == 3)) {
1426
+ // Perform a MongoDump backup
1427
+ const newBackupFile = 'mongodump-' + fileSuffix;
1428
+ var newBackupPath = parent.path.join(backupPath, newBackupFile);
1429
+ var mongoDumpPath = 'mongodump';
1430
+ if (parent.config.settings.autobackup && parent.config.settings.autobackup.mongodumppath) { mongoDumpPath = parent.config.settings.autobackup.mongodumppath; }
1431
+ const child_process = require('child_process');
1432
+ var cmd = '\"' + mongoDumpPath + '\" --db=\"' + dbname + '\" --archive=\"' + newBackupPath + '.archive\"';
1433
+ if (dburl) { cmd = '\"' + mongoDumpPath + '\" --uri=\"' + dburl.replace('?', '/?') + '\" --archive=\"' + newBackupPath + '.archive\"'; }
1434
+ var backupProcess = child_process.exec(cmd, { cwd: backupPath }, function (error, stdout, stderr) {
1435
+ try {
1436
+ var mongoDumpSuccess = true;
1437
+ backupProcess = null;
1438
+ if ((error != null) && (error != '')) { mongoDumpSuccess = false; console.log('ERROR: Unable to perform MongoDB backup: ' + error + '\r\n'); }
1439
+
1440
+ // Perform archive compression
1441
+ var archiver = require('archiver');
1442
+ var output = parent.fs.createWriteStream(newAutoBackupPath + '.zip');
1443
+ var archive = null;
1444
+ if (parent.config.settings.autobackup && (typeof parent.config.settings.autobackup.zippassword == 'string')) {
1445
+ try { archiver.registerFormat('zip-encrypted', require("archiver-zip-encrypted")); } catch (ex) { }
1446
+ archive = archiver.create('zip-encrypted', { zlib: { level: 9 }, encryptionMethod: 'aes256', password: parent.config.settings.autobackup.zippassword });
1447
+ } else {
1448
+ archive = archiver('zip', { zlib: { level: 9 } });
1449
+ }
1450
+ output.on('close', function () {
1451
+ obj.performingBackup = false;
1452
+ if (func) { if (mongoDumpSuccess) { func('Auto-backup completed.'); } else { func('Auto-backup completed without mongodb database: ' + error); } }
1453
+ obj.performCloudBackup(newAutoBackupPath + '.zip', func);
1454
+ setTimeout(function () { try { parent.fs.unlink(newBackupPath + '.archive', function () { }); } catch (ex) { console.log(ex); } }, 5000);
1455
+ });
1456
+ output.on('end', function () { });
1457
+ archive.on('warning', function (err) { console.log('Backup warning: ' + err); if (func) { func('Backup warning: ' + err); } });
1458
+ archive.on('error', function (err) { console.log('Backup error: ' + err); if (func) { func('Backup error: ' + err); } });
1459
+ archive.pipe(output);
1460
+ if (mongoDumpSuccess == true) { archive.file(newBackupPath + '.archive', { name: newBackupFile + '.archive' }); }
1461
+ archive.directory(parent.datapath, 'meshcentral-data');
1462
+ archive.finalize();
1463
+ } catch (ex) { console.log(ex); }
1464
+ });
1465
+ } else {
1466
+ // Perform a NeDB backup
1467
+ var archiver = require('archiver');
1468
+ var output = parent.fs.createWriteStream(newAutoBackupPath + '.zip');
1469
+ var archive = null;
1470
+ if (parent.config.settings.autobackup && (typeof parent.config.settings.autobackup.zippassword == 'string')) {
1471
+ try { archiver.registerFormat('zip-encrypted', require("archiver-zip-encrypted")); } catch (ex) { }
1472
+ archive = archiver.create('zip-encrypted', { zlib: { level: 9 }, encryptionMethod: 'aes256', password: parent.config.settings.autobackup.zippassword });
1473
+ } else {
1474
+ archive = archiver('zip', { zlib: { level: 9 } });
1475
+ }
1476
+ output.on('close', function () { obj.performingBackup = false; if (func) { func('Auto-backup completed.'); } obj.performCloudBackup(newAutoBackupPath + '.zip', func); });
1477
+ output.on('end', function () { });
1478
+ archive.on('warning', function (err) { console.log('Backup warning: ' + err); if (func) { func('Backup warning: ' + err); } });
1479
+ archive.on('error', function (err) { console.log('Backup error: ' + err); if (func) { func('Backup error: ' + err); } });
1480
+ archive.pipe(output);
1481
+ archive.directory(parent.datapath, 'meshcentral-data');
1482
+ archive.finalize();
1483
+ }
1484
+
1485
+ // Remove old backups
1486
+ if (parent.config.settings.autobackup && (typeof parent.config.settings.autobackup.keeplastdaysbackup == 'number')) {
1487
+ var cutoffDate = new Date();
1488
+ cutoffDate.setDate(cutoffDate.getDate() - parent.config.settings.autobackup.keeplastdaysbackup);
1489
+ parent.fs.readdir(parent.backuppath, function (err, dir) {
1490
+ try {
1491
+ if ((err == null) && (dir.length > 0)) {
1492
+ for (var i in dir) {
1493
+ var name = dir[i];
1494
+ if (name.startsWith('meshcentral-autobackup-') && name.endsWith('.zip')) {
1495
+ var timex = name.substring(23, name.length - 4).split('-');
1496
+ if (timex.length == 5) {
1497
+ var fileDate = new Date(parseInt(timex[0]), parseInt(timex[1]) - 1, parseInt(timex[2]), parseInt(timex[3]), parseInt(timex[4]));
1498
+ if (fileDate && (cutoffDate > fileDate)) { try { parent.fs.unlink(parent.path.join(parent.backuppath, name), function () { }); } catch (ex) { } }
1499
+ }
1500
+ }
1501
+ }
1502
+ }
1503
+ } catch (ex) { console.log(ex); }
1504
+ });
1505
+ }
1506
+ } catch (ex) { console.log(ex); }
1507
+ return 0;
1508
+ }
1509
+
1510
+ // Perform cloud backup
1511
+ obj.performCloudBackup = function (filename, func) {
1512
+
1513
+ // WebDAV Backup
1514
+ if ((typeof parent.config.settings.autobackup == 'object') && (typeof parent.config.settings.autobackup.webdav == 'object')) {
1515
+ const xdateTimeSort = function (a, b) { if (a.xdate > b.xdate) return 1; if (a.xdate < b.xdate) return -1; return 0; }
1516
+
1517
+ // Fetch the folder name
1518
+ var webdavfolderName = 'MeshCentral-Backups';
1519
+ if (typeof parent.config.settings.autobackup.webdav.foldername == 'string') { webdavfolderName = parent.config.settings.autobackup.webdav.foldername; }
1520
+
1521
+ // Clean up our WebDAV folder
1522
+ function performWebDavCleanup(client) {
1523
+ if ((typeof parent.config.settings.autobackup.webdav.maxfiles == 'number') && (parent.config.settings.autobackup.webdav.maxfiles > 1)) {
1524
+ var directoryItems = client.getDirectoryContents(webdavfolderName);
1525
+ directoryItems.then(
1526
+ function (files) {
1527
+ for (var i in files) { files[i].xdate = new Date(files[i].lastmod); }
1528
+ files.sort(xdateTimeSort);
1529
+ while (files.length >= parent.config.settings.autobackup.webdav.maxfiles) {
1530
+ client.deleteFile(files.shift().filename).then(function (state) {
1531
+ if (func) { func('WebDAV file deleted.'); }
1532
+ }).catch(function (err) {
1533
+ if (func) { func('WebDAV (deleteFile) error: ' + err); }
1534
+ });
1535
+ }
1536
+ }
1537
+ ).catch(function (err) {
1538
+ if (func) { func('WebDAV (getDirectoryContents) error: ' + err); }
1539
+ });
1540
+ }
1541
+ }
1542
+
1543
+ // Upload to the WebDAV folder
1544
+ function performWebDavUpload(client, filepath) {
1545
+ var fileStream = require('fs').createReadStream(filepath);
1546
+ fileStream.on('close', function () { if (func) { func('WebDAV upload completed'); } })
1547
+ fileStream.on('error', function (err) { if (func) { func('WebDAV (fileUpload) error: ' + err); } })
1548
+ fileStream.pipe(client.createWriteStream('/' + webdavfolderName + '/' + require('path').basename(filepath)));
1549
+ if (func) { func('Uploading using WebDAV...'); }
1550
+ }
1551
+
1552
+ if (func) { func('Attempting WebDAV upload...'); }
1553
+ const { createClient } = require('webdav');
1554
+ const client = createClient(parent.config.settings.autobackup.webdav.url, { username: parent.config.settings.autobackup.webdav.username, password: parent.config.settings.autobackup.webdav.password });
1555
+ var directoryItems = client.getDirectoryContents('/');
1556
+ directoryItems.then(
1557
+ function (files) {
1558
+ var folderFound = false;
1559
+ for (var i in files) { if ((files[i].basename == webdavfolderName) && (files[i].type == 'directory')) { folderFound = true; } }
1560
+ if (folderFound == false) {
1561
+ client.createDirectory(webdavfolderName).then(function (a) {
1562
+ if (a.statusText == 'Created') {
1563
+ if (func) { func('WebDAV folder created'); }
1564
+ performWebDavUpload(client, filename);
1565
+ } else {
1566
+ if (func) { func('WebDAV (createDirectory) status: ' + a.statusText); }
1567
+ }
1568
+ }).catch(function (err) {
1569
+ if (func) { func('WebDAV (createDirectory) error: ' + err); }
1570
+ });
1571
+ } else {
1572
+ performWebDavCleanup(client);
1573
+ performWebDavUpload(client, filename);
1574
+ }
1575
+ }
1576
+ ).catch(function (err) {
1577
+ if (func) { func('WebDAV (getDirectoryContents) error: ' + err); }
1578
+ });
1579
+ }
1580
+
1581
+ // Google Drive Backup
1582
+ if ((typeof parent.config.settings.autobackup == 'object') && (typeof parent.config.settings.autobackup.googledrive == 'object')) {
1583
+ obj.Get('GoogleDriveBackup', function (err, docs) {
1584
+ if ((err != null) || (docs.length != 1) || (docs[0].state != 3)) return;
1585
+ if (func) { func('Attempting Google Drive upload...'); }
1586
+ const {google} = require('googleapis');
1587
+ const oAuth2Client = new google.auth.OAuth2(docs[0].clientid, docs[0].clientsecret, "urn:ietf:wg:oauth:2.0:oob");
1588
+ oAuth2Client.on('tokens', function (tokens) { if (tokens.refresh_token) { docs[0].token = tokens.refresh_token; parent.db.Set(docs[0]); } }); // Update the token in the database
1589
+ oAuth2Client.setCredentials(docs[0].token);
1590
+ const drive = google.drive({ version: 'v3', auth: oAuth2Client });
1591
+ const createdTimeSort = function (a, b) { if (a.createdTime > b.createdTime) return 1; if (a.createdTime < b.createdTime) return -1; return 0; }
1592
+
1593
+ // Called once we know our folder id, clean up and upload a backup.
1594
+ var useGoogleDrive = function (folderid) {
1595
+ // List files to see if we need to delete older ones
1596
+ if (typeof parent.config.settings.autobackup.googledrive.maxfiles == 'number') {
1597
+ drive.files.list({
1598
+ q: 'trashed = false and \'' + folderid + '\' in parents',
1599
+ fields: 'nextPageToken, files(id, name, size, createdTime)',
1600
+ }, function (err, res) {
1601
+ if (err) {
1602
+ console.log('GoogleDrive (files.list) error: ' + err);
1603
+ if (func) { func('GoogleDrive (files.list) error: ' + err); }
1604
+ return;
1605
+ }
1606
+ // Delete any old files if more than 10 files are present in the backup folder.
1607
+ res.data.files.sort(createdTimeSort);
1608
+ while (res.data.files.length >= parent.config.settings.autobackup.googledrive.maxfiles) { drive.files.delete({ fileId: res.data.files.shift().id }, function (err, res) { }); }
1609
+ });
1610
+ }
1611
+
1612
+ //console.log('Uploading...');
1613
+ if (func) { func('Uploading to Google Drive...'); }
1614
+
1615
+ // Upload the backup
1616
+ drive.files.create({
1617
+ requestBody: { name: require('path').basename(filename), mimeType: 'text/plain', parents: [folderid] },
1618
+ media: { mimeType: 'application/zip', body: require('fs').createReadStream(filename) },
1619
+ }, function (err, res) {
1620
+ if (err) {
1621
+ console.log('GoogleDrive (files.create) error: ' + err);
1622
+ if (func) { func('GoogleDrive (files.create) error: ' + err); }
1623
+ return;
1624
+ }
1625
+ //console.log('Upload done.');
1626
+ if (func) { func('Google Drive upload completed.'); }
1627
+ });
1628
+ }
1629
+
1630
+ // Fetch the folder name
1631
+ var folderName = 'MeshCentral-Backups';
1632
+ if (typeof parent.config.settings.autobackup.googledrive.foldername == 'string') { folderName = parent.config.settings.autobackup.googledrive.foldername; }
1633
+
1634
+ // Find our backup folder, create one if needed.
1635
+ drive.files.list({
1636
+ q: 'mimeType = \'application/vnd.google-apps.folder\' and name=\'' + folderName + '\' and trashed = false',
1637
+ fields: 'nextPageToken, files(id, name)',
1638
+ }, function (err, res) {
1639
+ if (err) {
1640
+ console.log('GoogleDrive error: ' + err);
1641
+ if (func) { func('GoogleDrive error: ' + err); }
1642
+ return;
1643
+ }
1644
+ if (res.data.files.length == 0) {
1645
+ // Create a folder
1646
+ drive.files.create({ resource: { 'name': folderName, 'mimeType': 'application/vnd.google-apps.folder' }, fields: 'id' }, function (err, file) {
1647
+ if (err) {
1648
+ console.log('GoogleDrive (folder.create) error: ' + err);
1649
+ if (func) { func('GoogleDrive (folder.create) error: ' + err); }
1650
+ return;
1651
+ }
1652
+ useGoogleDrive(file.data.id);
1653
+ });
1654
+ } else { useGoogleDrive(res.data.files[0].id); }
1655
+ });
1656
+ });
1657
+ }
1658
+ }
1659
+
1660
+ // Transfer NeDB data into the current database
1661
+ obj.nedbtodb = function (func) {
1662
+ var nedbDatastore = require('nedb');
1663
+ var datastoreOptions = { filename: parent.getConfigFilePath('meshcentral.db'), autoload: true };
1664
+
1665
+ // If a DB encryption key is provided, perform database encryption
1666
+ if ((typeof parent.args.dbencryptkey == 'string') && (parent.args.dbencryptkey.length != 0)) {
1667
+ // Hash the database password into a AES256 key and setup encryption and decryption.
1668
+ var nedbKey = parent.crypto.createHash('sha384').update(parent.args.dbencryptkey).digest('raw').slice(0, 32);
1669
+ datastoreOptions.afterSerialization = function (plaintext) {
1670
+ const iv = parent.crypto.randomBytes(16);
1671
+ const aes = parent.crypto.createCipheriv('aes-256-cbc', nedbKey, iv);
1672
+ var ciphertext = aes.update(plaintext);
1673
+ ciphertext = Buffer.concat([iv, ciphertext, aes.final()]);
1674
+ return ciphertext.toString('base64');
1675
+ }
1676
+ datastoreOptions.beforeDeserialization = function (ciphertext) {
1677
+ const ciphertextBytes = Buffer.from(ciphertext, 'base64');
1678
+ const iv = ciphertextBytes.slice(0, 16);
1679
+ const data = ciphertextBytes.slice(16);
1680
+ const aes = parent.crypto.createDecipheriv('aes-256-cbc', nedbKey, iv);
1681
+ var plaintextBytes = Buffer.from(aes.update(data));
1682
+ plaintextBytes = Buffer.concat([plaintextBytes, aes.final()]);
1683
+ return plaintextBytes.toString();
1684
+ }
1685
+ }
1686
+
1687
+ // Setup all NeDB collections
1688
+ var nedbfile = new nedbDatastore(datastoreOptions);
1689
+ var nedbeventsfile = new nedbDatastore({ filename: parent.getConfigFilePath('meshcentral-events.db'), autoload: true, corruptAlertThreshold: 1 });
1690
+ var nedbpowerfile = new nedbDatastore({ filename: parent.getConfigFilePath('meshcentral-power.db'), autoload: true, corruptAlertThreshold: 1 });
1691
+ var nedbserverstatsfile = new nedbDatastore({ filename: parent.getConfigFilePath('meshcentral-stats.db'), autoload: true, corruptAlertThreshold: 1 });
1692
+
1693
+ // Transfered record counts
1694
+ var normalRecordsTransferCount = 0;
1695
+ var eventRecordsTransferCount = 0;
1696
+ var powerRecordsTransferCount = 0;
1697
+ var statsRecordsTransferCount = 0;
1698
+ var pendingTransfer = 0;
1699
+
1700
+ // Transfer the data from main database
1701
+ nedbfile.find({}, function (err, docs) {
1702
+ if ((err == null) && (docs.length > 0)) {
1703
+ performTypedRecordDecrypt(docs)
1704
+ for (var i in docs) {
1705
+ pendingTransfer++;
1706
+ normalRecordsTransferCount++;
1707
+ obj.Set(common.unEscapeLinksFieldName(docs[i]), function () { pendingTransfer--; });
1708
+ }
1709
+ }
1710
+
1711
+ // Transfer events
1712
+ nedbeventsfile.find({}, function (err, docs) {
1713
+ if ((err == null) && (docs.length > 0)) {
1714
+ for (var i in docs) {
1715
+ pendingTransfer++;
1716
+ eventRecordsTransferCount++;
1717
+ obj.StoreEvent(docs[i], function () { pendingTransfer--; });
1718
+ }
1719
+ }
1720
+
1721
+ // Transfer power events
1722
+ nedbpowerfile.find({}, function (err, docs) {
1723
+ if ((err == null) && (docs.length > 0)) {
1724
+ for (var i in docs) {
1725
+ pendingTransfer++;
1726
+ powerRecordsTransferCount++;
1727
+ obj.storePowerEvent(docs[i], null, function () { pendingTransfer--; });
1728
+ }
1729
+ }
1730
+
1731
+ // Transfer server stats
1732
+ nedbserverstatsfile.find({}, function (err, docs) {
1733
+ if ((err == null) && (docs.length > 0)) {
1734
+ for (var i in docs) {
1735
+ pendingTransfer++;
1736
+ statsRecordsTransferCount++;
1737
+ obj.SetServerStats(docs[i], function () { pendingTransfer--; });
1738
+ }
1739
+ }
1740
+
1741
+ // Only exit when all the records are stored.
1742
+ setInterval(function () {
1743
+ if (pendingTransfer == 0) { func("Done. " + normalRecordsTransferCount + " record(s), " + eventRecordsTransferCount + " event(s), " + powerRecordsTransferCount + " power change(s), " + statsRecordsTransferCount + " stat(s)."); }
1744
+ }, 200)
1745
+ });
1746
+ });
1747
+ });
1748
+ });
1749
+ }
1750
+
1751
+ function padNumber(number, digits) { return Array(Math.max(digits - String(number).length + 1, 0)).join(0) + number; }
1752
+
1753
+ // Called when a node has changed
1754
+ function dbNodeChange(nodeChange, added) {
1755
+ common.unEscapeLinksFieldName(nodeChange.fullDocument);
1756
+ const node = performTypedRecordDecrypt([nodeChange.fullDocument])[0];
1757
+ if (node.intelamt != null) { // Remove the Intel AMT password and MPS password before eventing this.
1758
+ if (node.intelamt.pass != null) { node.intelamt.pass = 1; }
1759
+ if (node.intelamt.mpspass != null) { node.intelamt.mpspass = 1; }
1760
+ }
1761
+ parent.DispatchEvent(['*', node.meshid], obj, { etype: 'node', action: (added ? 'addnode' : 'changenode'), node: node, nodeid: node._id, domain: node.domain, nolog: 1 });
1762
+ }
1763
+
1764
+ // Called when a device group has changed
1765
+ function dbMeshChange(meshChange, added) {
1766
+ if (parent.webserver == null) return;
1767
+ common.unEscapeLinksFieldName(meshChange.fullDocument);
1768
+ const mesh = performTypedRecordDecrypt([meshChange.fullDocument])[0];
1769
+
1770
+ // Update the mesh object in memory
1771
+ const mmesh = parent.webserver.meshes[mesh._id];
1772
+ for (var i in mesh) { mmesh[i] = mesh[i]; }
1773
+ for (var i in mmesh) { if (mesh[i] == null) { delete mmesh[i]; } }
1774
+
1775
+ // Send the mesh update
1776
+ if (mesh.deleted) { mesh.action = 'deletemesh'; } else { mesh.action = (added ? 'createmesh' : 'meshchange'); }
1777
+ mesh.meshid = mesh._id;
1778
+ mesh.nolog = 1;
1779
+ delete mesh.type;
1780
+ delete mesh._id;
1781
+ if ((mesh.amt != null) && (mesh.amt.password != null)) {
1782
+ mesh.amt = Object.assign({}, mesh.amt); // Shallow clone
1783
+ if (mesh.amt.password != null) { mesh.amt.password = 1; } // Remove the Intel AMT password if present
1784
+ }
1785
+ parent.DispatchEvent(['*', mesh.meshid], obj, mesh);
1786
+ }
1787
+
1788
+ // Called when a user account has changed
1789
+ function dbUserChange(userChange, added) {
1790
+ if (parent.webserver == null) return;
1791
+ common.unEscapeLinksFieldName(userChange.fullDocument);
1792
+ const user = performTypedRecordDecrypt([userChange.fullDocument])[0];
1793
+
1794
+ // Update the user object in memory
1795
+ const muser = parent.webserver.users[user._id];
1796
+ for (var i in user) { muser[i] = user[i]; }
1797
+ for (var i in muser) { if (user[i] == null) { delete muser[i]; } }
1798
+
1799
+ // Send the user update
1800
+ var targets = ['*', 'server-users', user._id];
1801
+ if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
1802
+ parent.DispatchEvent(targets, obj, { etype: 'user', username: user.name, account: parent.webserver.CloneSafeUser(user), action: (added ? 'accountcreate' : 'accountchange'), domain: user.domain, nolog: 1 });
1803
+ }
1804
+
1805
+ // Called when a user group has changed
1806
+ function dbUGrpChange(ugrpChange, added) {
1807
+ if (parent.webserver == null) return;
1808
+ common.unEscapeLinksFieldName(ugrpChange.fullDocument);
1809
+ const usergroup = ugrpChange.fullDocument;
1810
+
1811
+ // Update the user group object in memory
1812
+ const uusergroup = parent.webserver.userGroups[usergroup._id];
1813
+ for (var i in usergroup) { uusergroup[i] = usergroup[i]; }
1814
+ for (var i in uusergroup) { if (usergroup[i] == null) { delete uusergroup[i]; } }
1815
+
1816
+ // Send the user group update
1817
+ usergroup.action = (added ? 'createusergroup' : 'usergroupchange');
1818
+ usergroup.ugrpid = usergroup._id;
1819
+ usergroup.nolog = 1;
1820
+ delete usergroup.type;
1821
+ delete usergroup._id;
1822
+ parent.DispatchEvent(['*', usergroup.ugrpid], obj, usergroup);
1823
+ }
1824
+
1825
+ return obj;
1826
+};
db.js
+28
@@ -40,8 +40,10 @@ module.exports.CreateDB = function (parent, func) {
40
obj.pluginsActive = ((parent.config) && (parent.config.settings) && (parent.config.settings.plugins != null) && (parent.config.settings.plugins != false) && ((typeof parent.config.settings.plugins != 'object') || (parent.config.settings.plugins.enabled != false)));
41
42
// MongoDB bulk write state
43
+ /*
44
obj.filePendingGet = null;
45
obj.filePendingGets = null;
46
+ */
47
obj.filePendingSet = false;
48
obj.filePendingSets = null;
49
obj.filePendingCb = null;
@@ -1058,7 +1060,30 @@ module.exports.CreateDB = function (parent, func) {
1060
if (func != null) { if (obj.filePendingCb == null) { obj.filePendingCb = [ func ]; } else { obj.filePendingCb.push(func); } }
1061
}
1062
};
1063
+ obj.Get = function (id, func) {
1064
+ if (arguments.length > 2) {
1065
+ var parms = [func];
1066
+ for (var parmx = 2; parmx < arguments.length; ++parmx) { parms.push(arguments[parmx]); }
1067
+ var func2 = function _func2(arg1, arg2) {
1068
+ var userCallback = _func2.userArgs.shift();
1069
+ _func2.userArgs.unshift(arg2);
1070
+ _func2.userArgs.unshift(arg1);
1071
+ userCallback.apply(obj, _func2.userArgs);
1072
+ };
1073
+ func2.userArgs = parms;
1074
+ obj.file.find({ _id: id }).toArray(function (err, docs) {
1075
+ if ((docs != null) && (docs.length > 0) && (docs[0].links != null)) { docs[0] = common.unEscapeLinksFieldName(docs[0]); }
1076
+ func2(err, performTypedRecordDecrypt(docs));
1077
+ });
1078
+ } else {
1079
+ obj.file.find({ _id: id }).toArray(function (err, docs) {
1080
+ if ((docs != null) && (docs.length > 0) && (docs[0].links != null)) { docs[0] = common.unEscapeLinksFieldName(docs[0]); }
1081
+ func(err, performTypedRecordDecrypt(docs));
1082
+ });
1083
+ }
1084
+ };
1085
1086
+ /*
1087
obj.Get = function (id, func) { // Fast Get operation using a bulk find() to reduce round trips to the database.
1088
// Encode arguments into return function if any are present.
1089
var func2 = func;
@@ -1085,6 +1110,7 @@ module.exports.CreateDB = function (parent, func) {
1110
if (obj.filePendingGet[id] == null) { obj.filePendingGet[id] = [func2]; } else { obj.filePendingGet[id].push(func2); }
1111
}
1112
};
1113
+ */
1114
1115
obj.GetAll = function (func) { obj.file.find({}).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
1116
obj.GetHash = function (id, func) { obj.file.find({ _id: id }).project({ _id: 0, hash: 1 }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
@@ -1467,6 +1493,7 @@ module.exports.CreateDB = function (parent, func) {
1493
}
1494
}
1495
1496
+ /*
1497
// MongoDB pending bulk read operation, perform fast bulk document reads.
1498
function fileBulkReadCompleted(err, docs) {
1499
// Send out callbacks with results
@@ -1490,6 +1517,7 @@ module.exports.CreateDB = function (parent, func) {
1517
obj.file.find({ _id: { $in: findlist } }).toArray(fileBulkReadCompleted);
1518
}
1519
}
1520
+ */
1521
1522
// MongoDB pending bulk write operation, perform fast bulk document replacement.
1523
function fileBulkWriteCompleted() {
public/scripts/amt-wsman-0.2.0-min.js
+1
-1
@@ -1 +1 @@
1
-var WsmanStackCreateService=function(e,s,r,a,o,t){var p={};function l(e){if(!e)return"";var s=" ";for(var r in e)e.hasOwnProperty(r)&&0===r.indexOf("@")&&(s+=r.substring(1)+'="'+e[r]+'" ');return s}function w(e){if(!e)return"";if("string"==typeof e)return e;if(e.InstanceID)return'<w:SelectorSet><w:Selector Name="InstanceID">'+e.InstanceID+"</w:Selector></w:SelectorSet>";var s="<w:SelectorSet>";for(var r in e)if(e.hasOwnProperty(r)){if(s+='<w:Selector Name="'+r+'">',e[r].ReferenceParameters){s+="<a:EndpointReference>",s+="<a:Address>"+e[r].Address+"</a:Address><a:ReferenceParameters><w:ResourceURI>"+e[r].ReferenceParameters.ResourceURI+"</w:ResourceURI><w:SelectorSet>";var a=e[r].ReferenceParameters.SelectorSet.Selector;if(Array.isArray(a))for(var o=0;o<a.length;o++)s+="<w:Selector"+l(a[o])+">"+a[o].Value+"</w:Selector>";else s+="<w:Selector"+l(a)+">"+a.Value+"</w:Selector>";s+="</w:SelectorSet></a:ReferenceParameters></a:EndpointReference>"}else s+=e[r];s+="</w:Selector>"}return s+="</w:SelectorSet>"}return p.NextMessageId=1,p.Address="/wsman",p.comm=CreateWsmanComm(e,s,r,a,o,t),p.PerformAjax=function(e,o,s,r,a){null==a&&(a=""),p.comm.PerformAjax('<?xml version=\"1.0\" encoding=\"utf-8\"?><Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns="http://www.w3.org/2003/05/soap-envelope" '+a+"><Header><a:Action>"+e,function(e,s,r){if(200==s){var a=p.ParseWsman(e);a&&null!=a?o(p,a.Header.ResourceURI,a,200,r):o(p,null,{Header:{HttpError:s}},601,r)}else o(p,null,{Header:{HttpError:s}},s,r)},s,r)},p.CancelAllQueries=function(e){p.comm.CancelAllQueries(e)},p.GetNameFromUrl=function(e){var s=e.lastIndexOf("/");return-1==s?e:e.substring(s+1)},p.ExecSubscribe=function(e,s,r,a,o,t,n,l,d,c){var m="",i="";null!=d&&null!=c&&(m="<t:IssuedTokens><t:RequestSecurityTokenResponse><t:TokenType>http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#UsernameToken</t:TokenType><t:RequestedSecurityToken><se:UsernameToken><se:Username>"+d+'</se:Username><se:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd#PasswordText">'+c+"</se:Password></se:UsernameToken></t:RequestedSecurityToken></t:RequestSecurityTokenResponse></t:IssuedTokens>",i='<Auth Profile="http://schemas.xmlsoap.org/ws/2004/08/eventing/DeliveryModes/secprofile/http/digest"/>'),l=null!=l&&null!=l?"<a:ReferenceParameters>"+l+"</a:ReferenceParameters>":"";var u="http://schemas.xmlsoap.org/ws/2004/08/eventing/Subscribe</a:Action><a:To>"+p.Address+"</a:To><w:ResourceURI>"+e+"</w:ResourceURI><a:MessageID>"+p.NextMessageId+++"</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>"+w(n)+m+'</Header><Body><e:Subscribe><e:Delivery Mode="http://schemas.dmtf.org/wbem/wsman/1/wsman/'+s+'"><e:NotifyTo><a:Address>'+r+"</a:Address></e:NotifyTo>"+i+"</e:Delivery><e:Expires>PT0.000000S</e:Expires></e:Subscribe>";p.PerformAjax(u+"</Body></Envelope>",a,o,t,'xmlns:e="http://schemas.xmlsoap.org/ws/2004/08/eventing" xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust" xmlns:se="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:m="http://x.com"')},p.ExecUnSubscribe=function(e,s,r,a,o){var t="http://schemas.xmlsoap.org/ws/2004/08/eventing/Unsubscribe</a:Action><a:To>"+p.Address+"</a:To><w:ResourceURI>"+e+"</w:ResourceURI><a:MessageID>"+p.NextMessageId+++"</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>"+w(o)+"</Header><Body><e:Unsubscribe/>";p.PerformAjax(t+"</Body></Envelope>",s,r,a,'xmlns:e="http://schemas.xmlsoap.org/ws/2004/08/eventing"')},p.ExecPut=function(e,s,r,a,o,t){var n="http://schemas.xmlsoap.org/ws/2004/09/transfer/Put</a:Action><a:To>"+p.Address+"</a:To><w:ResourceURI>"+e+"</w:ResourceURI><a:MessageID>"+p.NextMessageId+++"</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><w:OperationTimeout>PT60.000S</w:OperationTimeout>"+w(t)+"</Header><Body>"+function(e,s){if(!e||null==s)return"";var r=p.GetNameFromUrl(e),a="<r:"+r+' xmlns:r="'+e+'">';for(var o in s)if(s.hasOwnProperty(o)&&0!==o.indexOf("__")&&0!==o.indexOf("@")&&void 0!==s[o]&&null!==s[o]&&"function"!=typeof s[o])if("object"==typeof s[o]&&s[o].ReferenceParameters){a+="<r:"+o+"><a:Address>"+s[o].Address+"</a:Address><a:ReferenceParameters><w:ResourceURI>"+s[o].ReferenceParameters.ResourceURI+"</w:ResourceURI><w:SelectorSet>";var t=s[o].ReferenceParameters.SelectorSet.Selector;if(Array.isArray(t))for(var n=0;n<t.length;n++)a+="<w:Selector"+l(t[n])+">"+t[n].Value+"</w:Selector>";else a+="<w:Selector"+l(t)+">"+t.Value+"</w:Selector>";a+="</w:SelectorSet></a:ReferenceParameters></r:"+o+">"}else if(Array.isArray(s[o]))for(n=0;n<s[o].length;n++)a+="<r:"+o+">"+s[o][n].toString()+"</r:"+o+">";else a+="<r:"+o+">"+s[o].toString()+"</r:"+o+">";return a+="</r:"+r+">"}(e,s);p.PerformAjax(n+"</Body></Envelope>",r,a,o)},p.ExecCreate=function(e,s,r,a,o,t){var n=p.GetNameFromUrl(e),l="http://schemas.xmlsoap.org/ws/2004/09/transfer/Create</a:Action><a:To>"+p.Address+"</a:To><w:ResourceURI>"+e+"</w:ResourceURI><a:MessageID>"+p.NextMessageId+++"</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><w:OperationTimeout>PT60S</w:OperationTimeout>"+w(t)+"</Header><Body><g:"+n+' xmlns:g="'+e+'">';for(var d in s)l+="<g:"+d+">"+s[d]+"</g:"+d+">";p.PerformAjax(l+"</g:"+n+"></Body></Envelope>",r,a,o)},p.ExecCreateXml=function(e,s,r,a,o){var t=p.GetNameFromUrl(e);p.PerformAjax("http://schemas.xmlsoap.org/ws/2004/09/transfer/Create</a:Action><a:To>"+p.Address+"</a:To><w:ResourceURI>"+e+"</w:ResourceURI><a:MessageID>"+p.NextMessageId+++"</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><w:OperationTimeout>PT60.000S</w:OperationTimeout></Header><Body><r:"+t+' xmlns:r="'+e+'">'+s+"</r:"+t+"></Body></Envelope>",r,a,o)},p.ExecDelete=function(e,s,r,a,o){var t="http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete</a:Action><a:To>"+p.Address+"</a:To><w:ResourceURI>"+e+"</w:ResourceURI><a:MessageID>"+p.NextMessageId+++"</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><w:OperationTimeout>PT60S</w:OperationTimeout>"+w(s)+"</Header><Body /></Envelope>";p.PerformAjax(t,r,a,o)},p.ExecGet=function(e,s,r,a){p.PerformAjax("http://schemas.xmlsoap.org/ws/2004/09/transfer/Get</a:Action><a:To>"+p.Address+"</a:To><w:ResourceURI>"+e+"</w:ResourceURI><a:MessageID>"+p.NextMessageId+++"</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><w:OperationTimeout>PT60S</w:OperationTimeout></Header><Body /></Envelope>",s,r,a)},p.ExecMethod=function(e,s,r,a,o,t,n){var l="";for(var d in r)if(null!=r[d])if(Array.isArray(r[d]))for(var c in r[d])l+="<r:"+d+">"+r[d][c]+"</r:"+d+">";else l+="<r:"+d+">"+r[d]+"</r:"+d+">";p.ExecMethodXml(e,s,l,a,o,t,n)},p.ExecMethodXml=function(e,s,r,a,o,t,n){p.PerformAjax(e+"/"+s+"</a:Action><a:To>"+p.Address+"</a:To><w:ResourceURI>"+e+"</w:ResourceURI><a:MessageID>"+p.NextMessageId+++"</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><w:OperationTimeout>PT60S</w:OperationTimeout>"+w(n)+"</Header><Body><r:"+s+'_INPUT xmlns:r="'+e+'">'+r+"</r:"+s+"_INPUT></Body></Envelope>",a,o,t)},p.ExecEnum=function(e,s,r,a){p.PerformAjax("http://schemas.xmlsoap.org/ws/2004/09/enumeration/Enumerate</a:Action><a:To>"+p.Address+"</a:To><w:ResourceURI>"+e+"</w:ResourceURI><a:MessageID>"+p.NextMessageId+++'</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><w:OperationTimeout>PT60S</w:OperationTimeout></Header><Body><Enumerate xmlns="http://schemas.xmlsoap.org/ws/2004/09/enumeration" /></Body></Envelope>',s,r,a)},p.ExecPull=function(e,s,r,a,o){p.PerformAjax("http://schemas.xmlsoap.org/ws/2004/09/enumeration/Pull</a:Action><a:To>"+p.Address+"</a:To><w:ResourceURI>"+e+"</w:ResourceURI><a:MessageID>"+p.NextMessageId+++'</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><w:OperationTimeout>PT60S</w:OperationTimeout></Header><Body><Pull xmlns="http://schemas.xmlsoap.org/ws/2004/09/enumeration"><EnumerationContext>'+s+"</EnumerationContext><MaxElements>999</MaxElements><MaxCharacters>99999</MaxCharacters></Pull></Body></Envelope>",r,a,o)},p.ParseWsman=function(s){try{s.childNodes||(s=function(e){{if(window.DOMParser)return(new DOMParser).parseFromString(e,"text/xml");var s=new ActiveXObject("Microsoft.XMLDOM");return s.async=!1,s.loadXML(e),s}}(s));var e,r={Header:{}},a=s.getElementsByTagName("Header")[0];if(!(a=a||s.getElementsByTagName("a:Header")[0]))return null;for(var o=0;o<a.childNodes.length;o++){var t=a.childNodes[o];r.Header[t.localName]=t.textContent}var n=s.getElementsByTagName("Body")[0];if(!(n=n||s.getElementsByTagName("a:Body")[0]))return null;if(0<n.childNodes.length){var l=(e=n.childNodes[0].localName).indexOf("_OUTPUT");-1!=l&&l==e.length-7&&(e=e.substring(0,e.length-7)),r.Header.Method=e,r.Body=function e(s){var r,a={};for(var o=0;o<s.childNodes.length;o++){var t=s.childNodes[o];"true"==(r=0==t.childElementCount?t.textContent:e(t))&&(r=!0),"false"==r&&(r=!1);var n=r;if(0<t.attributes.length){n={Value:r};for(var l=0;l<t.attributes.length;l++)n["@"+t.attributes[l].name]=t.attributes[l].value}a[t.localName]instanceof Array?a[t.localName].push(n):null==a[t.localName]?a[t.localName]=n:a[t.localName]=[a[t.localName],n]}return a}(n.childNodes[0])}return r}catch(e){return console.log("Unable to parse XML: "+s),null}},p}
\ No newline at end of file
1
+var WsmanStackCreateService=function(e,s,r,a,o,t){var p={};function l(e){if(!e)return"";var s=" ";for(var r in e)e.hasOwnProperty(r)&&0===r.indexOf("@")&&(s+=r.substring(1)+'="'+e[r]+'" ');return s}function w(e){if(!e)return"";if("string"==typeof e)return e;if(e.InstanceID)return'<w:SelectorSet><w:Selector Name="InstanceID">'+e.InstanceID+"</w:Selector></w:SelectorSet>";var s="<w:SelectorSet>";for(var r in e)if(e.hasOwnProperty(r)){if(s+='<w:Selector Name="'+r+'">',e[r].ReferenceParameters){s+="<a:EndpointReference>",s+="<a:Address>"+e[r].Address+"</a:Address><a:ReferenceParameters><w:ResourceURI>"+e[r].ReferenceParameters.ResourceURI+"</w:ResourceURI><w:SelectorSet>";var a=e[r].ReferenceParameters.SelectorSet.Selector;if(Array.isArray(a))for(var o=0;o<a.length;o++)s+="<w:Selector"+l(a[o])+">"+a[o].Value+"</w:Selector>";else s+="<w:Selector"+l(a)+">"+a.Value+"</w:Selector>";s+="</w:SelectorSet></a:ReferenceParameters></a:EndpointReference>"}else s+=e[r];s+="</w:Selector>"}return s+="</w:SelectorSet>"}return p.NextMessageId=1,p.Address="/wsman",p.comm=CreateWsmanComm(e,s,r,a,o,t),p.PerformAjax=function(e,o,s,r,a){null==a&&(a=""),p.comm.PerformAjax('<?xml version=\"1.0\" encoding=\"utf-8\"?><Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns="http://www.w3.org/2003/05/soap-envelope" '+a+"><Header><a:Action>"+e,function(e,s,r){if(200==s){var a=p.ParseWsman(e);a&&null!=a?o(p,a.Header.ResourceURI,a,200,r):o(p,null,{Header:{HttpError:s}},601,r)}else o(p,null,{Header:{HttpError:s}},s,r)},s,r)},p.CancelAllQueries=function(e){p.comm.CancelAllQueries(e)},p.GetNameFromUrl=function(e){var s=e.lastIndexOf("/");return-1==s?e:e.substring(s+1)},p.ExecSubscribe=function(e,s,r,a,o,t,n,l,c,d){var m="",i="";null!=c&&null!=d&&(m="<t:IssuedTokens><t:RequestSecurityTokenResponse><t:TokenType>http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#UsernameToken</t:TokenType><t:RequestedSecurityToken><se:UsernameToken><se:Username>"+c+'</se:Username><se:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd#PasswordText">'+d+"</se:Password></se:UsernameToken></t:RequestedSecurityToken></t:RequestSecurityTokenResponse></t:IssuedTokens>",i='<Auth Profile="http://schemas.xmlsoap.org/ws/2004/08/eventing/DeliveryModes/secprofile/http/digest"/>'),l=null!=l&&null!=l?"<a:ReferenceParameters>"+l+"</a:ReferenceParameters>":"";var u="http://schemas.xmlsoap.org/ws/2004/08/eventing/Subscribe</a:Action><a:To>"+p.Address+"</a:To><w:ResourceURI>"+e+"</w:ResourceURI><a:MessageID>"+p.NextMessageId+++"</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>"+w(n)+m+'</Header><Body><e:Subscribe><e:Delivery Mode="http://schemas.dmtf.org/wbem/wsman/1/wsman/'+s+'"><e:NotifyTo><a:Address>'+r+"</a:Address></e:NotifyTo>"+i+"</e:Delivery><e:Expires>PT0.000000S</e:Expires></e:Subscribe>";p.PerformAjax(u+"</Body></Envelope>",a,o,t,'xmlns:e="http://schemas.xmlsoap.org/ws/2004/08/eventing" xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust" xmlns:se="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:m="http://x.com"')},p.ExecUnSubscribe=function(e,s,r,a,o){var t="http://schemas.xmlsoap.org/ws/2004/08/eventing/Unsubscribe</a:Action><a:To>"+p.Address+"</a:To><w:ResourceURI>"+e+"</w:ResourceURI><a:MessageID>"+p.NextMessageId+++"</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>"+w(o)+"</Header><Body><e:Unsubscribe/>";p.PerformAjax(t+"</Body></Envelope>",s,r,a,'xmlns:e="http://schemas.xmlsoap.org/ws/2004/08/eventing"')},p.ExecPut=function(e,s,r,a,o,t){var n="http://schemas.xmlsoap.org/ws/2004/09/transfer/Put</a:Action><a:To>"+p.Address+"</a:To><w:ResourceURI>"+e+"</w:ResourceURI><a:MessageID>"+p.NextMessageId+++"</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><w:OperationTimeout>PT60.000S</w:OperationTimeout>"+w(t)+"</Header><Body>"+function(e,s){if(!e||null==s)return"";var r=p.GetNameFromUrl(e),a="<r:"+r+' xmlns:r="'+e+'">';for(var o in s)if(s.hasOwnProperty(o)&&0!==o.indexOf("__")&&0!==o.indexOf("@")&&void 0!==s[o]&&null!==s[o]&&"function"!=typeof s[o])if("object"==typeof s[o]&&s[o].ReferenceParameters){a+="<r:"+o+"><a:Address>"+s[o].Address+"</a:Address><a:ReferenceParameters><w:ResourceURI>"+s[o].ReferenceParameters.ResourceURI+"</w:ResourceURI><w:SelectorSet>";var t=s[o].ReferenceParameters.SelectorSet.Selector;if(Array.isArray(t))for(var n=0;n<t.length;n++)a+="<w:Selector"+l(t[n])+">"+t[n].Value+"</w:Selector>";else a+="<w:Selector"+l(t)+">"+t.Value+"</w:Selector>";a+="</w:SelectorSet></a:ReferenceParameters></r:"+o+">"}else if(Array.isArray(s[o]))for(n=0;n<s[o].length;n++)a+="<r:"+o+">"+s[o][n].toString()+"</r:"+o+">";else a+="<r:"+o+">"+s[o].toString()+"</r:"+o+">";return a+="</r:"+r+">"}(e,s);p.PerformAjax(n+"</Body></Envelope>",r,a,o)},p.ExecCreate=function(e,s,r,a,o,t){var n=p.GetNameFromUrl(e),l="http://schemas.xmlsoap.org/ws/2004/09/transfer/Create</a:Action><a:To>"+p.Address+"</a:To><w:ResourceURI>"+e+"</w:ResourceURI><a:MessageID>"+p.NextMessageId+++"</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><w:OperationTimeout>PT60S</w:OperationTimeout>"+w(t)+"</Header><Body><g:"+n+' xmlns:g="'+e+'">';for(var c in s)l+="<g:"+c+">"+s[c]+"</g:"+c+">";p.PerformAjax(l+"</g:"+n+"></Body></Envelope>",r,a,o)},p.ExecCreateXml=function(e,s,r,a,o){var t=p.GetNameFromUrl(e);p.PerformAjax("http://schemas.xmlsoap.org/ws/2004/09/transfer/Create</a:Action><a:To>"+p.Address+"</a:To><w:ResourceURI>"+e+"</w:ResourceURI><a:MessageID>"+p.NextMessageId+++"</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><w:OperationTimeout>PT60.000S</w:OperationTimeout></Header><Body><r:"+t+' xmlns:r="'+e+'">'+s+"</r:"+t+"></Body></Envelope>",r,a,o)},p.ExecDelete=function(e,s,r,a,o){var t="http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete</a:Action><a:To>"+p.Address+"</a:To><w:ResourceURI>"+e+"</w:ResourceURI><a:MessageID>"+p.NextMessageId+++"</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><w:OperationTimeout>PT60S</w:OperationTimeout>"+w(s)+"</Header><Body /></Envelope>";p.PerformAjax(t,r,a,o)},p.ExecGet=function(e,s,r,a){p.PerformAjax("http://schemas.xmlsoap.org/ws/2004/09/transfer/Get</a:Action><a:To>"+p.Address+"</a:To><w:ResourceURI>"+e+"</w:ResourceURI><a:MessageID>"+p.NextMessageId+++"</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><w:OperationTimeout>PT60S</w:OperationTimeout></Header><Body /></Envelope>",s,r,a)},p.ExecMethod=function(e,s,r,a,o,t,n){var l="";for(var c in r)if(null!=r[c])if(Array.isArray(r[c]))for(var d in r[c])l+="<r:"+c+">"+r[c][d]+"</r:"+c+">";else l+="<r:"+c+">"+r[c]+"</r:"+c+">";p.ExecMethodXml(e,s,l,a,o,t,n)},p.ExecMethodXml=function(e,s,r,a,o,t,n){p.PerformAjax(e+"/"+s+"</a:Action><a:To>"+p.Address+"</a:To><w:ResourceURI>"+e+"</w:ResourceURI><a:MessageID>"+p.NextMessageId+++"</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><w:OperationTimeout>PT60S</w:OperationTimeout>"+w(n)+"</Header><Body><r:"+s+'_INPUT xmlns:r="'+e+'">'+r+"</r:"+s+"_INPUT></Body></Envelope>",a,o,t)},p.ExecEnum=function(e,s,r,a){p.PerformAjax("http://schemas.xmlsoap.org/ws/2004/09/enumeration/Enumerate</a:Action><a:To>"+p.Address+"</a:To><w:ResourceURI>"+e+"</w:ResourceURI><a:MessageID>"+p.NextMessageId+++'</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><w:OperationTimeout>PT60S</w:OperationTimeout></Header><Body><Enumerate xmlns="http://schemas.xmlsoap.org/ws/2004/09/enumeration" /></Body></Envelope>',s,r,a)},p.ExecPull=function(e,s,r,a,o){p.PerformAjax("http://schemas.xmlsoap.org/ws/2004/09/enumeration/Pull</a:Action><a:To>"+p.Address+"</a:To><w:ResourceURI>"+e+"</w:ResourceURI><a:MessageID>"+p.NextMessageId+++'</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><w:OperationTimeout>PT60S</w:OperationTimeout></Header><Body><Pull xmlns="http://schemas.xmlsoap.org/ws/2004/09/enumeration"><EnumerationContext>'+s+"</EnumerationContext><MaxElements>999</MaxElements><MaxCharacters>99999</MaxCharacters></Pull></Body></Envelope>",r,a,o)},p.ParseWsman=function(s){try{s.childNodes||(s=function(e){{if(window.DOMParser)return(new DOMParser).parseFromString(e,"text/xml");var s=new ActiveXObject("Microsoft.XMLDOM");return s.async=!1,s.loadXML(e),s}}(s));var e,r={Header:{}},a=s.getElementsByTagName("Header")[0];if(!(a=a||s.getElementsByTagName("a:Header")[0]))return null;for(var o=0;o<a.childNodes.length;o++){var t=a.childNodes[o];r.Header[t.localName]=t.textContent}var n=s.getElementsByTagName("Body")[0];if(!(n=n||s.getElementsByTagName("a:Body")[0]))return null;if(0<n.childNodes.length){var l=(e=n.childNodes[0].localName).indexOf("_OUTPUT");-1!=l&&l==e.length-7&&(e=e.substring(0,e.length-7)),r.Header.Method=e,r.Body=function e(s){var r,a={};for(var o=0;o<s.childNodes.length;o++){var t=s.childNodes[o];"true"==(r=0==t.childElementCount?t.textContent:e(t))&&(r=!0),"false"==r&&(r=!1);var n=r;if(0<t.attributes.length){n={Value:r};for(var l=0;l<t.attributes.length;l++)n["@"+t.attributes[l].name]=t.attributes[l].value}a[t.localName]instanceof Array?a[t.localName].push(n):null==a[t.localName]?a[t.localName]=n:a[t.localName]=[a[t.localName],n]}return a}(n.childNodes[0])}return r}catch(e){return console.log("Unable to parse XML: "+s),null}},p}
\ No newline at end of file
views/default-mobile.handlebars
+1
-1
@@ -4856,7 +4856,7 @@
4856
"SMS error: {0}"
4857
];
4858
if (typeof n.titleid == 'number') { try { n.title = translatedTitles[n.titleid]; } catch (ex) { } }
4859
- if (typeof n.msgid == 'number') { try { n.text = translatedMessages[n.msgid]; if (Array.isArray(n.args)) { format(n.text, ...n.args); } } catch (ex) { } }
4859
+ if (typeof n.msgid == 'number') { try { n.text = translatedMessages[n.msgid]; if (Array.isArray(n.args)) { n.text = format(n.text, ...n.args); } } catch (ex) { } }
4860
4861
// Show notification within the web page.
4862
if (n.time == null) { n.time = Date.now(); }
views/default.handlebars
+1
-1
@@ -13325,7 +13325,7 @@
13325
"SMS error: {0}"
13326
];
13327
if (typeof n.titleid == 'number') { try { n.title = translatedTitles[n.titleid]; } catch (ex) {} }
13328
- if (typeof n.msgid == 'number') { try { n.text = translatedMessages[n.msgid]; if (Array.isArray(n.args)) { format(n.text, ...n.args); } } catch (ex) {} }
13328
+ if (typeof n.msgid == 'number') { try { n.text = translatedMessages[n.msgid]; if (Array.isArray(n.args)) { n.text = format(n.text, ...n.args); } } catch (ex) {} }
13329
13330
// Show notification within the web page.
13331
if (n.time == null) { n.time = Date.now(); }