1
0
Fork 0
mirror of https://github.com/Ylianst/MeshCentral.git synced 2025-02-12 11:01:52 +00:00

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

This commit is contained in:
Ylian Saint-Hilaire 2022-08-31 11:28:36 -07:00
parent d4d1f7d454
commit 15c882f24e
2 changed files with 139 additions and 44 deletions

51
db.js
View file

@ -1329,20 +1329,21 @@ module.exports.CreateDB = function (parent, func) {
func(err, performTypedRecordDecrypt(docs));
});
};
obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, func) {
obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, skip, limit, func) {
if (limit == 0) { limit = -1; } // In SQLite, no limit is -1
if (id && (id != '')) {
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) {
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) {
if (docs != null) { for (var i in docs) { delete docs[i].type; if (docs[i].links != null) { docs[i] = common.unEscapeLinksFieldName(docs[i]); } } }
func(err, performTypedRecordDecrypt(docs));
});
} else {
if (extrasids == null) {
sqlDbQuery('SELECT doc FROM main WHERE (type = $1) AND (domain = $2) AND (extra IN (' + dbMergeSqlArray(meshes) + '))', [type, domain], function (err, docs) {
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) {
if (docs != null) { for (var i in docs) { delete docs[i].type; if (docs[i].links != null) { docs[i] = common.unEscapeLinksFieldName(docs[i]); } } }
func(err, performTypedRecordDecrypt(docs));
});
} else {
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) {
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) {
if (docs != null) { for (var i in docs) { delete docs[i].type; if (docs[i].links != null) { docs[i] = common.unEscapeLinksFieldName(docs[i]); } } }
func(err, performTypedRecordDecrypt(docs));
});
@ -1550,7 +1551,7 @@ module.exports.CreateDB = function (parent, func) {
func(null, common.aceUnEscapeAllFieldNames(docs));
});
}
obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, func) {
obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, skip, limit, func) {
if (meshes.length == 0) { func(null, []); return; }
var query = obj.file.query('meshcentral').take(999999).filter('type', '==', type).filter('domain', '==', domain);
if (id) { query = query.filter('_id', '==', id); }
@ -1780,14 +1781,15 @@ module.exports.CreateDB = function (parent, func) {
obj.GetAll = function (func) { sqlDbQuery('SELECT domain, doc FROM main', null, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); }
obj.GetHash = function (id, func) { sqlDbQuery('SELECT doc FROM main WHERE id = $1', [id], function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); }
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)); }); };
obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, func) {
obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, skip, limit, func) {
if (limit == 0) { limit = 0xFFFFFFFF; }
if (id && (id != '')) {
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)); });
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)); });
} else {
if (extrasids == null) {
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);
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);
} else {
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)); });
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)); });
}
}
};
@ -1947,13 +1949,14 @@ module.exports.CreateDB = function (parent, func) {
obj.GetAll = function (func) { sqlDbQuery('SELECT domain, doc FROM main', null, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); }
obj.GetHash = function (id, func) { sqlDbQuery('SELECT doc FROM main WHERE id = ?', [id], function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); }
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)); }); };
obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, func) {
obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, skip, limit, func) {
if (limit == 0) { limit = 0xFFFFFFFF; }
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.
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.
if (id && (id != '')) {
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)); });
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)); });
} else {
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)); });
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)); });
}
};
obj.GetAllTypeNodeFiltered = function (nodes, domain, type, id, func) {
@ -2177,15 +2180,21 @@ module.exports.CreateDB = function (parent, func) {
obj.GetAll = function (func) { obj.file.find({}).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
obj.GetHash = function (id, func) { obj.file.find({ _id: id }).project({ _id: 0, hash: 1 }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
obj.GetAllTypeNoTypeField = function (type, domain, func) { obj.file.find({ type: type, domain: domain }).project({ type: 0 }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, func) {
obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, skip, limit, func) {
if (extrasids == null) {
var x = { type: type, domain: domain, meshid: { $in: meshes } };
const x = { type: type, domain: domain, meshid: { $in: meshes } };
if (id) { x._id = id; }
obj.file.find(x, { type: 0 }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
var f = obj.file.find(x, { type: 0 });
if (skip > 0) f = f.skip(skip); // Skip records
if (limit > 0) f = f.limit(limit); // Limit records
f.toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
} else {
var x = { type: type, domain: domain, $or: [ { meshid: { $in: meshes } }, { _id: { $in: extrasids } } ] };
const x = { type: type, domain: domain, $or: [ { meshid: { $in: meshes } }, { _id: { $in: extrasids } } ] };
if (id) { x._id = id; }
obj.file.find(x, { type: 0 }).toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
var f = obj.file.find(x, { type: 0 });
if (skip > 0) f = f.skip(skip); // Skip records
if (limit > 0) f = f.limit(limit); // Limit records
f.toArray(function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
}
};
obj.GetAllTypeNodeFiltered = function (nodes, domain, type, id, func) {
@ -2409,18 +2418,18 @@ module.exports.CreateDB = function (parent, func) {
obj.GetAll = function (func) { obj.file.find({}, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
obj.GetHash = function (id, func) { obj.file.find({ _id: id }, { _id: 0, hash: 1 }, func); };
obj.GetAllTypeNoTypeField = function (type, domain, func) { obj.file.find({ type: type, domain: domain }, { type: 0 }, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); }); };
//obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, domain, type, id, func) {
//obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, domain, type, id, skip, limit, func) {
//var x = { type: type, domain: domain, meshid: { $in: meshes } };
//if (id) { x._id = id; }
//obj.file.find(x, { type: 0 }, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
//};
obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, func) {
obj.GetAllTypeNoTypeFieldMeshFiltered = function (meshes, extrasids, domain, type, id, skip, limit, func) {
if (extrasids == null) {
var x = { type: type, domain: domain, meshid: { $in: meshes } };
const x = { type: type, domain: domain, meshid: { $in: meshes } };
if (id) { x._id = id; }
obj.file.find(x, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
} else {
var x = { type: type, domain: domain, $or: [{ meshid: { $in: meshes } }, { _id: { $in: extrasids } }] };
const x = { type: type, domain: domain, $or: [{ meshid: { $in: meshes } }, { _id: { $in: extrasids } }] };
if (id) { x._id = id; }
obj.file.find(x, function (err, docs) { func(err, performTypedRecordDecrypt(docs)); });
}

View file

@ -105,6 +105,11 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
obj.domain = domain;
obj.ws = ws;
// Information related to the current page the user is looking at
obj.deviceSkip = 0; // How many devices to skip
obj.deviceLimit = 0; // How many devices to view
obj.visibleDevices = null; // An object of visible nodeid's if the user is in paging mode
// Check if we are a cross-domain administrator
if (parent.parent.config.settings.managecrossdomain && (parent.parent.config.settings.managecrossdomain.indexOf(user._id) >= 0)) { obj.crossDomain = true; }
@ -414,6 +419,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
// If this session is logged in using a loginToken and the token is removed, disconnect.
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; }
// If this user is not viewing all devices and paging, check if this event is in the current page
if (isEventWithinPage(ids) == false) return;
// Normally, only allow this user to receive messages from it's own domain.
// If the user is a cross domain administrator, allow some select messages from different domains.
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
}
// Request a list of all nodes
db.GetAllTypeNoTypeFieldMeshFiltered(links, extraids, domain.id, 'node', command.id, function (err, docs) {
db.GetAllTypeNoTypeFieldMeshFiltered(links, extraids, domain.id, 'node', command.id, obj.deviceSkip, obj.deviceLimit, function (err, docs) {
//console.log(err, docs, links, extraids, domain.id, 'node', command.id);
@ -714,10 +722,14 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
parent.common.unEscapeAllLinksFieldName(docs);
var r = {};
if (obj.visibleDevices != null) { obj.visibleDevices = {}; }
for (i in docs) {
// Check device links, if a link points to an unknown user, remove it.
parent.cleanDevice(docs[i]);
// If we are paging, add the device to the page here
if (obj.visibleDevices != null) { obj.visibleDevices[docs[i]._id] = 1; }
// Remove any connectivity and power state information, that should not be in the database anyway.
// TODO: Find why these are sometimes saved in the db.
if (docs[i].conn != null) { delete docs[i].conn; }
@ -788,7 +800,14 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
r[meshid].push(docs[i]);
}
try { ws.send(JSON.stringify({ action: 'nodes', responseid: command.responseid, nodes: r, tag: command.tag })); } catch (ex) { }
const response = { action: 'nodes', responseid: command.responseid, nodes: r, tag: command.tag };
if (obj.visibleDevices != null) {
// If in paging mode, report back the skip and limit values
response.skip = obj.deviceSkip;
response.limit = obj.deviceLimit;
// TODO: Add total device count
}
try { ws.send(JSON.stringify(response)); } catch (ex) { }
});
break;
}
@ -6128,9 +6147,11 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
}
function serverCommandLastConnects(command) {
if (obj.visibleDevices == null) {
// If we are not paging, get all devices visible to this user
const links = parent.GetAllMeshIdWithRights(user);
const extraids = getUserExtraIds();
db.GetAllTypeNoTypeFieldMeshFiltered(links, extraids, domain.id, 'node', null, function (err, docs) {
db.GetAllTypeNoTypeFieldMeshFiltered(links, extraids, domain.id, 'node', null, obj.deviceSkip, obj.deviceLimit, function (err, docs) {
if (docs == null) return;
// Create a list of node ids for this user and query them for last device connection time
@ -6145,6 +6166,20 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
obj.send({ action: 'lastconnects', lastconnects: response, tag: command.tag });
});
});
} else {
// If we are paging, we know what devices the user is look at
// Create a list of node ids for this user and query them for last device connection time
const ids = []
for (var i in obj.visibleDevices) { ids.push('lc' + i); }
// Pull list of last connections only for device owned by this user
db.GetAllIdsOfType(ids, domain.id, 'lastconnect', function (err, docs) {
if (docs == null) return;
const response = {};
for (var j in docs) { response[docs[j]._id.substring(2)] = docs[j].time; }
obj.send({ action: 'lastconnects', lastconnects: response, tag: command.tag });
});
}
}
function serverCommandLoginCookie(command) {
@ -7505,6 +7540,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
// Return detailed information about all nodes this user has access to
function getAllDeviceDetailedInfo(type, func) {
// If we are not paging, get all devices visible to this user
if (obj.visibleDevices == null) {
// Get all device groups this user has access to
var links = parent.GetAllMeshIdWithRights(user);
@ -7512,7 +7550,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
var extraids = getUserExtraIds();
// Request a list of all nodes
db.GetAllTypeNoTypeFieldMeshFiltered(links, extraids, domain.id, 'node', null, function (err, docs) {
db.GetAllTypeNoTypeFieldMeshFiltered(links, extraids, domain.id, 'node', null, obj.deviceSkip, obj.deviceLimit, function (err, docs) {
if (docs == null) { docs = []; }
parent.common.unEscapeAllLinksFieldName(docs);
@ -7555,6 +7593,43 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
parent.GetNodeWithRights(domain, user, docs[i]._id, getNodeFunc);
}
});
} else {
// If we are paging, we know what devices the user is look at
for (var id in obj.visibleDevices) {
// Fetch the node from the database
resultPendingCount++;
const getNodeFunc = function (node, rights, visible) {
if ((node != null) && (visible == true)) {
const getNodeSysInfoFunc = function (err, docs) {
const getNodeNetInfoFunc = function (err, docs) {
var netinfo = null;
if ((err == null) && (docs != null) && (docs.length == 1)) { netinfo = docs[0]; }
resultPendingCount--;
getNodeNetInfoFunc.results.push({ node: parent.CloneSafeNode(getNodeNetInfoFunc.node), sys: getNodeNetInfoFunc.sysinfo, net: netinfo });
if (resultPendingCount == 0) { func(getNodeFunc.results, type); }
}
getNodeNetInfoFunc.results = getNodeSysInfoFunc.results;
getNodeNetInfoFunc.nodeid = getNodeSysInfoFunc.nodeid;
getNodeNetInfoFunc.node = getNodeSysInfoFunc.node;
if ((err == null) && (docs != null) && (docs.length == 1)) { getNodeNetInfoFunc.sysinfo = docs[0]; }
// Query the database for network information
db.Get('if' + getNodeSysInfoFunc.nodeid, getNodeNetInfoFunc);
}
getNodeSysInfoFunc.results = getNodeFunc.results;
getNodeSysInfoFunc.nodeid = getNodeFunc.nodeid;
getNodeSysInfoFunc.node = node;
// Query the database for system information
db.Get('si' + getNodeFunc.nodeid, getNodeSysInfoFunc);
} else { resultPendingCount--; }
if (resultPendingCount == 0) { func(getNodeFunc.results.join('\r\n'), type); }
}
getNodeFunc.results = results;
getNodeFunc.nodeid = id;
parent.GetNodeWithRights(domain, user, id, getNodeFunc);
}
}
}
// Display a notification message for this session only.
@ -7721,5 +7796,16 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
return authFactorCount;
}
// Return true if the event is for a device that is part of the currently visible page
function isEventWithinPage(ids) {
if (obj.visibleDevices == null) return true; // Add devices are visible
var r = true;
for (var i in ids) {
// If the event is for a visible device, return true
if (ids[i].startsWith('node/')) { r = false; if (obj.visibleDevices[ids[i]] != null) return true; }
}
return r; // If this event is not for any specific device, return true
}
return obj;
};