diff --git a/meshagent.js b/meshagent.js index fac05f5a..230765d1 100644 --- a/meshagent.js +++ b/meshagent.js @@ -1474,13 +1474,17 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { if (obj.agentInfo.capabilities & 0x40) return; if ((command == null) || (command == null)) return; // Safety, should never happen. + // If the device is pending a change, hold. + if (obj.deviceChanging === true) { setTimeout(function () { ChangeAgentCoreInfo(command); }, 100); return; } + obj.deviceChanging = true; + // Check that the mesh exists const mesh = parent.meshes[obj.dbMeshKey]; - if (mesh == null) return; + if (mesh == null) { delete obj.deviceChanging; return; } // Get the node and change it if needed db.Get(obj.dbNodeKey, function (err, nodes) { // TODO: THIS IS A BIG RACE CONDITION HERE, WE NEED TO FIX THAT. If this call is made twice at the same time on the same device, data will be missed. - if ((nodes == null) || (nodes.length != 1)) return; + if ((nodes == null) || (nodes.length != 1)) { delete obj.deviceChanging; return; } const device = nodes[0]; if (device.agent) { var changes = [], change = 0, log = 0; @@ -1538,6 +1542,9 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the node. Another event will come. parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(device.meshid, [obj.dbNodeKey]), obj, event); } + + // Device change is done. + delete obj.deviceChanging; } }); } @@ -1551,9 +1558,13 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { const mesh = parent.meshes[obj.dbMeshKey]; if (mesh == null) return; + // If the device is pending a change, hold. + if (obj.deviceChanging === true) { setTimeout(function () { ChangeAgentLocationInfo(command); }, 100); return; } + obj.deviceChanging = true; + // Get the node and change it if needed db.Get(obj.dbNodeKey, function (err, nodes) { - if ((nodes == null) || (nodes.length != 1)) { return; } + if ((nodes == null) || (nodes.length != 1)) { delete obj.deviceChanging; return; } const device = nodes[0]; if (device.agent) { var changes = [], change = 0; @@ -1580,27 +1591,87 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(device.meshid, [obj.dbNodeKey]), obj, event); } } + + // Done changing the device + delete obj.deviceChanging; }); } // Update the mesh agent tab in the database function ChangeAgentTag(tag) { if (obj.agentInfo.capabilities & 0x40) return; - if (tag.length == 0) { tag = null; } + if ((tag != null) && (tag.length == 0)) { tag = null; } + + // If the device is pending a change, hold. + if (obj.deviceChanging === true) { setTimeout(function () { ChangeAgentCoreInfo(command); }, 100); return; } + obj.deviceChanging = true; + // Get the node and change it if needed db.Get(obj.dbNodeKey, function (err, nodes) { - if ((nodes == null) || (nodes.length != 1)) return; + if ((nodes == null) || (nodes.length != 1)) { delete obj.deviceChanging; return; } const device = nodes[0]; if (device.agent) { - if (device.agent.tag != tag) { + // Parse the agent tag + var agentTag = null, serverName = null, serverDesc = null, serverTags = null; + if (tag != null) { + var taglines = tag.split('\r\n').join('\n').split('\r').join('\n').split('\n'); + for (var i in taglines) { + var tagline = taglines[i].trim(); + if (tagline.length > 0) { + if (tagline.startsWith('~')) { + if (tagline.startsWith('~ServerName:') && (tagline.length > 12) && (serverName == null)) { serverName = tagline.substring(12).trim(); } + if (tagline.startsWith('~ServerDesc:') && (tagline.length > 12) && (serverDesc == null)) { serverDesc = tagline.substring(12).trim(); } + if (tagline.startsWith('~ServerTags:') && (tagline.length > 12) && (serverTags == null)) { serverTags = tagline.substring(12).split(','); for (var j in serverTags) { serverTags[j] = serverTags[j].trim(); } } + } else { if (agentTag == null) { agentTag = tagline; } } + } + } + } + + // Set the agent tag + var changes = false; + if (device.agent.tag != agentTag) { device.agent.tag = agentTag; if ((device.agent.tag == null) || (device.agent.tag == '')) { delete device.agent.tag; } changes = true; } + if (domain.agenttag != null) { + // Set the device's server name + if ((serverName != null) && (domain.agenttag.servername === 1) && (device.name != serverName)) { device.name = serverName; changes = true; } + + // Set the device's server description + if ((serverDesc != null) && (domain.agenttag.serverdesc === 1) && (device.desc != serverDesc)) { device.desc = serverDesc; changes = true; } + + // Set the device's server description if there is no description + if ((serverDesc != null) && (domain.agenttag.serverdesc === 2) && (device.desc != serverDesc) && ((device.desc == null) || (device.desc == ''))) { device.desc = serverDesc; changes = true; } + + if ((serverTags != null) && (domain.agenttag.servertags != null) && (domain.agenttag.servertags != 0)) { + // Sort the tags + serverTags.sort(); + + // Stringify the tags + var st2 = '', st1 = serverTags.join(','); + if (device.tags != null) { st2 = device.tags.join(','); } + + // Set the device's server tags + if ((domain.agenttag.servertags === 1) && (st1 != st2)) { device.tags = serverTags; changes = true; } + + // Set the device's server tags if there are not tags + if ((domain.agenttag.servertags === 2) && (st2 == '')) { device.tags = serverTags; changes = true; } + + // Append to device's server tags + if ((domain.agenttag.servertags === 3) && (st1 != st2)) { + if (device.tags == null) { device.tags = []; } + for (var i in serverTags) { if (device.tags.indexOf(serverTags[i]) == -1) { device.tags.push(serverTags[i]); } } + device.tags.sort(); + changes = true; + } + } + } + + if (changes == true) { // Do some clean up if needed, these values should not be in the database. if (device.conn != null) { delete device.conn; } if (device.pwr != null) { delete device.pwr; } if (device.agct != null) { delete device.agct; } if (device.cict != null) { delete device.cict; } - // Set the new tag - device.agent.tag = tag; + // Update the device db.Set(device); // Event the node change @@ -1609,6 +1680,9 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(device.meshid, [obj.dbNodeKey]), obj, event); } } + + // Done changing the device + delete obj.deviceChanging; }); } diff --git a/meshcentral-config-schema.json b/meshcentral-config-schema.json index c2922f7f..ef25de01 100644 --- a/meshcentral-config-schema.json +++ b/meshcentral-config-schema.json @@ -196,6 +196,16 @@ "ldapOptions": { "type": "object", "description": "LDAP options passed to ldapauth-fork" }, "agentInviteCodes": { "type": "boolean", "default": false, "description": "Enabled a feature where you can set one or more invitation codes in a device group. You can then give a invitation link to users who can use it to download the agent." }, "agentNoProxy": { "type": "boolean", "default": false, "description": "When enabled, all newly installed MeshAgents will be instructed to no use a HTTP/HTTPS proxy even if one is configured on the remote system" }, + "agentTag": { + "type": "object", + "description": "This section is used to indicate if parts of the meshagent.tag file should be used to set server-side device properties.", + "additionalProperties": false, + "properties": { + "ServerName": { "type": "integer", "default": 0, "description": "Action taken if one of the lines in meshagent.tag contains ~ServerName:name. 0=Ignore, 1=Set." }, + "ServerDesc": { "type": "integer", "default": 0, "description": "Action taken if one of the lines in meshagent.tag contains ~ServerDesc:desc. 0=Ignore, 1=Set, 2=SetIfEmpty." }, + "ServerTags": { "type": "integer", "default": 0, "description": "Action taken if one of the lines in meshagent.tag contains ~ServerTags:tag1,tag2,tag3. 0=Ignore, 1=Set, 2=SetIfEmpty, 3=Append." } + } + }, "geoLocation": { "type": "boolean", "default": false, "description": "Enables the geo-location feature and device location map in the user interface, this feature is not being worked on." }, "novnc": { "type": "boolean", "default": true, "description": "When enabled, activates the built-in web-based noVNC client." }, "mstsc": { "type": "boolean", "default": false, "description": "When enabled, activates the built-in web-based RDP client." }, @@ -204,7 +214,7 @@ "customUI": { "type": "object" }, "consentMessages": { "type": "object", - "description": "This section is user to customize user consent prompts, these show up when asking if a remote session is allowed or not.", + "description": "This section is used to customize user consent prompts, these show up when asking if a remote session is allowed or not.", "additionalProperties": false, "properties": { "Title": { "type": "string" },