Event limit, server improvements

Ylian Saint-Hilaire committed Jan 4, 2018 at 15:59 UTC 348065fec309a80fabadff8cf20fa49d7194e8e4
8 files changed +77 -19
common.js
+9
@@ -117,3 +117,12 @@ module.exports.ComputeDigesthash = function (username, password, realm, method,
117 module.exports.toNumber = function (str) { var x = parseInt(str); if (x == str) return x; return str; }
118 module.exports.escapeHtml = function (string) { return String(string).replace(/[&<>"'`=\/]/g, function (s) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;', '/': '&#x2F;', '`': '&#x60;', '=': '&#x3D;' }[s]; }); }
119 module.exports.escapeHtmlBreaks = function (string) { return String(string).replace(/[&<>"'`=\/]/g, function (s) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;', '/': '&#x2F;', '`': '&#x60;', '=': '&#x3D;', '\r': '<br />', '\n': '' }[s]; }); }
120 +
121 +// Lowercase all the names in a object recursively
122 +module.exports.objKeysToLower = function (obj) {
123 + for (var i in obj) {
124 + if (i.toLowerCase() !== i) { obj[i.toLowerCase()] = obj[i]; delete obj[i]; } // LowerCase all key names
125 + if (typeof obj[i] == 'object') { module.exports.objKeysToLower(obj[i]); } // LowerCase all key names in the child object
126 + }
127 + return obj;
128 +}
\ No newline at end of file
db.js
+2 -1
@@ -96,7 +96,8 @@ module.exports.CreateDB = function (args, datapath) {
96 obj.RemoveAllOfType = function (type, func) { obj.file.remove({ type: type }, { multi: true }, func); }
97 obj.InsertMany = function (data, func) { obj.file.insert(data, func); }
98 obj.StoreEvent = function (ids, source, event) { obj.file.insert(event); }
99 - 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) } }
99 + 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) } }
100 + 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); } }
101 obj.RemoveMesh = function (id) { obj.file.remove({ mesh: id }, { multi: true }); obj.file.remove({ _id: id }); }
102 obj.RemoveAllEvents = function (domain) { obj.file.remove({ type: 'event', domain: domain }, { multi: true }); }
103 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]); } }); }
meshcentral.js
+22 -2
@@ -198,6 +198,7 @@ function CreateMeshCentralServer() {
198 // Set the command line arguments to the config file if they are not present
199 if (obj.config.settings) { for (var i in obj.config.settings) { if (obj.args[i] == null) obj.args[i] = obj.config.settings[i]; } }
200 }
201 + obj.common.objKeysToLower(obj.config); // Lower case all keys in the config file
202
203 // Read environment variables. For a subset of arguments, we allow them to be read from environment variables.
204 var xenv = ['user', 'port', 'mpsport', 'redirport', 'exactport', 'debug'];
@@ -211,7 +212,6 @@ function CreateMeshCentralServer() {
212 var bannedDomains = ['public', 'private', 'images', 'scripts', 'styles', 'views']; // List of banned domains
213 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; } } }
214 for (var i in obj.config.domains) {
214 - 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
215 if (obj.config.domains[i].dns == null) { obj.config.domains[i].url = (i == '') ? '/' : ('/' + i + '/'); } else { obj.config.domains[i].url = '/'; }
216 obj.config.domains[i].id = i;
217 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() {
385 obj.DispatchEvent(['*'], obj, { etype: 'server', action: 'started', msg: 'Server started' })
386
387 // Load the login cookie encryption key from the database if allowed
388 - if ((obj.config) && (obj.config.settings) && (obj.config.settings.allowLoginToken == true)) {
388 + if ((obj.config) && (obj.config.settings) && (obj.config.settings.allowlogintoken == true)) {
389 obj.db.Get('LoginCookieEncryptionKey', function (err, docs) {
390 if ((docs.length > 0) && (docs[0].key != null) && (obj.args.logintokengen == null)) {
391 obj.loginCookieEncryptionKey = Buffer.from(docs[0].key, 'hex');
@@ -697,6 +697,7 @@ function CreateMeshCentralServer() {
697 }
698
699 // Update the default mesh core
700 + obj.updateMeshCoreTimer = 'notset';
701 obj.updateMeshCore = function (func) {
702 // Figure out where meshcore.js is
703 var meshcorePath = obj.datapath;
@@ -731,9 +732,19 @@ function CreateMeshCentralServer() {
732 obj.defaultMeshCoreNoMei = obj.common.IntToStr(0) + moduleAdditionsNoMei + meshCore;
733 obj.defaultMeshCoreNoMeiHash = obj.crypto.createHash('sha384').update(obj.defaultMeshCoreNoMei).digest("binary");
734 if (func != null) { func(true); }
735 +
736 + // If meshcore.js is in the data folder, monitor the file for changes.
737 + if ((obj.updateMeshCoreTimer === 'notset') && (meshcorePath == obj.datapath)) {
738 + obj.updateMeshCoreTimer = null;
739 + obj.fs.watch(obj.path.join(meshcorePath, 'meshcore.js'), function (eventType, filename) {
740 + if (obj.updateMeshCoreTimer != null) { clearTimeout(obj.updateMeshCoreTimer); obj.updateMeshCoreTimer = null; }
741 + obj.updateMeshCoreTimer = setTimeout(function () { obj.updateMeshCore(); console.log('Updated meshcore.js.'); }, 5000);
742 + })
743 + }
744 }
745
746 // Update the default meshcmd
747 + obj.updateMeshCmdTimer = 'notset';
748 obj.updateMeshCmd = function (func) {
749 // Figure out where meshcmd.js is
750 var meshcmdPath = obj.datapath;
@@ -762,6 +773,15 @@ function CreateMeshCentralServer() {
773 // Set the new default meshcmd.js
774 obj.defaultMeshCmd = moduleAdditions + meshCmd;
775 if (func != null) { func(true); }
776 +
777 + // If meshcore.js is in the data folder, monitor the file for changes.
778 + if ((obj.updateMeshCmdTimer === 'notset') && (meshcmdPath == obj.datapath)) {
779 + obj.updateMeshCmdTimer = null;
780 + obj.fs.watch(obj.path.join(meshcmdPath, 'meshcmd.js'), function (eventType, filename) {
781 + if (obj.updateMeshCmdTimer != null) { clearTimeout(obj.updateMeshCmdTimer); obj.updateMeshCmdTimer = null; }
782 + obj.updateMeshCmdTimer = setTimeout(function () { obj.updateMeshCmd(); console.log('Updated meshcmd.js.'); }, 5000);
783 + })
784 + }
785 }
786
787 // List of possible mesh agent install scripts
meshuser.js
+22 -10
@@ -73,14 +73,21 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
73 // Request a list of all meshes this user as rights to
74 var docs = [];
75 for (var i in user.links) { if (obj.parent.meshes[i]) { docs.push(obj.parent.meshes[i]); } }
76 - ws.send(JSON.stringify({ action: 'meshes', meshes: docs }));
76 + ws.send(JSON.stringify({ action: 'meshes', meshes: docs, tag: command.tag }));
77 break;
78 }
79 case 'nodes':
80 {
81 - // Request a list of all meshes this user as rights to
81 var links = [];
83 - for (var i in user.links) { links.push(i); }
82 + if (command.meshid == null) {
83 + // Request a list of all meshes this user as rights to
84 + for (var i in user.links) { links.push(i); }
85 + } else {
86 + // Request list of all nodes for one specific meshid
87 + var meshid = command.meshid;
88 + if (meshid.split('/').length == 0) { meshid = 'mesh/' + domain.id + '/' + command.meshid; }
89 + if (user.links[meshid] != null) { links.push(meshid); }
90 + }
91
92 // Request a list of all nodes
93 obj.db.GetAllTypeNoTypeFieldMeshFiltered(links, domain.id, 'node', function (err, docs) {
@@ -105,7 +112,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
112
113 r[meshid].push(docs[i]);
114 }
108 - ws.send(JSON.stringify({ action: 'nodes', nodes: r }));
115 + ws.send(JSON.stringify({ action: 'nodes', nodes: r, tag: command.tag }));
116 });
117 break;
118 }
@@ -148,11 +155,11 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
155 }
156 }
157 }
151 - ws.send(JSON.stringify({ action: 'powertimeline', nodeid: command.nodeid, timeline: timeline }));
158 + ws.send(JSON.stringify({ action: 'powertimeline', nodeid: command.nodeid, timeline: timeline, tag: command.tag }));
159 } else {
160 // No records found, send current state if we have it
161 var state = obj.parent.parent.GetConnectivityState(command.nodeid);
155 - if (state != null) { ws.send(JSON.stringify({ action: 'powertimeline', nodeid: command.nodeid, timeline: [state.powerState, Date.now(), state.powerState] })); }
162 + if (state != null) { ws.send(JSON.stringify({ action: 'powertimeline', nodeid: command.nodeid, timeline: [state.powerState, Date.now(), state.powerState], tag: command.tag })); }
163 }
164 });
165 break;
@@ -223,8 +230,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
230 }
231 case 'events':
232 {
226 - // Send the list of events for this session
227 - obj.db.GetEvents(user.subscriptions, domain.id, function (err, docs) { if (err != null) return; ws.send(JSON.stringify({ action: 'events', events: docs })); });
233 + if ((command.limit == null) || (typeof command.limit != 'number')) {
234 + // Send the list of all events for this session
235 + 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 })); });
236 + } else {
237 + // Send the list of most recent events for this session, up to 'limit' count
238 + 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 })); });
239 + }
240 break;
241 }
242 case 'clearevents':
@@ -253,7 +265,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
265 docs.push(userinfo);
266 }
267 }
256 - ws.send(JSON.stringify({ action: 'users', users: docs }));
268 + ws.send(JSON.stringify({ action: 'users', users: docs, tag: command.tag }));
269 break;
270 }
271 case 'changeemail':
@@ -320,7 +332,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
332 // We have peer servers, use more complex session counting
333 for (var userid in obj.sessionsCount) { if (userid.split('/')[1] == domain.id) { wssessions[userid] = obj.sessionsCount[userid]; } }
334 }
323 - ws.send(JSON.stringify({ action: 'wssessioncount', wssessions: wssessions })); // wssessions is: userid --> count
335 + ws.send(JSON.stringify({ action: 'wssessioncount', wssessions: wssessions, tag: command.tag })); // wssessions is: userid --> count
336 break;
337 }
338 case 'deleteuser':
multiserver.js
+1 -1
@@ -370,7 +370,7 @@ module.exports.CreateMultiServer = function (parent, args) {
370 // If we have no peering configuration, don't setup this object
371 if (obj.peerConfig == null) { return null; }
372 obj.serverid = obj.parent.config.peers.serverId;
373 - if (obj.serverid == null) { obj.serverid = require("os").hostname(); }
373 + if (obj.serverid == null) { obj.serverid = require("os").hostname().toLowerCase(); }
374 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; }
375
376 // Return the private key of a peer server
package.json
+1 -1
@@ -1,6 +1,6 @@
1 {
2 "name": "meshcentral",
3 - "version": "0.1.1-u",
3 + "version": "0.1.1-v",
4 "keywords": [
5 "Remote Management",
6 "Intel AMT",
views/default.handlebars
+18 -2
@@ -195,6 +195,14 @@
195 <div class=h1 style=height:100%;float:left>&nbsp;</div>
196 <div class=style14 style=height:100%;float:left>&nbsp;&nbsp;<input id=p2deleteall type=button onclick=showDeleteAllEventsDialog() style=display:none value="Delete All..." />&nbsp;</div>
197 <div class="auto-style1" style="height:100%;float:right">
198 + Show
199 + <select id=p3limitdropdown onchange=refreshEvents()>
200 + <option value=60>Last 60</option>
201 + <option value=120>Last 120</option>
202 + <option value=250>Last 250</option>
203 + <option value=500>Last 500</option>
204 + <option value=1000>Last 1000</option>
205 + </select>
206 <div style="height:100%;width:20px;float:right;background-color:#ffffff"></div>
207 <div class="h2" style="height:100%;float:right;">&nbsp;</div>
208 </div>
@@ -815,7 +823,7 @@
823 updateUsers();
824 if (xxcurrentView == 4) go(1);
825 }
818 - meshserver.Send({ action: 'events' });
826 + meshserver.Send({ action: 'events', limit: parseInt(p3limitdropdown.value) });
827 QV('p2deleteall', userinfo.siteadmin == 0xFFFFFFFF);
828 }
829
@@ -968,7 +976,12 @@
976 break;
977 }
978 case 'event': {
971 - if (!message.event.nolog) { events.unshift(message.event); events_update(); }
979 + if (!message.event.nolog) {
980 + events.unshift(message.event);
981 + var eventLimit = parseInt(p3limitdropdown.value);
982 + while (events.length > eventLimit) { events.pop(); } // Remove element(s) at the end
983 + events_update();
984 + }
985 switch (message.event.action) {
986 case 'accountcreate':
987 case 'accountchange': {
@@ -4351,6 +4364,9 @@
4364 meshserver.Send({ action: 'clearevents' });
4365 }
4366
4367 + function refreshEvents() {
4368 + meshserver.Send({ action: 'events', limit: parseInt(p3limitdropdown.value) });
4369 + }
4370
4371 //
4372 // MY USERS
webserver.js
+2 -2
@@ -680,7 +680,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
680 if (obj.args.nousers == true) { features += 4; } // Single user mode
681 if (domain.userQuota == -1) { features += 8; } // No server files mode
682 if (obj.args.tlsoffload == true) { features += 16; } // No mutual-auth CIRA
683 - if ((parent.config != null) && (parent.config.settings != null) && (parent.config.settings.allowFraming == true)) { features += 32; } // Allow site within iframe
683 + if ((parent.config != null) && (parent.config.settings != null) && (parent.config.settings.allowframing == true)) { features += 32; } // Allow site within iframe
684 if ((!obj.args.user) && (obj.args.nousers != true) && (nologout == false)) { logoutcontrol += ' <a href=' + domain.url + 'logout?' + Math.random() + ' style=color:white>Logout</a>'; } // If a default user is in use or no user mode, don't display the logout button
685 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 });
686 } else {
@@ -688,7 +688,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
688 var loginmode = req.session.loginmode;
689 delete req.session.loginmode; // Clear this state, if the user hits refresh, we want to go back to the login page.
690 var features = 0;
691 - if ((parent.config != null) && (parent.config.settings != null) && (parent.config.settings.allowFraming == true)) { features += 32; } // Allow site within iframe
691 + if ((parent.config != null) && (parent.config.settings != null) && (parent.config.settings.allowframing == true)) { features += 32; } // Allow site within iframe
692 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 });
693 }
694 }