diff --git a/db.js b/db.js index a8cfe293..725d2da0 100644 --- a/db.js +++ b/db.js @@ -41,6 +41,13 @@ module.exports.CreateDB = function (parent) { var dbcollection = 'meshcentral'; if (obj.parent.args.mongodbcol) { dbcollection = obj.parent.args.mongodbcol; } obj.file = db.collection(dbcollection); + + // Setup MongoDB indexes + obj.file.createIndex({ type: 1, domain: 1, meshid: 1 }, { sparse: 1 }); // Speeds up GetAllTypeNoTypeField() and GetAllTypeNoTypeFieldMeshFiltered() + obj.file.createIndex({ email: 1 }, { sparse: 1 }); // Speeds up GetUserWithEmail() and GetUserWithVerifiedEmail() + obj.file.createIndex({ ids: 1, time: -1 }, { sparse: 1 }); // Speeds up GetEvents() and GetEventsWithLimit() + obj.file.createIndex({ type: 1, node: 1, time: -1 }, { sparse: 1 }); // Speeds up getPowerTimeline() + obj.file.createIndex({ mesh: 1 }, { sparse: 1 }); // Speeds up RemoveMesh() } else { // Use NeDB (The default) obj.databaseType = 1; @@ -72,6 +79,13 @@ module.exports.CreateDB = function (parent) { // Start NeDB obj.file = new Datastore(datastoreOptions); obj.file.persistence.setAutocompactionInterval(3600); + + // Setup NeDB indexes + obj.file.ensureIndex({ fieldName: 'type' }); + obj.file.ensureIndex({ fieldName: 'domain' }); + obj.file.ensureIndex({ fieldName: 'meshid' }); + obj.file.ensureIndex({ fieldName: 'node' }); + obj.file.ensureIndex({ fieldName: 'email' }); } obj.SetupDatabase = function (func) { @@ -232,27 +246,30 @@ module.exports.CreateDB = function (parent) { } catch (ex) { return null; } } - // Get the number of records in the database for various types, this is the slow NeDB way. TODO: MongoDB can use group() to do this faster. + // Get the number of records in the database for various types, this is the slow NeDB way. + // 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. obj.getStats = function (func) { - obj.file.count({ type: 'node' }, function (err, nodeCount) { - obj.file.count({ type: 'mesh' }, function (err, meshCount) { - obj.file.count({ type: 'power' }, function (err, powerCount) { - obj.file.count({ type: 'user' }, function (err, userCount) { - obj.file.count({ type: 'ifinfo' }, function (err, nodeInterfaceCount) { - obj.file.count({ type: 'note' }, function (err, noteCount) { - obj.file.count({ type: 'smbios' }, function (err, nodeSmbiosCount) { - obj.file.count({ type: 'lastconnect' }, function (err, nodeLastConnectCount) { - obj.file.count({ }, function (err, totalCount) { - func({ nodes: nodeCount, meshes: meshCount, powerEvents: powerCount, users: userCount, nodeInterfaces: nodeInterfaceCount, notes: noteCount, connectEvent: nodeLastConnectCount, smbios: nodeSmbiosCount, total: totalCount }); - }); - }); - }); + if (obj.databaseType == 2) { + // MongoDB version + obj.file.aggregate([{ "$group": { _id: "$type", count: { $sum: 1 } } }], function (err, docs) { + var counters = {}, totalCount = 0; + for (var i in docs) { if (docs[i]._id != null) { counters[docs[i]._id] = docs[i].count; totalCount += docs[i].count; } } + func({ nodes: counters['node'], meshes: counters['mesh'], powerEvents: counters['power'], users: counters['user'], total: totalCount }); + }) + } else { + // NeDB version + obj.file.count({ type: 'node' }, function (err, nodeCount) { + obj.file.count({ type: 'mesh' }, function (err, meshCount) { + obj.file.count({ type: 'power' }, function (err, powerCount) { + obj.file.count({ type: 'user' }, function (err, userCount) { + obj.file.count({}, function (err, totalCount) { + func({ nodes: nodeCount, meshes: meshCount, powerEvents: powerCount, users: userCount, nodeInterfaces: nodeInterfaceCount, notes: noteCount, connectEvent: nodeLastConnectCount, smbios: nodeSmbiosCount, total: totalCount }); }); }); }); }); }); - }); + } } // 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. diff --git a/meshuser.js b/meshuser.js index 5419190f..7e22a650 100644 --- a/meshuser.js +++ b/meshuser.js @@ -178,15 +178,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use // Send current server statistics obj.SendServerStats = function () { - obj.db.getStats(function (data) { - var os = require('os'); - var stats = { action: 'serverstats', totalmem: os.totalmem(), freemem: os.freemem() }; - if (obj.parent.parent.platform != 'win32') { stats.cpuavg = os.loadavg(); } // else { stats.cpuavg = [ 0.2435345, 0.523234234, 0.6435345345 ]; } - var serverStats = { "User Accounts": Object.keys(obj.parent.users).length, "Device Groups": Object.keys(obj.parent.meshes).length, "Connected Agents": Object.keys(obj.parent.wsagents).length, "Connected Users": Object.keys(obj.parent.wssessions2).length }; - if (obj.parent.parent.mpsserver != null) { serverStats['Connected Intel® AMT'] = Object.keys(obj.parent.parent.mpsserver.ciraConnections).length; } - stats.values = { "Server State": serverStats, "Database": { "Records": data.total, "Users": data.users, "Device Groups": data.meshes, "Devices": data.nodes, "Device NetInfo": data.nodeInterfaces, "Device Power Event": data.powerEvents, "Notes": data.notes, "Connection Records": data.connectEvents, "SMBios": data.smbios } } - try { ws.send(JSON.stringify(stats)); } catch (ex) { } - }); + var os = require('os'); + var stats = { action: 'serverstats', totalmem: os.totalmem(), freemem: os.freemem() }; + if (obj.parent.parent.platform != 'win32') { stats.cpuavg = os.loadavg(); } // else { stats.cpuavg = [ 0.2435345, 0.523234234, 0.6435345345 ]; } + var serverStats = { "User Accounts": Object.keys(obj.parent.users).length, "Device Groups": Object.keys(obj.parent.meshes).length, "Connected Agents": Object.keys(obj.parent.wsagents).length, "Connected Users": Object.keys(obj.parent.wssessions2).length }; + if (obj.parent.parent.mpsserver != null) { serverStats['Connected Intel® AMT'] = Object.keys(obj.parent.parent.mpsserver.ciraConnections).length; } + stats.values = { "Server State": serverStats } + try { ws.send(JSON.stringify(stats)); } catch (ex) { } } // When data is received from the web socket @@ -256,7 +254,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } case 'serverstats': { - if ((user.siteadmin) != 0) { + if ((user.siteadmin & 21) != 0) { // Only site administrators with "site backup" or "site restore" or "site update" permissions can use this. if (obj.common.validateInt(command.interval, 1000, 1000000) == false) { // Clear the timer if (obj.serverStatsTimer != null) { clearInterval(obj.serverStatsTimer); obj.serverStatsTimer = null; } @@ -1438,38 +1436,65 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } case 'uploadagentcore': { - if (user.siteadmin != 0xFFFFFFFF) break; if (obj.common.validateString(command.nodeid, 1, 1024) == false) break; // Check nodeid if (obj.common.validateString(command.type, 1, 40) == false) break; // Check path - if (command.type == 'default') { - // Send the default core to the agent - obj.parent.parent.updateMeshCore(function () { obj.parent.sendMeshAgentCore(user, domain, command.nodeid, 'default'); }); - } else if (command.type == 'clear') { - // Clear the mesh agent core on the mesh agent - obj.parent.sendMeshAgentCore(user, domain, command.nodeid, 'clear'); - } else if (command.type == 'recovery') { - // Send the recovery core to the agent - obj.parent.sendMeshAgentCore(user, domain, command.nodeid, 'recovery'); - } else if ((command.type == 'custom') && (obj.common.validateString(command.path, 1, 2048) == true)) { - // Send a mesh agent core to the mesh agent - var file = obj.parent.getServerFilePath(user, domain, command.path); - if (file != null) { - obj.parent.parent.fs.readFile(file.fullpath, 'utf8', function (err, data) { - if (err != null) { - data = obj.common.IntToStr(0) + data; // Add the 4 bytes encoding type & flags (Set to 0 for raw) - obj.parent.sendMeshAgentCore(user, domain, command.nodeid, 'custom', data); + + // Change the device + obj.db.Get(command.nodeid, function (err, nodes) { + if (nodes.length != 1) return; + var node = nodes[0]; + + // Get the mesh for this device + mesh = obj.parent.meshes[node.meshid]; + if (mesh) { + // Check if this user has rights to do this + if (mesh.links[user._id] == null || (((mesh.links[user._id].rights & 16) == 0) && (user.siteadmin != 0xFFFFFFFF))) { return; } + + if (command.type == 'default') { + // Send the default core to the agent + obj.parent.parent.updateMeshCore(function () { obj.parent.sendMeshAgentCore(user, domain, command.nodeid, 'default'); }); + } else if (command.type == 'clear') { + // Clear the mesh agent core on the mesh agent + obj.parent.sendMeshAgentCore(user, domain, command.nodeid, 'clear'); + } else if (command.type == 'recovery') { + // Send the recovery core to the agent + obj.parent.sendMeshAgentCore(user, domain, command.nodeid, 'recovery'); + } else if ((command.type == 'custom') && (obj.common.validateString(command.path, 1, 2048) == true)) { + // Send a mesh agent core to the mesh agent + var file = obj.parent.getServerFilePath(user, domain, command.path); + if (file != null) { + obj.parent.parent.fs.readFile(file.fullpath, 'utf8', function (err, data) { + if (err != null) { + data = obj.common.IntToStr(0) + data; // Add the 4 bytes encoding type & flags (Set to 0 for raw) + obj.parent.sendMeshAgentCore(user, domain, command.nodeid, 'custom', data); + } + }); } - }); + } } - } + }); break; } case 'agentdisconnect': { - // Force mesh agent disconnection if (obj.common.validateString(command.nodeid, 1, 1024) == false) break; // Check nodeid - if (obj.common.validateInt(command.disconnectMode) == false) break; // Check disconnect mode - obj.parent.forceMeshAgentDisconnect(user, domain, command.nodeid, command.disconnectMode); + if (obj.common.validateInt(command.disconnectMode) == false) return; // Check disconnect mode + + // Change the device + obj.db.Get(command.nodeid, function (err, nodes) { + if (nodes.length != 1) return; + var node = nodes[0]; + + // Get the mesh for this device + mesh = obj.parent.meshes[node.meshid]; + if (mesh) { + // Check if this user has rights to do this + if (mesh.links[user._id] == null || (((mesh.links[user._id].rights & 16) == 0) && (user.siteadmin != 0xFFFFFFFF))) return; + + // Force mesh agent disconnection + obj.parent.forceMeshAgentDisconnect(user, domain, command.nodeid, command.disconnectMode); + } + }); break; } case 'close': diff --git a/package.json b/package.json index cd6c7e71..dfd38774 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.2.8-m", + "version": "0.2.8-o", "keywords": [ "Remote Management", "Intel AMT", diff --git a/views/default-min.handlebars b/views/default-min.handlebars index d98f98eb..a3e900f1 100644 --- a/views/default-min.handlebars +++ b/views/default-min.handlebars @@ -1 +1 @@ - MeshCentral
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file + MeshCentral
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file diff --git a/views/default.handlebars b/views/default.handlebars index 9c698ea1..1e9199d1 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -3539,7 +3539,7 @@ QV('MainDevFiles', ((mesh.mtype == 2) && ((node.agent == null) || (node.agent.caps == null) || ((node.agent.caps & 4) != 0))) && (meshrights & 8)); QV('MainDevAmt', (node.intelamt != null) && ((node.intelamt.state == 2) || (node.conn & 2)) && (meshrights & 8)); QV('MainDevConsole', (consoleRights && (mesh.mtype == 2) && ((node.agent == null) || (node.agent.caps == null) || ((node.agent.caps & 8) != 0))) && (meshrights & 8)); - QV('p15uploadCore', (node.agent != null) && (node.agent.caps != null) && ((node.agent.caps & 16) != 0) && (userinfo.siteadmin == 0xFFFFFFFF)); + QV('p15uploadCore', (node.agent != null) && (node.agent.caps != null) && ((node.agent.caps & 16) != 0)); QH('p15coreName', ((node.agent != null) && (node.agent.core != null))?node.agent.core:''); // Setup/Refresh Intel AMT tab diff --git a/webserver.js b/webserver.js index a8a9f579..d5b69fc2 100644 --- a/webserver.js +++ b/webserver.js @@ -2411,7 +2411,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Check we have agent rights var rights = user.links[agent.dbMeshKey].rights; - if ((rights != null) && ((rights & MESHRIGHT_AGENTCONSOLE) != 0) && (user.siteadmin == 0xFFFFFFFF)) { agent.close(disconnectMode); } + if ((rights != null) && ((rights & MESHRIGHT_AGENTCONSOLE) != 0) || (user.siteadmin == 0xFFFFFFFF)) { agent.close(disconnectMode); } }; // Send the core module to the mesh agent @@ -2426,7 +2426,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Check we have agent rights var rights = user.links[agent.dbMeshKey].rights; - if ((rights != null) && ((rights & MESHRIGHT_AGENTCONSOLE) != 0) && (user.siteadmin == 0xFFFFFFFF)) { + if ((rights != null) && ((rights & MESHRIGHT_AGENTCONSOLE) != 0) || (user.siteadmin == 0xFFFFFFFF)) { if (coretype == 'clear') { // Clear the mesh agent core agent.agentCoreCheck = 1000; // Tell the agent object we are using a custom core.