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(); }