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) { }