Completed device inactive auto-remove feature.

Ylian Saint-Hilaire committed Aug 4, 2021 at 10:55 UTC 2281a61e30fb8f58d2fabb0d721277286bc80252
4 files changed +114 -3
db.js
+105
@@ -112,6 +112,111 @@ module.exports.CreateDB = function (parent, func) {
112 }
113 }
114
115 + // Remove inactive devices
116 + obj.removeInactiveDevices = function () {
117 + // Get a list of domains and what their inactive device removal setting is
118 + var removeInactiveDevicesPerDomain = {}, minRemoveInactiveDevicesPerDomain = {}, minRemoveInactiveDevice = 9999;
119 + for (var i in parent.config.domains) {
120 + if (typeof parent.config.domains[i].autoremoveinactivedevices == 'number') {
121 + var v = parent.config.domains[i].autoremoveinactivedevices;
122 + if ((v > 1) && (v <= 2000)) {
123 + if (v < minRemoveInactiveDevice) { minRemoveInactiveDevice = v; }
124 + removeInactiveDevicesPerDomain[i] = v;
125 + minRemoveInactiveDevicesPerDomain[i] = v;
126 + }
127 + }
128 + }
129 +
130 + // Check if any device groups have a inactive device removal setting
131 + for (var i in parent.webserver.meshes) {
132 + if (typeof parent.webserver.meshes[i].expireDevs == 'number') {
133 + var v = parent.webserver.meshes[i].expireDevs;
134 + if ((v > 1) && (v <= 2000)) {
135 + if (v < minRemoveInactiveDevice) { minRemoveInactiveDevice = v; }
136 + if ((minRemoveInactiveDevicesPerDomain[parent.webserver.meshes[i].domain] == null) || (minRemoveInactiveDevicesPerDomain[parent.webserver.meshes[i].domain] > v)) {
137 + minRemoveInactiveDevicesPerDomain[parent.webserver.meshes[i].domain] = v;
138 + }
139 + } else {
140 + delete parent.webserver.meshes[i].expireDevs;
141 + }
142 + }
143 + }
144 +
145 + // If there are no such settings for any domain, we can exit now.
146 + if (minRemoveInactiveDevice == 9999) return;
147 + const now = Date.now();
148 +
149 + // For each domain with a inactive device removal setting, get a list of last device connections
150 + for (var domainid in minRemoveInactiveDevicesPerDomain) {
151 + obj.GetAllTypeNoTypeField('lastconnect', domainid, function (err, docs) {
152 + if ((err != null) || (docs == null)) return;
153 + for (var j in docs) {
154 + const days = (now - docs[j].time) / 86400000; // Calculate the number of inactive days
155 + var remove = false;
156 + if (removeInactiveDevicesPerDomain[docs[j].domain] && (removeInactiveDevicesPerDomain[docs[j].domain] < days)) { remove = true; }
157 + else { const mesh = parent.webserver.meshes[docs[j].meshid]; if (mesh && (typeof mesh.expireDevs == 'number') && (mesh.expireDevs < days)) { remove = true; } }
158 + if (remove) {
159 + // Check if this device is connected right now
160 + const nodeid = docs[j]._id.substring(2);
161 + const conn = parent.GetConnectivityState(nodeid);
162 + if (conn == null) {
163 + // Remove the device
164 + obj.Get(nodeid, function (err, docs) {
165 + if ((err != null) || (docs == null) || (docs.length != 1)) return;
166 + const node = docs[0];
167 +
168 + // Delete this node including network interface information, events and timeline
169 + obj.Remove(node._id); // Remove node with that id
170 + obj.Remove('if' + node._id); // Remove interface information
171 + obj.Remove('nt' + node._id); // Remove notes
172 + obj.Remove('lc' + node._id); // Remove last connect time
173 + obj.Remove('si' + node._id); // Remove system information
174 + obj.Remove('al' + node._id); // Remove error log last time
175 + if (obj.RemoveSMBIOS) { obj.RemoveSMBIOS(node._id); } // Remove SMBios data
176 + obj.RemoveAllNodeEvents(node._id); // Remove all events for this node
177 + obj.removeAllPowerEventsForNode(node._id); // Remove all power events for this node
178 + if (typeof node.pmt == 'string') { obj.Remove('pmt_' + node.pmt); } // Remove Push Messaging Token
179 + obj.Get('ra' + node._id, function (err, nodes) {
180 + if ((nodes != null) && (nodes.length == 1)) { obj.Remove('da' + nodes[0].daid); } // Remove diagnostic agent to real agent link
181 + obj.Remove('ra' + node._id); // Remove real agent to diagnostic agent link
182 + });
183 +
184 + // Remove any user node links
185 + if (node.links != null) {
186 + for (var i in node.links) {
187 + if (i.startsWith('user/')) {
188 + var cuser = parent.webserver.users[i];
189 + if ((cuser != null) && (cuser.links != null) && (cuser.links[node._id] != null)) {
190 + // Remove the user link & save the user
191 + delete cuser.links[node._id];
192 + if (Object.keys(cuser.links).length == 0) { delete cuser.links; }
193 + obj.SetUser(cuser);
194 +
195 + // Notify user change
196 + var targets = ['*', 'server-users', cuser._id];
197 + var event = { etype: 'user', userid: cuser._id, username: cuser.name, action: 'accountchange', msgid: 86, msgArgs: [cuser.name], msg: 'Removed user device rights for ' + cuser.name, domain: node.domain, account: parent.webserver.CloneSafeUser(cuser) };
198 + if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
199 + parent.DispatchEvent(targets, obj, event);
200 + }
201 + }
202 + }
203 + }
204 +
205 + // Event node deletion
206 + var meshname = '(unknown)';
207 + if ((parent.webserver.meshes[node.meshid] != null) && (parent.webserver.meshes[node.meshid].name != null)) { meshname = parent.webserver.meshes[node.meshid].name; }
208 + var event = { etype: 'node', action: 'removenode', nodeid: node._id, msgid: 87, msgArgs: [node.name, meshname], msg: 'Removed device ' + node.name + ' from device group ' + meshname, domain: node.domain };
209 + // TODO: We can't use the changeStream for node delete because we will not know the meshid the device was in.
210 + //if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to remove the node. Another event will come.
211 + parent.DispatchEvent(parent.webserver.CreateNodeDispatchTargets(node.meshid, node._id), obj, event);
212 + });
213 + }
214 + }
215 + }
216 + });
217 + }
218 + }
219 +
220 obj.cleanup = function (func) {
221 // TODO: Remove all mesh links to invalid users
222 // TODO: Remove all meshes that dont have any links
meshcentral-config-schema.json
+1 -1
@@ -295,7 +295,7 @@
295 "footer": { "type": "string", "default": null, "description": "This is a HTML string displayed at the bottom of the web page when a user is logged in." },
296 "loginfooter": { "type": "string", "default": null, "description": "This is a HTML string displayed at the bottom of the web page when a user is not logged in." },
297 "guestDeviceSharing": { "type": "boolean", "default": true, "description": "When set to false, the desktop/terminal sharing link feature is not available." },
298 - "autoRemoveInactiveDevices": { "type": "integer", "default": 0, "description": "Number of days a device can be inactive before it's removed. 0 disables this feature. Device group setting will override this value." },
298 + "autoRemoveInactiveDevices": { "type": "integer", "default": 0, "minimum": 0, "maximum": 2000, "description": "Number of days a device can be inactive before it's removed. 0 disables this feature. Device group setting will override this value." },
299 "altMessenging": {
300 "type": "object",
301 "properties": {
meshcentral.js
+2 -2
@@ -2129,7 +2129,7 @@ function CreateMeshCentralServer(config, args) {
2129 obj.DispatchEvent(obj.webserver.CreateNodeDispatchTargets(meshid, nodeid), obj, { action: 'nodeconnect', meshid: meshid, nodeid: nodeid, domain: nodeid.split('/')[1], conn: state.connectivity, pwr: state.powerState, ct: connectTime, nolog: 1, nopeers: 1 });
2130
2131 // Save indication of node connection change
2132 - const lc = { _id: 'lc' + nodeid, type: 'lastconnect', domain: nodeid.split('/')[1], meshid: meshid, time: Date.now(), cause: 1, connectType: connectType, serverid: obj.serverId };
2132 + const lc = { _id: 'lc' + nodeid, type: 'lastconnect', domain: nodeid.split('/')[1], meshid: meshid, time: Date.now(), cause: 1, connectType: connectType };
2133 if (extraInfo && extraInfo.remoteaddrport) { lc.addr = extraInfo.remoteaddrport; }
2134 obj.db.Set(lc);
2135 }
@@ -2200,7 +2200,7 @@ function CreateMeshCentralServer(config, args) {
2200 state.connectivity -= connectType;
2201
2202 // Save indication of node connection change
2203 - const lc = { _id: 'lc' + nodeid, type: 'lastconnect', domain: nodeid.split('/')[1], meshid: meshid, time: Date.now(), cause: 0, connectType: connectType, serverid: obj.serverId };
2203 + const lc = { _id: 'lc' + nodeid, type: 'lastconnect', domain: nodeid.split('/')[1], meshid: meshid, time: Date.now(), cause: 0, connectType: connectType };
2204 if (extraInfo && extraInfo.remoteaddrport) { lc.addr = extraInfo.remoteaddrport; }
2205 obj.db.Set(lc);
2206
meshuser.js
+6
@@ -5508,6 +5508,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
5508 'nodeconfig': [serverUserCommandNodeConfig, ""],
5509 'print': [serverUserCommandPrint, ""],
5510 'relays': [serverUserCommandRelays, ""],
5511 + 'removeinactivedevices': [serverUserCommandRemoveInactiveDevices, ""],
5512 'resetserver': [serverUserCommandResetServer, "Causes the server to reset, this is sometimes useful is the config.json file was changed."],
5513 'serverupdate': [serverUserCommandServerUpdate, "Updates server to latest version. Optional version argument to install specific version. Example: serverupdate 0.8.49"],
5514 'setmaxtasks': [serverUserCommandSetMaxTasks, ""],
@@ -6231,6 +6232,11 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
6232 if (cmdData.result == '') { cmdData.result = 'No relays.'; }
6233 }
6234
6235 + function serverUserCommandRemoveInactiveDevices(cmdData) {
6236 + parent.db.removeInactiveDevices();
6237 + cmdData.result = 'Ok';
6238 + }
6239 +
6240 function serverUserCommandAutoBackup(cmdData) {
6241 var backupResult = parent.db.performBackup(function (msg) {
6242 try { ws.send(JSON.stringify({ action: 'serverconsole', value: msg, tag: cmdData.command.tag })); } catch (ex) { }