diff --git a/common.js b/common.js index 63f1ff21..29055817 100644 --- a/common.js +++ b/common.js @@ -130,5 +130,6 @@ module.exports.objKeysToLower = function (obj) { // Validation methods module.exports.validateString = function(str, minlen, maxlen) { return ((str != null) && (typeof str == 'string') && ((minlen == null) || (str.length >= minlen)) && ((maxlen == null) || (str.length <= maxlen))); } module.exports.validateInt = function(int, minval, maxval) { return ((int != null) && (typeof int == 'number') && ((minval == null) || (int >= minval)) && ((maxval == null) || (int <= maxval))); } -module.exports.validateArray = function(array, minlen, maxlen) { return ((array != null) && Array.isArray(array) && ((minlen == null) || (array.length >= minlen)) && ((maxlen == null) || (array.length <= maxlen))); } +module.exports.validateArray = function (array, minlen, maxlen) { return ((array != null) && Array.isArray(array) && ((minlen == null) || (array.length >= minlen)) && ((maxlen == null) || (array.length <= maxlen))); } +module.exports.validateStrArray = function (array, minlen, maxlen) { if (((array != null) && Array.isArray(array)) == false) return false; for (var i in array) { if ((typeof array[i] != 'string') && ((minlen == null) || (array[i].length >= minlen)) && ((maxlen == null) || (array[i].length <= maxlen))) return false; } return true; } module.exports.validateObject = function(obj) { return ((obj != null) && (typeof obj == 'object')); } diff --git a/meshuser.js b/meshuser.js index d02f9e0a..00df9846 100644 --- a/meshuser.js +++ b/meshuser.js @@ -976,6 +976,12 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { if ((command.intelamt.user != null) && (command.intelamt.pass != undefined) && ((command.intelamt.user != node.intelamt.user) || (command.intelamt.pass != node.intelamt.pass))) { change = 1; node.intelamt.user = command.intelamt.user; node.intelamt.pass = command.intelamt.pass; changes.push('Intel AMT credentials'); } if (command.intelamt.tls && (command.intelamt.tls != node.intelamt.tls)) { change = 1; node.intelamt.tls = command.intelamt.tls; changes.push('Intel AMT TLS'); } } + if (command.tags) { // Node grouping tag, this is a array of strings that can't be empty and can't contain a comma + var ok = true; + if (obj.common.validateString(command.tags, 0, 4096) == true) { command.tags = command.tags.split(','); } + if (obj.common.validateStrArray(command.tags, 1, 256) == true) { var groupTags = command.tags; for (var i in groupTags) { groupTags[i] = groupTags[i].trim(); if ((groupTags[i] == '') || (groupTags[i].indexOf(',') >= 0)) { ok = false; } } } + if (ok == true) { groupTags.sort(function (a, b) { return a.toLowerCase().localeCompare(b.toLowerCase()); }); node.tags = groupTags; change = 1; } + } else if ((command.tags === '') && node.tags) { delete node.tags; change = 1; } if (change == 1) { // Save the node diff --git a/package.json b/package.json index 54588d96..f5b27324 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.1.7-a", + "version": "0.1.7-b", "keywords": [ "Remote Management", "Intel AMT", diff --git a/views/default.handlebars b/views/default.handlebars index 0a663b67..accf94cd 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -155,6 +155,7 @@ +   @@ -1284,6 +1285,7 @@ node.iploc = message.event.node.iploc; node.wifiloc = message.event.node.wifiloc; node.gpsloc = message.event.node.gpsloc; + node.tags = message.event.node.tags; node.userloc = message.event.node.userloc; if (message.event.node.agent != null) { if (node.agent == null) node.agent = {}; @@ -1476,17 +1478,16 @@ var deviceHeaders = {}; var oldviewmode = 0; function updateDevices() { - var r = '', c = 0, current = null, count = 0, displayedMeshes = {}, view = Q('viewselect').value; + var r = '', c = 0, current = null, count = 0, displayedMeshes = {}, view = Q('viewselect').value, groups = {}, groupCount = {}; QV('xdevices', view < 4); QV('xdevicesmap', view == 4); QV('devListToolbar', view < 3); QV('kvmListToolbar', view == 3); QV('devMapToolbar', view == 4); - QV('devListToolbarSort', view < 4); QV('devListToolbarSize', view == 3); QV('NoMeshesPanel', meshcount == 0); QV('devListToolbarView', (meshcount != 0) && (nodes.length > 0)); - QV('devListToolbarSort', (meshcount != 0) && (nodes.length > 0)); + QV('devListToolbarSort', (meshcount != 0) && (nodes.length > 0) && (view < 4)); if ((meshcount == 0) || (nodes.length == 0)) { view = 1; } if (view == 4) { setTimeout( function() { if (xxmap.map != null) { xxmap.map.updateSize(); } }, 200); @@ -1572,10 +1573,41 @@ } } + // If we are displaying devices by group, put the device in the right group. + if ((sort == 3) && (r != '')) { + if (nodes[i].tags) { + for (var j in nodes[i].tags) { + var tag = nodes[i].tags[j]; + if (groups[tag] == null) { groups[tag] = r; groupCount[tag] = 1; } else { groups[tag] += r; groupCount[tag] += 1; } + if (view == 3) break; + } + } + r = ''; + } + deviceHeaderTotal++; if (typeof deviceHeaderCount[nodes[i].state] == 'undefined') { deviceHeaderCount[nodes[i].state] = 1; } else { deviceHeaderCount[nodes[i].state]++; } } + // If displaying devices by groups, sort the group names and display the devices. + if (sort == 3) { + var groupNames = []; + for (var i in groups) { groupNames.push(i); } + groupNames.sort(function (a, b) { return a.toLowerCase().localeCompare(b.toLowerCase()); }); + for (var j in groupNames) { + var i = groupNames[j]; r += '
' + i + ', ' + groupCount[i] + ' device' + ((groupCount[i] > 1)?'s':'') + '
' + groups[i]; + } + } + + // If there is nothing to display, explain the problem + if (r == '') { + if ((Q('SearchInput').value == '') && (sort == 3)) { + r = '
No devices are included in any groups, click on a device\'s \"Groups\" to add to a group.
'; + } else { + r = '
No devices matching this search.
'; + } + } + if ((view == 1) && (c == 2)) r += '
'; // Adds device padding // Display all empty meshes, we need to do this because users can add devices to these at any time. @@ -1654,7 +1686,7 @@ multiDesktop[id] = desktop; desktop = desktopNode = currentNode = null; // Setup a replacement desktop - QH('DeskParent', ''); + QH('DeskParent', ''); } else { // This is a new device, create a canvas for it. var c = document.createElement('canvas'); @@ -1664,7 +1696,7 @@ c.setAttribute('oncontextmenu', 'return false'); c.setAttribute('style', 'background-color:black;width:' + vsize.x + 'px;height:' + vsize.y + 'px'); c.setAttribute('onclick', 'toggleKvmDevice(\'' + id + '\')'); - Q('xkvmid_' + shortid).appendChild(c); + try { Q('xkvmid_' + shortid).appendChild(c); } catch (ex) {} // Check if we need to auto-connect if (Q('autoConnectDesktopCheckbox').checked == true) { setTimeout(function() { connectMultiDesktop(node, 1); }, 100); } } @@ -2037,13 +2069,18 @@ setDialogMode(2, "Group Action", 3, groupActionFunctionEx, x); } + // Get the list of checked devices, removes any duplicates. + function getCheckedDevices() { + var nodeids = [], elements = document.getElementsByClassName("DeviceCheckbox"), checkcount = 0; + for (var i in elements) { if (elements[i].checked) { if (elements[i].value) { var nid = elements[i].value.substring(6); if (nodeids.indexOf(nid) == -1) { nodeids.push(nid); } } } } + return nodeids; + } + function groupActionFunctionEx() { var op = Q('d2groupop').value; if (op == 100) { // Group wake - var nodeids = [], elements = document.getElementsByClassName("DeviceCheckbox"), checkcount = 0; - for (var i in elements) { if (elements[i].checked) { nodeids.push(elements[i].value.substring(6)); } } - meshserver.send({ action: 'wakedevices', nodeids: nodeids }); + meshserver.send({ action: 'wakedevices', nodeids: getCheckedDevices() }); } else if (op == 101) { // Group delete, ask for confirmation var x = "Confirm delete selected devices(s)?

"; @@ -2052,19 +2089,12 @@ QE('idx_dlgOkButton', false); } else { // Power operation - var nodeids = [], elements = document.getElementsByClassName("DeviceCheckbox"), checkcount = 0; - for (var i in elements) { if (elements[i].checked) { nodeids.push(elements[i].value.substring(6)); } } - meshserver.send({ action: 'poweraction', nodeids: nodeids, actiontype: op }); + meshserver.send({ action: 'poweraction', nodeids: getCheckedDevices(), actiontype: op }); } } function d2groupActionFunctionDelEx() { QE('idx_dlgOkButton', Q('d2check').checked); } - - function groupActionFunctionDelEx() { - var nodeids = [], elements = document.getElementsByClassName("DeviceCheckbox"), checkcount = 0; - for (var i in elements) { if (elements[i].checked) { nodeids.push(elements[i].value.substring(6)); } } - meshserver.send({ action: 'removedevices', nodeids: nodeids }); - } + function groupActionFunctionDelEx() { meshserver.send({ action: 'removedevices', nodeids: getCheckedDevices() }); } function onSortSelectChange(skipsave) { sort = document.getElementById("sortselect").selectedIndex; @@ -2858,6 +2888,11 @@ x += addDeviceAttribute('Connectivity', cstate.join(', ')); } + // Node grouping tags + var groupingTags = 'None'; + if (node.tags != null) { groupingTags = ''; for (var i in node.tags) { groupingTags += '' + node.tags[i] + ''; } } + x += addDeviceAttribute('Groups', '' + groupingTags + ''); + x += '
'; // Show action button, only show if we have permissions 4, 8, 64 if ((meshrights & 76) != 0) { x += ''; } @@ -3233,14 +3268,16 @@ meshserver.send({ action: 'changedevice', nodeid: currentNode._id, icon: icon }); } - var showEditNodeValueDialog_modes = ['Device Name', 'Hostname', 'Description']; - var showEditNodeValueDialog_modes2 = ['name', 'host', 'desc']; + var showEditNodeValueDialog_modes = ['Device Name', 'Hostname', 'Description', 'Groups']; + var showEditNodeValueDialog_modes2 = ['name', 'host', 'desc', 'tags']; + var showEditNodeValueDialog_modes3 = ['', '', '', 'Group1, Group2, Group3']; function showEditNodeValueDialog(mode) { if (xxdialogMode) return; - var x = addHtmlValue(showEditNodeValueDialog_modes[mode], ''); + var x = addHtmlValue(showEditNodeValueDialog_modes[mode], ''); setDialogMode(2, "Edit Device", 3, showEditNodeValueDialogEx, x, mode); var v = currentNode[showEditNodeValueDialog_modes2[mode]]; if (v == null) v = ''; + if (Array.isArray(v)) { v = v.join(', '); } Q('dp10devicevalue').value = v; p10editdevicevalueValidate(); Q('dp10devicevalue').focus();