From 348065fec309a80fabadff8cf20fa49d7194e8e4 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Thu, 4 Jan 2018 15:59:57 -0800 Subject: [PATCH] Event limit, server improvements --- common.js | 9 +++++++++ db.js | 3 ++- meshcentral.js | 24 ++++++++++++++++++++++-- meshuser.js | 32 ++++++++++++++++++++++---------- multiserver.js | 2 +- package.json | 2 +- views/default.handlebars | 20 ++++++++++++++++++-- webserver.js | 4 ++-- 8 files changed, 77 insertions(+), 19 deletions(-) diff --git a/common.js b/common.js index e073d255..7533e17f 100644 --- a/common.js +++ b/common.js @@ -117,3 +117,12 @@ module.exports.ComputeDigesthash = function (username, password, realm, method, module.exports.toNumber = function (str) { var x = parseInt(str); if (x == str) return x; return str; } module.exports.escapeHtml = function (string) { return String(string).replace(/[&<>"'`=\/]/g, function (s) { return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '/': '/', '`': '`', '=': '=' }[s]; }); } module.exports.escapeHtmlBreaks = function (string) { return String(string).replace(/[&<>"'`=\/]/g, function (s) { return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '/': '/', '`': '`', '=': '=', '\r': '
', '\n': '' }[s]; }); } + +// Lowercase all the names in a object recursively +module.exports.objKeysToLower = function (obj) { + for (var i in obj) { + if (i.toLowerCase() !== i) { obj[i.toLowerCase()] = obj[i]; delete obj[i]; } // LowerCase all key names + if (typeof obj[i] == 'object') { module.exports.objKeysToLower(obj[i]); } // LowerCase all key names in the child object + } + return obj; +} \ No newline at end of file diff --git a/db.js b/db.js index 37e4c443..7fd833fa 100644 --- a/db.js +++ b/db.js @@ -96,7 +96,8 @@ module.exports.CreateDB = function (args, datapath) { obj.RemoveAllOfType = function (type, func) { obj.file.remove({ type: type }, { multi: true }, func); } obj.InsertMany = function (data, func) { obj.file.insert(data, func); } obj.StoreEvent = function (ids, source, event) { obj.file.insert(event); } - obj.GetEvents = function (ids, domain, func) { if (obj.databaseType == 1) { obj.file.find({ type: 'event', domain: domain, ids: { $in: ids } }, { type: 0, _id: 0 }).sort({ time: -1 }).exec(func); } else { obj.file.find({ type: 'event', domain: domain, ids: { $in: ids } }, { type: 0, _id: 0 }).sort({ time: -1 }, func) } } + obj.GetEvents = function (ids, domain, func) { if (obj.databaseType == 1) { obj.file.find({ type: 'event', domain: domain, ids: { $in: ids } }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).exec(func); } else { obj.file.find({ type: 'event', domain: domain, ids: { $in: ids } }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }, func) } } + obj.GetEventsWithLimit = function (ids, domain, limit, func) { if (obj.databaseType == 1) { obj.file.find({ type: 'event', domain: domain, ids: { $in: ids } }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).exec(func); } else { obj.file.find({ type: 'event', domain: domain, ids: { $in: ids } }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit, func); } } obj.RemoveMesh = function (id) { obj.file.remove({ mesh: id }, { multi: true }); obj.file.remove({ _id: id }); } obj.RemoveAllEvents = function (domain) { obj.file.remove({ type: 'event', domain: domain }, { multi: true }); } obj.MakeSiteAdmin = function (username, domain) { obj.Get('user/' + domain + '/' + username, function (err, docs) { if (docs.length == 1) { docs[0].siteadmin = 0xFFFFFFFF; obj.Set(docs[0]); } }); } diff --git a/meshcentral.js b/meshcentral.js index 4cef4f90..641ca31b 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -198,6 +198,7 @@ function CreateMeshCentralServer() { // Set the command line arguments to the config file if they are not present if (obj.config.settings) { for (var i in obj.config.settings) { if (obj.args[i] == null) obj.args[i] = obj.config.settings[i]; } } } + obj.common.objKeysToLower(obj.config); // Lower case all keys in the config file // Read environment variables. For a subset of arguments, we allow them to be read from environment variables. var xenv = ['user', 'port', 'mpsport', 'redirport', 'exactport', 'debug']; @@ -211,7 +212,6 @@ function CreateMeshCentralServer() { var bannedDomains = ['public', 'private', 'images', 'scripts', 'styles', 'views']; // List of banned domains for (var i in obj.config.domains) { for (var j in bannedDomains) { if (i == bannedDomains[j]) { console.log("ERROR: Domain '" + i + "' is not allowed domain name in ./data/config.json."); return; } } } for (var i in obj.config.domains) { - for (var j in obj.config.domains[i]) { if (j.toLocaleLowerCase() !== j) { obj.config.domains[i][j.toLocaleLowerCase()] = obj.config.domains[i][j]; delete obj.config.domains[i][j]; } } // LowerCase all domain keys if (obj.config.domains[i].dns == null) { obj.config.domains[i].url = (i == '') ? '/' : ('/' + i + '/'); } else { obj.config.domains[i].url = '/'; } obj.config.domains[i].id = i; if (typeof obj.config.domains[i].userallowedip == 'string') { obj.config.domains[i].userallowedip = null; if (obj.config.domains[i].userallowedip != "") { obj.config.domains[i].userallowedip = obj.config.domains[i].userallowedip.split(','); } } @@ -385,7 +385,7 @@ function CreateMeshCentralServer() { obj.DispatchEvent(['*'], obj, { etype: 'server', action: 'started', msg: 'Server started' }) // Load the login cookie encryption key from the database if allowed - if ((obj.config) && (obj.config.settings) && (obj.config.settings.allowLoginToken == true)) { + if ((obj.config) && (obj.config.settings) && (obj.config.settings.allowlogintoken == true)) { obj.db.Get('LoginCookieEncryptionKey', function (err, docs) { if ((docs.length > 0) && (docs[0].key != null) && (obj.args.logintokengen == null)) { obj.loginCookieEncryptionKey = Buffer.from(docs[0].key, 'hex'); @@ -697,6 +697,7 @@ function CreateMeshCentralServer() { } // Update the default mesh core + obj.updateMeshCoreTimer = 'notset'; obj.updateMeshCore = function (func) { // Figure out where meshcore.js is var meshcorePath = obj.datapath; @@ -731,9 +732,19 @@ function CreateMeshCentralServer() { obj.defaultMeshCoreNoMei = obj.common.IntToStr(0) + moduleAdditionsNoMei + meshCore; obj.defaultMeshCoreNoMeiHash = obj.crypto.createHash('sha384').update(obj.defaultMeshCoreNoMei).digest("binary"); if (func != null) { func(true); } + + // If meshcore.js is in the data folder, monitor the file for changes. + if ((obj.updateMeshCoreTimer === 'notset') && (meshcorePath == obj.datapath)) { + obj.updateMeshCoreTimer = null; + obj.fs.watch(obj.path.join(meshcorePath, 'meshcore.js'), function (eventType, filename) { + if (obj.updateMeshCoreTimer != null) { clearTimeout(obj.updateMeshCoreTimer); obj.updateMeshCoreTimer = null; } + obj.updateMeshCoreTimer = setTimeout(function () { obj.updateMeshCore(); console.log('Updated meshcore.js.'); }, 5000); + }) + } } // Update the default meshcmd + obj.updateMeshCmdTimer = 'notset'; obj.updateMeshCmd = function (func) { // Figure out where meshcmd.js is var meshcmdPath = obj.datapath; @@ -762,6 +773,15 @@ function CreateMeshCentralServer() { // Set the new default meshcmd.js obj.defaultMeshCmd = moduleAdditions + meshCmd; if (func != null) { func(true); } + + // If meshcore.js is in the data folder, monitor the file for changes. + if ((obj.updateMeshCmdTimer === 'notset') && (meshcmdPath == obj.datapath)) { + obj.updateMeshCmdTimer = null; + obj.fs.watch(obj.path.join(meshcmdPath, 'meshcmd.js'), function (eventType, filename) { + if (obj.updateMeshCmdTimer != null) { clearTimeout(obj.updateMeshCmdTimer); obj.updateMeshCmdTimer = null; } + obj.updateMeshCmdTimer = setTimeout(function () { obj.updateMeshCmd(); console.log('Updated meshcmd.js.'); }, 5000); + }) + } } // List of possible mesh agent install scripts diff --git a/meshuser.js b/meshuser.js index 58f14e9b..45052214 100644 --- a/meshuser.js +++ b/meshuser.js @@ -73,14 +73,21 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { // Request a list of all meshes this user as rights to var docs = []; for (var i in user.links) { if (obj.parent.meshes[i]) { docs.push(obj.parent.meshes[i]); } } - ws.send(JSON.stringify({ action: 'meshes', meshes: docs })); + ws.send(JSON.stringify({ action: 'meshes', meshes: docs, tag: command.tag })); break; } case 'nodes': { - // Request a list of all meshes this user as rights to var links = []; - for (var i in user.links) { links.push(i); } + if (command.meshid == null) { + // Request a list of all meshes this user as rights to + for (var i in user.links) { links.push(i); } + } else { + // Request list of all nodes for one specific meshid + var meshid = command.meshid; + if (meshid.split('/').length == 0) { meshid = 'mesh/' + domain.id + '/' + command.meshid; } + if (user.links[meshid] != null) { links.push(meshid); } + } // Request a list of all nodes obj.db.GetAllTypeNoTypeFieldMeshFiltered(links, domain.id, 'node', function (err, docs) { @@ -105,7 +112,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { r[meshid].push(docs[i]); } - ws.send(JSON.stringify({ action: 'nodes', nodes: r })); + ws.send(JSON.stringify({ action: 'nodes', nodes: r, tag: command.tag })); }); break; } @@ -148,11 +155,11 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { } } } - ws.send(JSON.stringify({ action: 'powertimeline', nodeid: command.nodeid, timeline: timeline })); + ws.send(JSON.stringify({ action: 'powertimeline', nodeid: command.nodeid, timeline: timeline, tag: command.tag })); } else { // No records found, send current state if we have it var state = obj.parent.parent.GetConnectivityState(command.nodeid); - if (state != null) { ws.send(JSON.stringify({ action: 'powertimeline', nodeid: command.nodeid, timeline: [state.powerState, Date.now(), state.powerState] })); } + if (state != null) { ws.send(JSON.stringify({ action: 'powertimeline', nodeid: command.nodeid, timeline: [state.powerState, Date.now(), state.powerState], tag: command.tag })); } } }); break; @@ -223,8 +230,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { } case 'events': { - // Send the list of events for this session - obj.db.GetEvents(user.subscriptions, domain.id, function (err, docs) { if (err != null) return; ws.send(JSON.stringify({ action: 'events', events: docs })); }); + if ((command.limit == null) || (typeof command.limit != 'number')) { + // Send the list of all events for this session + obj.db.GetEvents(user.subscriptions, domain.id, function (err, docs) { if (err != null) return; ws.send(JSON.stringify({ action: 'events', events: docs, tag: command.tag })); }); + } else { + // Send the list of most recent events for this session, up to 'limit' count + obj.db.GetEventsWithLimit(user.subscriptions, domain.id, command.limit, function (err, docs) { if (err != null) return; ws.send(JSON.stringify({ action: 'events', events: docs, tag: command.tag })); }); + } break; } case 'clearevents': @@ -253,7 +265,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { docs.push(userinfo); } } - ws.send(JSON.stringify({ action: 'users', users: docs })); + ws.send(JSON.stringify({ action: 'users', users: docs, tag: command.tag })); break; } case 'changeemail': @@ -320,7 +332,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { // We have peer servers, use more complex session counting for (var userid in obj.sessionsCount) { if (userid.split('/')[1] == domain.id) { wssessions[userid] = obj.sessionsCount[userid]; } } } - ws.send(JSON.stringify({ action: 'wssessioncount', wssessions: wssessions })); // wssessions is: userid --> count + ws.send(JSON.stringify({ action: 'wssessioncount', wssessions: wssessions, tag: command.tag })); // wssessions is: userid --> count break; } case 'deleteuser': diff --git a/multiserver.js b/multiserver.js index f2c94704..939baca2 100644 --- a/multiserver.js +++ b/multiserver.js @@ -370,7 +370,7 @@ module.exports.CreateMultiServer = function (parent, args) { // If we have no peering configuration, don't setup this object if (obj.peerConfig == null) { return null; } obj.serverid = obj.parent.config.peers.serverId; - if (obj.serverid == null) { obj.serverid = require("os").hostname(); } + if (obj.serverid == null) { obj.serverid = require("os").hostname().toLowerCase(); } if (obj.parent.config.peers.servers[obj.serverid] == null) { console.log("Error: Unable to peer with other servers, \"" + obj.serverid + "\" not present in peer servers list."); return null; } // Return the private key of a peer server diff --git a/package.json b/package.json index 5089e951..16a9198e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.1.1-u", + "version": "0.1.1-v", "keywords": [ "Remote Management", "Intel AMT", diff --git a/views/default.handlebars b/views/default.handlebars index 1828b51d..68c01aaa 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -195,6 +195,14 @@
 
   
+ Show +
 
@@ -815,7 +823,7 @@ updateUsers(); if (xxcurrentView == 4) go(1); } - meshserver.Send({ action: 'events' }); + meshserver.Send({ action: 'events', limit: parseInt(p3limitdropdown.value) }); QV('p2deleteall', userinfo.siteadmin == 0xFFFFFFFF); } @@ -968,7 +976,12 @@ break; } case 'event': { - if (!message.event.nolog) { events.unshift(message.event); events_update(); } + if (!message.event.nolog) { + events.unshift(message.event); + var eventLimit = parseInt(p3limitdropdown.value); + while (events.length > eventLimit) { events.pop(); } // Remove element(s) at the end + events_update(); + } switch (message.event.action) { case 'accountcreate': case 'accountchange': { @@ -4351,6 +4364,9 @@ meshserver.Send({ action: 'clearevents' }); } + function refreshEvents() { + meshserver.Send({ action: 'events', limit: parseInt(p3limitdropdown.value) }); + } // // MY USERS diff --git a/webserver.js b/webserver.js index ed636141..f2666be7 100644 --- a/webserver.js +++ b/webserver.js @@ -680,7 +680,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate if (obj.args.nousers == true) { features += 4; } // Single user mode if (domain.userQuota == -1) { features += 8; } // No server files mode if (obj.args.tlsoffload == true) { features += 16; } // No mutual-auth CIRA - if ((parent.config != null) && (parent.config.settings != null) && (parent.config.settings.allowFraming == true)) { features += 32; } // Allow site within iframe + if ((parent.config != null) && (parent.config.settings != null) && (parent.config.settings.allowframing == true)) { features += 32; } // Allow site within iframe if ((!obj.args.user) && (obj.args.nousers != true) && (nologout == false)) { logoutcontrol += ' Logout'; } // If a default user is in use or no user mode, don't display the logout button res.render(obj.path.join(__dirname, 'views/default'), { viewmode: viewmode, currentNode: currentNode, logoutControl: logoutcontrol, title: domain.title, title2: domain.title2, domainurl: domain.url, domain: domain.id, debuglevel: parent.debugLevel, serverDnsName: getWebServerName(domain), serverRedirPort: args.redirport, serverPublicPort: args.port, noServerBackup: (args.noserverbackup == 1 ? 1 : 0), features: features, mpspass: args.mpspass, webcerthash: obj.webCertificateHashBase64 }); } else { @@ -688,7 +688,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate var loginmode = req.session.loginmode; delete req.session.loginmode; // Clear this state, if the user hits refresh, we want to go back to the login page. var features = 0; - if ((parent.config != null) && (parent.config.settings != null) && (parent.config.settings.allowFraming == true)) { features += 32; } // Allow site within iframe + if ((parent.config != null) && (parent.config.settings != null) && (parent.config.settings.allowframing == true)) { features += 32; } // Allow site within iframe res.render(obj.path.join(__dirname, 'views/login'), { loginmode: loginmode, rootCertLink: getRootCertLink(), title: domain.title, title2: domain.title2, newAccount: domain.newaccounts, newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1), serverDnsName: getWebServerName(domain), serverPublicPort: obj.args.port, emailcheck: obj.parent.mailserver != null, features: features }); } }