Added support for device groups
Ylian Saint-Hilaire committed
May 1, 2018 at 11:42 UTC
a6e0fbef7215f7d6a41f309384790a49bcd5b071
4 files changed
+66
-22
common.js
+2
-1
@@ -130,5 +130,6 @@ module.exports.objKeysToLower = function (obj) {
130
// Validation methods
131
module.exports.validateString = function(str, minlen, maxlen) { return ((str != null) && (typeof str == 'string') && ((minlen == null) || (str.length >= minlen)) && ((maxlen == null) || (str.length <= maxlen))); }
132
module.exports.validateInt = function(int, minval, maxval) { return ((int != null) && (typeof int == 'number') && ((minval == null) || (int >= minval)) && ((maxval == null) || (int <= maxval))); }
133
-module.exports.validateArray = function(array, minlen, maxlen) { return ((array != null) && Array.isArray(array) && ((minlen == null) || (array.length >= minlen)) && ((maxlen == null) || (array.length <= maxlen))); }
133
+module.exports.validateArray = function (array, minlen, maxlen) { return ((array != null) && Array.isArray(array) && ((minlen == null) || (array.length >= minlen)) && ((maxlen == null) || (array.length <= maxlen))); }
134
+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; }
135
module.exports.validateObject = function(obj) { return ((obj != null) && (typeof obj == 'object')); }
meshuser.js
+6
@@ -976,6 +976,12 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
976
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'); }
977
if (command.intelamt.tls && (command.intelamt.tls != node.intelamt.tls)) { change = 1; node.intelamt.tls = command.intelamt.tls; changes.push('Intel AMT TLS'); }
978
}
979
+ if (command.tags) { // Node grouping tag, this is a array of strings that can't be empty and can't contain a comma
980
+ var ok = true;
981
+ if (obj.common.validateString(command.tags, 0, 4096) == true) { command.tags = command.tags.split(','); }
982
+ 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; } } }
983
+ if (ok == true) { groupTags.sort(function (a, b) { return a.toLowerCase().localeCompare(b.toLowerCase()); }); node.tags = groupTags; change = 1; }
984
+ } else if ((command.tags === '') && node.tags) { delete node.tags; change = 1; }
985
986
if (change == 1) {
987
// Save the node
package.json
+1
-1
@@ -1,6 +1,6 @@
1
{
2
"name": "meshcentral",
3
- "version": "0.1.7-a",
3
+ "version": "0.1.7-b",
4
"keywords": [
5
"Remote Management",
6
"Intel AMT",
views/default.handlebars
+57
-20
@@ -155,6 +155,7 @@
155
<option>Mesh</option>
156
<option>Power</option>
157
<option>Device</option>
158
+ <option>Group</option>
159
</select>
160
161
</div>
@@ -1284,6 +1285,7 @@
1285
node.iploc = message.event.node.iploc;
1286
node.wifiloc = message.event.node.wifiloc;
1287
node.gpsloc = message.event.node.gpsloc;
1288
+ node.tags = message.event.node.tags;
1289
node.userloc = message.event.node.userloc;
1290
if (message.event.node.agent != null) {
1291
if (node.agent == null) node.agent = {};
@@ -1476,17 +1478,16 @@
1478
var deviceHeaders = {};
1479
var oldviewmode = 0;
1480
function updateDevices() {
1479
- var r = '', c = 0, current = null, count = 0, displayedMeshes = {}, view = Q('viewselect').value;
1481
+ var r = '', c = 0, current = null, count = 0, displayedMeshes = {}, view = Q('viewselect').value, groups = {}, groupCount = {};
1482
QV('xdevices', view < 4);
1483
QV('xdevicesmap', view == 4);
1484
QV('devListToolbar', view < 3);
1485
QV('kvmListToolbar', view == 3);
1486
QV('devMapToolbar', view == 4);
1485
- QV('devListToolbarSort', view < 4);
1487
QV('devListToolbarSize', view == 3);
1488
QV('NoMeshesPanel', meshcount == 0);
1489
QV('devListToolbarView', (meshcount != 0) && (nodes.length > 0));
1489
- QV('devListToolbarSort', (meshcount != 0) && (nodes.length > 0));
1490
+ QV('devListToolbarSort', (meshcount != 0) && (nodes.length > 0) && (view < 4));
1491
if ((meshcount == 0) || (nodes.length == 0)) { view = 1; }
1492
if (view == 4) {
1493
setTimeout( function() { if (xxmap.map != null) { xxmap.map.updateSize(); } }, 200);
@@ -1572,10 +1573,41 @@
1573
}
1574
}
1575
1576
+ // If we are displaying devices by group, put the device in the right group.
1577
+ if ((sort == 3) && (r != '')) {
1578
+ if (nodes[i].tags) {
1579
+ for (var j in nodes[i].tags) {
1580
+ var tag = nodes[i].tags[j];
1581
+ if (groups[tag] == null) { groups[tag] = r; groupCount[tag] = 1; } else { groups[tag] += r; groupCount[tag] += 1; }
1582
+ if (view == 3) break;
1583
+ }
1584
+ }
1585
+ r = '';
1586
+ }
1587
+
1588
deviceHeaderTotal++;
1589
if (typeof deviceHeaderCount[nodes[i].state] == 'undefined') { deviceHeaderCount[nodes[i].state] = 1; } else { deviceHeaderCount[nodes[i].state]++; }
1590
}
1591
1592
+ // If displaying devices by groups, sort the group names and display the devices.
1593
+ if (sort == 3) {
1594
+ var groupNames = [];
1595
+ for (var i in groups) { groupNames.push(i); }
1596
+ groupNames.sort(function (a, b) { return a.toLowerCase().localeCompare(b.toLowerCase()); });
1597
+ for (var j in groupNames) {
1598
+ var i = groupNames[j]; r += '<div class=DevSt style=width:100%;padding-top:4px><span>' + i + '</span><span class="devHeaderx">, ' + groupCount[i] + ' device' + ((groupCount[i] > 1)?'s':'') + '</span></div>' + groups[i];
1599
+ }
1600
+ }
1601
+
1602
+ // If there is nothing to display, explain the problem
1603
+ if (r == '') {
1604
+ if ((Q('SearchInput').value == '') && (sort == 3)) {
1605
+ r = '<div style="margin:30px">No devices are included in any groups, click on a device\'s \"Groups\" to add to a group.</div>';
1606
+ } else {
1607
+ r = '<div style="margin:30px">No devices matching this search.</div>';
1608
+ }
1609
+ }
1610
+
1611
if ((view == 1) && (c == 2)) r += '<td><div style=width:301px></div></td>'; // Adds device padding
1612
1613
// Display all empty meshes, we need to do this because users can add devices to these at any time.
@@ -1654,7 +1686,7 @@
1686
multiDesktop[id] = desktop;
1687
desktop = desktopNode = currentNode = null;
1688
// Setup a replacement desktop
1657
- QH('DeskParent', '<canvas id="Desk" width="640" height="200" style="width:100%;-ms-touch-action:none;margin-left:0px" oncontextmenu="return false" onmousedown="dmousedown(event)" onmouseup="dmouseup(event)" onmousemove="dmousemove(event)"></canvas>');
1689
+ QH('DeskParent', '<canvas id="Desk" width="640" height="200" style="width:100%;-ms-touch-action:none;margin-left:0px" oncontextmenu="return false" onmousedown=dmousedown(event) onmouseup=dmouseup(event) onmousemove=dmousemove(event)></canvas>');
1690
} else {
1691
// This is a new device, create a canvas for it.
1692
var c = document.createElement('canvas');
@@ -1664,7 +1696,7 @@
1696
c.setAttribute('oncontextmenu', 'return false');
1697
c.setAttribute('style', 'background-color:black;width:' + vsize.x + 'px;height:' + vsize.y + 'px');
1698
c.setAttribute('onclick', 'toggleKvmDevice(\'' + id + '\')');
1667
- Q('xkvmid_' + shortid).appendChild(c);
1699
+ try { Q('xkvmid_' + shortid).appendChild(c); } catch (ex) {}
1700
// Check if we need to auto-connect
1701
if (Q('autoConnectDesktopCheckbox').checked == true) { setTimeout(function() { connectMultiDesktop(node, 1); }, 100); }
1702
}
@@ -2037,13 +2069,18 @@
2069
setDialogMode(2, "Group Action", 3, groupActionFunctionEx, x);
2070
}
2071
2072
+ // Get the list of checked devices, removes any duplicates.
2073
+ function getCheckedDevices() {
2074
+ var nodeids = [], elements = document.getElementsByClassName("DeviceCheckbox"), checkcount = 0;
2075
+ 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); } } } }
2076
+ return nodeids;
2077
+ }
2078
+
2079
function groupActionFunctionEx() {
2080
var op = Q('d2groupop').value;
2081
if (op == 100) {
2082
// Group wake
2044
- var nodeids = [], elements = document.getElementsByClassName("DeviceCheckbox"), checkcount = 0;
2045
- for (var i in elements) { if (elements[i].checked) { nodeids.push(elements[i].value.substring(6)); } }
2046
- meshserver.send({ action: 'wakedevices', nodeids: nodeids });
2083
+ meshserver.send({ action: 'wakedevices', nodeids: getCheckedDevices() });
2084
} else if (op == 101) {
2085
// Group delete, ask for confirmation
2086
var x = "Confirm delete selected devices(s)?<br /><br />";
@@ -2052,19 +2089,12 @@
2089
QE('idx_dlgOkButton', false);
2090
} else {
2091
// Power operation
2055
- var nodeids = [], elements = document.getElementsByClassName("DeviceCheckbox"), checkcount = 0;
2056
- for (var i in elements) { if (elements[i].checked) { nodeids.push(elements[i].value.substring(6)); } }
2057
- meshserver.send({ action: 'poweraction', nodeids: nodeids, actiontype: op });
2092
+ meshserver.send({ action: 'poweraction', nodeids: getCheckedDevices(), actiontype: op });
2093
}
2094
}
2095
2096
function d2groupActionFunctionDelEx() { QE('idx_dlgOkButton', Q('d2check').checked); }
2062
-
2063
- function groupActionFunctionDelEx() {
2064
- var nodeids = [], elements = document.getElementsByClassName("DeviceCheckbox"), checkcount = 0;
2065
- for (var i in elements) { if (elements[i].checked) { nodeids.push(elements[i].value.substring(6)); } }
2066
- meshserver.send({ action: 'removedevices', nodeids: nodeids });
2067
- }
2097
+ function groupActionFunctionDelEx() { meshserver.send({ action: 'removedevices', nodeids: getCheckedDevices() }); }
2098
2099
function onSortSelectChange(skipsave) {
2100
sort = document.getElementById("sortselect").selectedIndex;
@@ -2858,6 +2888,11 @@
2888
x += addDeviceAttribute('Connectivity', cstate.join(', '));
2889
}
2890
2891
+ // Node grouping tags
2892
+ var groupingTags = '<i>None</i>';
2893
+ if (node.tags != null) { groupingTags = ''; for (var i in node.tags) { groupingTags += '<span style="background-color:lightgray;padding:3px;margin-right:4px;border-radius:5px">' + node.tags[i] + '</span>'; } }
2894
+ x += addDeviceAttribute('Groups', '<span onclick=showEditNodeValueDialog(3) style=cursor:pointer>' + groupingTags + '</span>');
2895
+
2896
x += '</table><br />';
2897
// Show action button, only show if we have permissions 4, 8, 64
2898
if ((meshrights & 76) != 0) { x += '<input type=button value=Actions title="Perform power actions on the device" onclick=deviceActionFunction() />'; }
@@ -3233,14 +3268,16 @@
3268
meshserver.send({ action: 'changedevice', nodeid: currentNode._id, icon: icon });
3269
}
3270
3236
- var showEditNodeValueDialog_modes = ['Device Name', 'Hostname', 'Description'];
3237
- var showEditNodeValueDialog_modes2 = ['name', 'host', 'desc'];
3271
+ var showEditNodeValueDialog_modes = ['Device Name', 'Hostname', 'Description', 'Groups'];
3272
+ var showEditNodeValueDialog_modes2 = ['name', 'host', 'desc', 'tags'];
3273
+ var showEditNodeValueDialog_modes3 = ['', '', '', 'Group1, Group2, Group3'];
3274
function showEditNodeValueDialog(mode) {
3275
if (xxdialogMode) return;
3240
- var x = addHtmlValue(showEditNodeValueDialog_modes[mode], '<input id=dp10devicevalue style=width:230px maxlength=64 onchange=p10editdevicevalueValidate(' + mode + ',event) onkeyup=p10editdevicevalueValidate(' + mode + ',event) />');
3276
+ var x = addHtmlValue(showEditNodeValueDialog_modes[mode], '<input id=dp10devicevalue style=width:230px maxlength=64 placeholder="' + showEditNodeValueDialog_modes3[mode] + '" onchange=p10editdevicevalueValidate(' + mode + ',event) onkeyup=p10editdevicevalueValidate(' + mode + ',event) />');
3277
setDialogMode(2, "Edit Device", 3, showEditNodeValueDialogEx, x, mode);
3278
var v = currentNode[showEditNodeValueDialog_modes2[mode]];
3279
if (v == null) v = '';
3280
+ if (Array.isArray(v)) { v = v.join(', '); }
3281
Q('dp10devicevalue').value = v;
3282
p10editdevicevalueValidate();
3283
Q('dp10devicevalue').focus();