Partial support for device paging support on the server side, allows a user to view only a subset of the devices.

Ylian Saint-Hilaire committed Aug 31, 2022 at 11:28 UTC 15c882f24ed62fe7e21d8b67d8af98401899da07
2 files changed +142 -47
db.js
+30 -21
@@ -1329,20 +1329,21 @@ module.exports.CreateDB = function (parent, func) {
1329 func(err, performTypedRecordDecrypt(docs));
1330 });
1331 };
1332 - obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, func) {
1332 + obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, skip, limit, func) {
1333 + if (limit == 0) { limit = -1; } // In SQLite, no limit is -1
1334 if (id && (id != '')) {
1334 - sqlDbQuery('SELECT doc FROM main WHERE (id = $1) AND (type = $2) AND (domain = $3) AND (extra IN (' + dbMergeSqlArray(meshes) + '))', [id, type, domain], function (err, docs) {
1335 + sqlDbQuery('SELECT doc FROM main WHERE (id = $1) AND (type = $2) AND (domain = $3) AND (extra IN (' + dbMergeSqlArray(meshes) + ')) LIMIT $4 OFFSET $5', [id, type, domain, limit, skip], function (err, docs) {
1336 if (docs != null) { for (var i in docs) { delete docs[i].type; if (docs[i].links != null) { docs[i] = common.unEscapeLinksFieldName(docs[i]); } } }
1337 func(err, performTypedRecordDecrypt(docs));
1338 });
1339 } else {
1340 if (extrasids == null) {
1340 - sqlDbQuery('SELECT doc FROM main WHERE (type = $1) AND (domain = $2) AND (extra IN (' + dbMergeSqlArray(meshes) + '))', [type, domain], function (err, docs) {
1341 + sqlDbQuery('SELECT doc FROM main WHERE (type = $1) AND (domain = $2) AND (extra IN (' + dbMergeSqlArray(meshes) + ')) LIMIT $3 OFFSET $4', [type, domain, limit, skip], function (err, docs) {
1342 if (docs != null) { for (var i in docs) { delete docs[i].type; if (docs[i].links != null) { docs[i] = common.unEscapeLinksFieldName(docs[i]); } } }
1343 func(err, performTypedRecordDecrypt(docs));
1344 });
1345 } else {
1345 - sqlDbQuery('SELECT doc FROM main WHERE (type = $1) AND (domain = $2) AND ((extra IN (' + dbMergeSqlArray(meshes) + ')) OR (id IN (' + dbMergeSqlArray(extrasids) + ')))', [type, domain], function (err, docs) {
1346 + sqlDbQuery('SELECT doc FROM main WHERE (type = $1) AND (domain = $2) AND ((extra IN (' + dbMergeSqlArray(meshes) + ')) OR (id IN (' + dbMergeSqlArray(extrasids) + '))) LIMIT $3 OFFSET $4', [type, domain, limit, skip], function (err, docs) {
1347 if (docs != null) { for (var i in docs) { delete docs[i].type; if (docs[i].links != null) { docs[i] = common.unEscapeLinksFieldName(docs[i]); } } }
1348 func(err, performTypedRecordDecrypt(docs));
1349 });
@@ -1550,7 +1551,7 @@ module.exports.CreateDB = function (parent, func) {
1551 func(null, common.aceUnEscapeAllFieldNames(docs));
1552 });
1553 }
1553 - obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, func) {
1554 + obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, skip, limit, func) {
1555 if (meshes.length == 0) { func(null, []); return; }
1556 var query = obj.file.query('meshcentral').take(999999).filter('type', '==', type).filter('domain', '==', domain);
1557 if (id) { query = query.filter('_id', '==', id); }
@@ -1780,14 +1781,15 @@ module.exports.CreateDB = function (parent, func) {
1781 obj.GetAll = function (func) { sqlDbQuery('SELECT domain, doc FROM main', null, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); }
1782 obj.GetHash = function (id, func) { sqlDbQuery('SELECT doc FROM main WHERE id = $1', [id], function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); }
1783 obj.GetAllTypeNoTypeField = function (type, domain, func) { sqlDbQuery('SELECT doc FROM main WHERE type = $1 AND domain = $2', [type, domain], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, performTypedRecordDecrypt(docs)); }); };
1783 - obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, func) {
1784 + obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, skip, limit, func) {
1785 + if (limit == 0) { limit = 0xFFFFFFFF; }
1786 if (id && (id != '')) {
1785 - sqlDbQuery('SELECT doc FROM main WHERE (id = $1) AND (type = $2) AND (domain = $3) AND (extra = ANY ($4))', [id, type, domain, meshes], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, performTypedRecordDecrypt(docs)); });
1787 + sqlDbQuery('SELECT doc FROM main WHERE (id = $1) AND (type = $2) AND (domain = $3) AND (extra = ANY ($4)) LIMIT $5 OFFSET $6', [id, type, domain, meshes, limit, skip], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, performTypedRecordDecrypt(docs)); });
1788 } else {
1789 if (extrasids == null) {
1788 - sqlDbQuery('SELECT doc FROM main WHERE (type = $1) AND (domain = $2) AND (extra = ANY ($3))', [type, domain, meshes], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, performTypedRecordDecrypt(docs)); }, true);
1790 + sqlDbQuery('SELECT doc FROM main WHERE (type = $1) AND (domain = $2) AND (extra = ANY ($3)) LIMIT $4 OFFSET $5', [type, domain, meshes, limit, skip], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, performTypedRecordDecrypt(docs)); }, true);
1791 } else {
1790 - sqlDbQuery('SELECT doc FROM main WHERE (type = $1) AND (domain = $2) AND ((extra = ANY ($3)) OR (id = ANY ($4)))', [type, domain, meshes, extrasids], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, performTypedRecordDecrypt(docs)); });
1792 + sqlDbQuery('SELECT doc FROM main WHERE (type = $1) AND (domain = $2) AND ((extra = ANY ($3)) OR (id = ANY ($4))) LIMIT $5 OFFSET $6', [type, domain, meshes, extrasids, limit, skip], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, performTypedRecordDecrypt(docs)); });
1793 }
1794 }
1795 };
@@ -1947,13 +1949,14 @@ module.exports.CreateDB = function (parent, func) {
1949 obj.GetAll = function (func) { sqlDbQuery('SELECT domain, doc FROM main', null, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); }
1950 obj.GetHash = function (id, func) { sqlDbQuery('SELECT doc FROM main WHERE id = ?', [id], function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); }
1951 obj.GetAllTypeNoTypeField = function (type, domain, func) { sqlDbQuery('SELECT doc FROM main WHERE type = ? AND domain = ?', [type, domain], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, performTypedRecordDecrypt(docs)); }); };
1950 - obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, func) {
1952 + obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, skip, limit, func) {
1953 + if (limit == 0) { limit = 0xFFFFFFFF; }
1954 if ((meshes == null) || (meshes.length == 0)) { meshes = ''; } // MySQL can't handle a query with IN() on an empty array, we have to use an empty string instead.
1955 if ((extrasids == null) || (extrasids.length == 0)) { extrasids = ''; } // MySQL can't handle a query with IN() on an empty array, we have to use an empty string instead.
1956 if (id && (id != '')) {
1954 - sqlDbQuery('SELECT doc FROM 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, performTypedRecordDecrypt(docs)); });
1957 + sqlDbQuery('SELECT doc FROM main WHERE id = ? AND type = ? AND domain = ? AND extra IN (?) LIMIT ? OFFSET ?', [id, type, domain, meshes, limit, skip], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, performTypedRecordDecrypt(docs)); });
1958 } else {
1956 - sqlDbQuery('SELECT doc FROM 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, performTypedRecordDecrypt(docs)); });
1959 + sqlDbQuery('SELECT doc FROM main WHERE type = ? AND domain = ? AND (extra IN (?) OR id IN (?)) LIMIT ? OFFSET ?', [type, domain, meshes, extrasids, limit, skip], function (err, docs) { if (err == null) { for (var i in docs) { delete docs[i].type } } func(err, performTypedRecordDecrypt(docs)); });
1960 }
1961 };
1962 obj.GetAllTypeNodeFiltered = function (nodes, domain, type, id, func) {
@@ -2177,15 +2180,21 @@ module.exports.CreateDB = function (parent, func) {
2180 obj.GetAll = function (func) { obj.file.find({}).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
2181 obj.GetHash = function (id, func) { obj.file.find({ _id: id }).project({ _id: 0, hash: 1 }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
2182 obj.GetAllTypeNoTypeField = function (type, domain, func) { obj.file.find({ type: type, domain: domain }).project({ type: 0 }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
2180 - obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, func) {
2183 + obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, skip, limit, func) {
2184 if (extrasids == null) {
2182 - var x = { type: type, domain: domain, meshid: { $in: meshes } };
2185 + const x = { type: type, domain: domain, meshid: { $in: meshes } };
2186 if (id) { x._id = id; }
2184 - obj.file.find(x, { type: 0 }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
2187 + var f = obj.file.find(x, { type: 0 });
2188 + if (skip > 0) f = f.skip(skip); // Skip records
2189 + if (limit > 0) f = f.limit(limit); // Limit records
2190 + f.toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
2191 } else {
2186 - var x = { type: type, domain: domain, $or: [ { meshid: { $in: meshes } }, { _id: { $in: extrasids } } ] };
2192 + const x = { type: type, domain: domain, $or: [ { meshid: { $in: meshes } }, { _id: { $in: extrasids } } ] };
2193 if (id) { x._id = id; }
2188 - obj.file.find(x, { type: 0 }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
2194 + var f = obj.file.find(x, { type: 0 });
2195 + if (skip > 0) f = f.skip(skip); // Skip records
2196 + if (limit > 0) f = f.limit(limit); // Limit records
2197 + f.toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
2198 }
2199 };
2200 obj.GetAllTypeNodeFiltered = function (nodes, domain, type, id, func) {
@@ -2409,18 +2418,18 @@ module.exports.CreateDB = function (parent, func) {
2418 obj.GetAll = function (func) { obj.file.find({}, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
2419 obj.GetHash = function (id, func) { obj.file.find({ _id: id }, { _id: 0, hash: 1 }, func); };
2420 obj.GetAllTypeNoTypeField = function (type, domain, func) { obj.file.find({ type: type, domain: domain }, { type: 0 }, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
2412 - //obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, domain, type, id, func) {
2421 + //obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, domain, type, id, skip, limit, func) {
2422 //var x = { type: type, domain: domain, meshid: { $in: meshes } };
2423 //if (id) { x._id = id; }
2424 //obj.file.find(x, { type: 0 }, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
2425 //};
2417 - obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, func) {
2426 + obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, skip, limit, func) {
2427 if (extrasids == null) {
2419 - var x = { type: type, domain: domain, meshid: { $in: meshes } };
2428 + const x = { type: type, domain: domain, meshid: { $in: meshes } };
2429 if (id) { x._id = id; }
2430 obj.file.find(x, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
2431 } else {
2423 - var x = { type: type, domain: domain, $or: [{ meshid: { $in: meshes } }, { _id: { $in: extrasids } }] };
2432 + const x = { type: type, domain: domain, $or: [{ meshid: { $in: meshes } }, { _id: { $in: extrasids } }] };
2433 if (id) { x._id = id; }
2434 obj.file.find(x, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
2435 }
meshuser.js
+112 -26
@@ -105,6 +105,11 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
105 obj.domain = domain;
106 obj.ws = ws;
107
108 + // Information related to the current page the user is looking at
109 + obj.deviceSkip = 0; // How many devices to skip
110 + obj.deviceLimit = 0; // How many devices to view
111 + obj.visibleDevices = null; // An object of visible nodeid's if the user is in paging mode
112 +
113 // Check if we are a cross-domain administrator
114 if (parent.parent.config.settings.managecrossdomain && (parent.parent.config.settings.managecrossdomain.indexOf(user._id) >= 0)) { obj.crossDomain = true; }
115
@@ -414,6 +419,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
419 // If this session is logged in using a loginToken and the token is removed, disconnect.
420 if ((req.session.loginToken != null) && (typeof event == 'object') && (event.action == 'loginTokenChanged') && (event.removed != null) && (event.removed.indexOf(req.session.loginToken) >= 0)) { delete req.session; obj.close(); return; }
421
422 + // If this user is not viewing all devices and paging, check if this event is in the current page
423 + if (isEventWithinPage(ids) == false) return;
424 +
425 // Normally, only allow this user to receive messages from it's own domain.
426 // If the user is a cross domain administrator, allow some select messages from different domains.
427 if ((event.domain == null) || (event.domain == domain.id) || ((obj.crossDomain === true) && (allowedCrossDomainMessages.indexOf(event.action) >= 0))) {
@@ -706,7 +714,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
714 }
715
716 // Request a list of all nodes
709 - db.GetAllTypeNoTypeFieldMeshFiltered(links, extraids, domain.id, 'node', command.id, function (err, docs) {
717 + db.GetAllTypeNoTypeFieldMeshFiltered(links, extraids, domain.id, 'node', command.id, obj.deviceSkip, obj.deviceLimit, function (err, docs) {
718
719 //console.log(err, docs, links, extraids, domain.id, 'node', command.id);
720
@@ -714,10 +722,14 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
722 parent.common.unEscapeAllLinksFieldName(docs);
723
724 var r = {};
725 + if (obj.visibleDevices != null) { obj.visibleDevices = {}; }
726 for (i in docs) {
727 // Check device links, if a link points to an unknown user, remove it.
728 parent.cleanDevice(docs[i]);
729
730 + // If we are paging, add the device to the page here
731 + if (obj.visibleDevices != null) { obj.visibleDevices[docs[i]._id] = 1; }
732 +
733 // Remove any connectivity and power state information, that should not be in the database anyway.
734 // TODO: Find why these are sometimes saved in the db.
735 if (docs[i].conn != null) { delete docs[i].conn; }
@@ -788,7 +800,14 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
800
801 r[meshid].push(docs[i]);
802 }
791 - try { ws.send(JSON.stringify({ action: 'nodes', responseid: command.responseid, nodes: r, tag: command.tag })); } catch (ex) { }
803 + const response = { action: 'nodes', responseid: command.responseid, nodes: r, tag: command.tag };
804 + if (obj.visibleDevices != null) {
805 + // If in paging mode, report back the skip and limit values
806 + response.skip = obj.deviceSkip;
807 + response.limit = obj.deviceLimit;
808 + // TODO: Add total device count
809 + }
810 + try { ws.send(JSON.stringify(response)); } catch (ex) { }
811 });
812 break;
813 }
@@ -6128,14 +6147,30 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
6147 }
6148
6149 function serverCommandLastConnects(command) {
6131 - const links = parent.GetAllMeshIdWithRights(user);
6132 - const extraids = getUserExtraIds();
6133 - db.GetAllTypeNoTypeFieldMeshFiltered(links, extraids, domain.id, 'node', null, function (err, docs) {
6134 - if (docs == null) return;
6150 + if (obj.visibleDevices == null) {
6151 + // If we are not paging, get all devices visible to this user
6152 + const links = parent.GetAllMeshIdWithRights(user);
6153 + const extraids = getUserExtraIds();
6154 + db.GetAllTypeNoTypeFieldMeshFiltered(links, extraids, domain.id, 'node', null, obj.deviceSkip, obj.deviceLimit, function (err, docs) {
6155 + if (docs == null) return;
6156 +
6157 + // Create a list of node ids for this user and query them for last device connection time
6158 + const ids = []
6159 + for (var i in docs) { ids.push('lc' + docs[i]._id); }
6160
6161 + // Pull list of last connections only for device owned by this user
6162 + db.GetAllIdsOfType(ids, domain.id, 'lastconnect', function (err, docs) {
6163 + if (docs == null) return;
6164 + const response = {};
6165 + for (var j in docs) { response[docs[j]._id.substring(2)] = docs[j].time; }
6166 + obj.send({ action: 'lastconnects', lastconnects: response, tag: command.tag });
6167 + });
6168 + });
6169 + } else {
6170 + // If we are paging, we know what devices the user is look at
6171 // Create a list of node ids for this user and query them for last device connection time
6172 const ids = []
6138 - for (var i in docs) { ids.push('lc' + docs[i]._id); }
6173 + for (var i in obj.visibleDevices) { ids.push('lc' + i); }
6174
6175 // Pull list of last connections only for device owned by this user
6176 db.GetAllIdsOfType(ids, domain.id, 'lastconnect', function (err, docs) {
@@ -6144,7 +6179,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
6179 for (var j in docs) { response[docs[j]._id.substring(2)] = docs[j].time; }
6180 obj.send({ action: 'lastconnects', lastconnects: response, tag: command.tag });
6181 });
6147 - });
6182 + }
6183 }
6184
6185 function serverCommandLoginCookie(command) {
@@ -7505,22 +7540,62 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
7540
7541 // Return detailed information about all nodes this user has access to
7542 function getAllDeviceDetailedInfo(type, func) {
7508 - // Get all device groups this user has access to
7509 - var links = parent.GetAllMeshIdWithRights(user);
7510 -
7511 - // Add any nodes with direct rights or any nodes with user group direct rights
7512 - var extraids = getUserExtraIds();
7513 -
7514 - // Request a list of all nodes
7515 - db.GetAllTypeNoTypeFieldMeshFiltered(links, extraids, domain.id, 'node', null, function (err, docs) {
7516 - if (docs == null) { docs = []; }
7517 - parent.common.unEscapeAllLinksFieldName(docs);
7518 -
7519 - var results = [], resultPendingCount = 0;
7520 - for (i in docs) {
7521 - // Check device links, if a link points to an unknown user, remove it.
7522 - parent.cleanDevice(docs[i]);
7543 + // If we are not paging, get all devices visible to this user
7544 + if (obj.visibleDevices == null) {
7545 +
7546 + // Get all device groups this user has access to
7547 + var links = parent.GetAllMeshIdWithRights(user);
7548 +
7549 + // Add any nodes with direct rights or any nodes with user group direct rights
7550 + var extraids = getUserExtraIds();
7551 +
7552 + // Request a list of all nodes
7553 + db.GetAllTypeNoTypeFieldMeshFiltered(links, extraids, domain.id, 'node', null, obj.deviceSkip, obj.deviceLimit, function (err, docs) {
7554 + if (docs == null) { docs = []; }
7555 + parent.common.unEscapeAllLinksFieldName(docs);
7556 +
7557 + var results = [], resultPendingCount = 0;
7558 + for (i in docs) {
7559 + // Check device links, if a link points to an unknown user, remove it.
7560 + parent.cleanDevice(docs[i]);
7561 +
7562 + // Fetch the node from the database
7563 + resultPendingCount++;
7564 + const getNodeFunc = function (node, rights, visible) {
7565 + if ((node != null) && (visible == true)) {
7566 + const getNodeSysInfoFunc = function (err, docs) {
7567 + const getNodeNetInfoFunc = function (err, docs) {
7568 + var netinfo = null;
7569 + if ((err == null) && (docs != null) && (docs.length == 1)) { netinfo = docs[0]; }
7570 + resultPendingCount--;
7571 + getNodeNetInfoFunc.results.push({ node: parent.CloneSafeNode(getNodeNetInfoFunc.node), sys: getNodeNetInfoFunc.sysinfo, net: netinfo });
7572 + if (resultPendingCount == 0) { func(getNodeFunc.results, type); }
7573 + }
7574 + getNodeNetInfoFunc.results = getNodeSysInfoFunc.results;
7575 + getNodeNetInfoFunc.nodeid = getNodeSysInfoFunc.nodeid;
7576 + getNodeNetInfoFunc.node = getNodeSysInfoFunc.node;
7577 + if ((err == null) && (docs != null) && (docs.length == 1)) { getNodeNetInfoFunc.sysinfo = docs[0]; }
7578
7579 + // Query the database for network information
7580 + db.Get('if' + getNodeSysInfoFunc.nodeid, getNodeNetInfoFunc);
7581 + }
7582 + getNodeSysInfoFunc.results = getNodeFunc.results;
7583 + getNodeSysInfoFunc.nodeid = getNodeFunc.nodeid;
7584 + getNodeSysInfoFunc.node = node;
7585 +
7586 + // Query the database for system information
7587 + db.Get('si' + getNodeFunc.nodeid, getNodeSysInfoFunc);
7588 + } else { resultPendingCount--; }
7589 + if (resultPendingCount == 0) { func(getNodeFunc.results.join('\r\n'), type); }
7590 + }
7591 + getNodeFunc.results = results;
7592 + getNodeFunc.nodeid = docs[i]._id;
7593 + parent.GetNodeWithRights(domain, user, docs[i]._id, getNodeFunc);
7594 + }
7595 + });
7596 + } else {
7597 + // If we are paging, we know what devices the user is look at
7598 + for (var id in obj.visibleDevices) {
7599 // Fetch the node from the database
7600 resultPendingCount++;
7601 const getNodeFunc = function (node, rights, visible) {
@@ -7551,10 +7626,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
7626 if (resultPendingCount == 0) { func(getNodeFunc.results.join('\r\n'), type); }
7627 }
7628 getNodeFunc.results = results;
7554 - getNodeFunc.nodeid = docs[i]._id;
7555 - parent.GetNodeWithRights(domain, user, docs[i]._id, getNodeFunc);
7629 + getNodeFunc.nodeid = id;
7630 + parent.GetNodeWithRights(domain, user, id, getNodeFunc);
7631 }
7557 - });
7632 + }
7633 }
7634
7635 // Display a notification message for this session only.
@@ -7721,5 +7796,16 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
7796 return authFactorCount;
7797 }
7798
7799 + // Return true if the event is for a device that is part of the currently visible page
7800 + function isEventWithinPage(ids) {
7801 + if (obj.visibleDevices == null) return true; // Add devices are visible
7802 + var r = true;
7803 + for (var i in ids) {
7804 + // If the event is for a visible device, return true
7805 + if (ids[i].startsWith('node/')) { r = false; if (obj.visibleDevices[ids[i]] != null) return true; }
7806 + }
7807 + return r; // If this event is not for any specific device, return true
7808 + }
7809 +
7810 return obj;
7811 };