Added deviceinfo support to meshCtrl.
Ylian Saint-Hilaire committed
May 2, 2020 at 13:49 UTC
a2b944cd3384724beb9f754e906e5420ca44b61b
6 files changed
+253
-11
meshctrl.js
+203
-1
@@ -7,7 +7,7 @@ try { require('ws'); } catch (ex) { console.log('Missing module "ws", type "npm
7
var settings = {};
8
const crypto = require('crypto');
9
const args = require('minimist')(process.argv.slice(2));
10
-const possibleCommands = ['listusers', 'listdevicegroups', 'listdevices', 'listusersofdevicegroup', 'serverinfo', 'userinfo', 'adduser', 'removeuser', 'adddevicegroup', 'removedevicegroup', 'broadcast', 'showevents', 'addusertodevicegroup', 'removeuserfromdevicegroup', 'addusertodevice', 'removeuserfromdevice', 'sendinviteemail', 'generateinvitelink', 'config', 'movetodevicegroup'];
10
+const possibleCommands = ['listusers', 'listdevicegroups', 'listdevices', 'listusersofdevicegroup', 'serverinfo', 'userinfo', 'adduser', 'removeuser', 'adddevicegroup', 'removedevicegroup', 'broadcast', 'showevents', 'addusertodevicegroup', 'removeuserfromdevicegroup', 'addusertodevice', 'removeuserfromdevice', 'sendinviteemail', 'generateinvitelink', 'config', 'movetodevicegroup', 'deviceinfo'];
11
if (args.proxy != null) { try { require('https-proxy-agent'); } catch (ex) { console.log('Missing module "https-proxy-agent", type "npm install https-proxy-agent" to install it.'); return; } }
12
13
if (args['_'].length == 0) {
@@ -22,6 +22,7 @@ if (args['_'].length == 0) {
22
console.log(" ListDevices - List devices.");
23
console.log(" ListDeviceGroups - List device groups.");
24
console.log(" ListUsersOfDeviceGroup - List the users in a device group.");
25
+ console.log(" DeviceInfo - Show information about a device.");
26
console.log(" Config - Perform operation on config.json file.");
27
console.log(" AddUser - Create a new user account.");
28
console.log(" RemoveUser - Delete a user account.");
@@ -64,6 +65,11 @@ if (args['_'].length == 0) {
65
else { ok = true; }
66
break;
67
}
68
+ case 'deviceinfo': {
69
+ if (args.id == null) { console.log("Missing device id, use --id [deviceid]"); }
70
+ else { ok = true; }
71
+ break;
72
+ }
73
case 'addusertodevicegroup': {
74
if ((args.id == null) && (args.group == null)) { console.log("Device group identifier missing, use --id [groupid] or --group [groupname]"); }
75
else if (args.userid == null) { console.log("Add user to group missing useid, use --userid [userid]"); }
@@ -364,6 +370,17 @@ if (args['_'].length == 0) {
370
console.log(" --msg [message] - Message to display.");
371
break;
372
}
373
+ case 'deviceinfo': {
374
+ console.log("Display information about a device, Example usages:\r\n");
375
+ console.log(" MeshCtrl DeviceInfo --id deviceid");
376
+ console.log(" MeshCtrl DeviceInfo --id deviceid --json");
377
+ console.log("\r\nRequired arguments:\r\n");
378
+ console.log(" --id [deviceid] - The device identifier.");
379
+ console.log("\r\nOptional arguments:\r\n");
380
+ console.log(" --raw - Output raw data in JSON format.");
381
+ console.log(" --json - Give results in JSON format.");
382
+ break;
383
+ }
384
default: {
385
console.log("Get help on an action. Type:\r\n\r\n help [action]\r\n\r\nPossible actions are: " + possibleCommands.join(', ') + '.');
386
}
@@ -696,6 +713,13 @@ function serverConnect() {
713
console.log('Connected. Press ctrl-c to end.');
714
break;
715
}
716
+ case 'deviceinfo': {
717
+ settings.deviceinfocount = 3;
718
+ ws.send(JSON.stringify({ action: 'getnetworkinfo', nodeid: args.id, nodeinfo: true, responseid: 'meshctrl' }));
719
+ ws.send(JSON.stringify({ action: 'lastconnect', nodeid: args.id, nodeinfo: true, responseid: 'meshctrl' }));
720
+ ws.send(JSON.stringify({ action: 'getsysinfo', nodeid: args.id, nodeinfo: true, responseid: 'meshctrl' }));
721
+ break;
722
+ }
723
}
724
});
725
@@ -738,6 +762,32 @@ function serverConnect() {
762
}
763
break;
764
}
765
+ case 'getsysinfo': { // DEVICEINFO
766
+ if (settings.cmd == 'deviceinfo') {
767
+ if (data.result) {
768
+ console.log(data.result);
769
+ process.exit();
770
+ } else {
771
+ settings.sysinfo = data;
772
+ if (--settings.deviceinfocount == 0) { displayDeviceInfo(settings.sysinfo, settings.lastconnect, settings.networking); process.exit(); }
773
+ }
774
+ }
775
+ break;
776
+ }
777
+ case 'lastconnect': {
778
+ if (settings.cmd == 'deviceinfo') {
779
+ settings.lastconnect = (data.result)?null:data;
780
+ if (--settings.deviceinfocount == 0) { displayDeviceInfo(settings.sysinfo, settings.lastconnect, settings.networking); process.exit(); }
781
+ }
782
+ break;
783
+ }
784
+ case 'getnetworkinfo': {
785
+ if (settings.cmd == 'deviceinfo') {
786
+ settings.networking = (data.result) ? null : data;
787
+ if (--settings.deviceinfocount == 0) { displayDeviceInfo(settings.sysinfo, settings.lastconnect, settings.networking); process.exit(); }
788
+ }
789
+ break;
790
+ }
791
case 'adduser': // ADDUSER
792
case 'deleteuser': // REMOVEUSER
793
case 'createmesh': // ADDDEVICEGROUP
@@ -919,3 +969,155 @@ function encodeCookie(o, key) {
969
// Generate a random Intel AMT password
970
function checkAmtPassword(p) { return (p.length > 7) && (/\d/.test(p)) && (/[a-z]/.test(p)) && (/[A-Z]/.test(p)) && (/\W/.test(p)); }
971
function getRandomAmtPassword() { var p; do { p = Buffer.from(crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); } while (checkAmtPassword(p) == false); return p; }
972
+function format(format) { var args = Array.prototype.slice.call(arguments, 1); return format.replace(/{(\d+)}/g, function (match, number) { return typeof args[number] != 'undefined' ? args[number] : match; }); };
973
+
974
+function displayDeviceInfo(sysinfo, lastconnect, network) {
975
+ var node = sysinfo.node;
976
+ var hardware = sysinfo.hardware;
977
+ var info = {};
978
+
979
+ if (network != null) { sysinfo.netif = network.netif; }
980
+ if (lastconnect != null) { node.lastconnect = lastconnect.time; node.lastaddr = lastconnect.addr; }
981
+ if (args.raw) { console.log(JSON.stringify(sysinfo, ' ', 2)); return; }
982
+
983
+ // Operating System
984
+ if ((hardware.windows && hardware.windows.osinfo) || node.osdesc) {
985
+ var output = {}, outputCount = 0;
986
+ if (node.rname) { output["Name"] = node.rname; outputCount++; }
987
+ if (node.osdesc) { output["Version"] = node.osdesc; outputCount++; }
988
+ if (hardware.windows && hardware.windows.osinfo) { var m = hardware.windows.osinfo; if (m.OSArchitecture) { output["Architecture"] = m.OSArchitecture; outputCount++; } }
989
+ if (outputCount > 0) { info["Operating System"] = output; }
990
+ }
991
+
992
+ // MeshAgent
993
+ if (node.agent) {
994
+ var output = {}, outputCount = 0;
995
+ var agentsStr = ["Unknown", "Windows 32bit console", "Windows 64bit console", "Windows 32bit service", "Windows 64bit service", "Linux 32bit", "Linux 64bit", "MIPS", "XENx86", "Android ARM", "Linux ARM", "MacOS 32bit", "Android x86", "PogoPlug ARM", "Android APK", "Linux Poky x86-32bit", "MacOS 64bit", "ChromeOS", "Linux Poky x86-64bit", "Linux NoKVM x86-32bit", "Linux NoKVM x86-64bit", "Windows MinCore console", "Windows MinCore service", "NodeJS", "ARM-Linaro", "ARMv6l / ARMv7l", "ARMv8 64bit", "ARMv6l / ARMv7l / NoKVM", "Unknown", "Unknown", "FreeBSD x86-64"];
996
+ if ((node.agent != null) && (node.agent.id != null) && (node.agent.ver != null)) {
997
+ var str = '';
998
+ if (node.agent.id <= agentsStr.length) { str = agentsStr[node.agent.id]; } else { str = agentsStr[0]; }
999
+ if (node.agent.ver != 0) { str += ' v' + node.agent.ver; }
1000
+ output["Mesh Agent"] = str; outputCount++;
1001
+ }
1002
+ if ((node.conn & 1) != 0) {
1003
+ output["Last agent connection"] = "Connected now"; outputCount++;
1004
+ } else {
1005
+ if (node.lastconnect) { output["Last agent connection"] = new Date(node.lastconnect).toLocaleString(); outputCount++; }
1006
+ }
1007
+ if (node.lastaddr) {
1008
+ var splitip = node.lastaddr.split(':');
1009
+ if (splitip.length > 2) {
1010
+ output["Last agent address"] = node.lastaddr; outputCount++; // IPv6
1011
+ } else {
1012
+ output["Last agent address"] = splitip[0]; outputCount++; // IPv4
1013
+ }
1014
+ }
1015
+ if (outputCount > 0) { info["Mesh Agent"] = output; }
1016
+ }
1017
+
1018
+ // Networking
1019
+ if (network.netif != null) {
1020
+ var output = {}, outputCount = 0, minfo = {};
1021
+ for (var i in network.netif) {
1022
+ var m = network.netif[i], moutput = {}, moutputCount = 0;
1023
+ if (m.desc) { moutput["Description"] = m.desc; moutputCount++; }
1024
+ if (m.mac) {
1025
+ if (m.gatewaymac) {
1026
+ moutput["MAC Layer"] = format("MAC: {0}, Gateway: {1}", m.mac, m.gatewaymac); moutputCount++;
1027
+ } else {
1028
+ moutput["MAC Layer"] = format("MAC: {0}", m.mac); moutputCount++;
1029
+ }
1030
+ }
1031
+ if (m.v4addr && (m.v4addr != '0.0.0.0')) {
1032
+ if (m.v4gateway && m.v4mask) {
1033
+ moutput["IPv4 Layer"] = format("IP: {0}, Mask: {1}, Gateway: {2}", m.v4addr, m.v4mask, m.v4gateway); moutputCount++;
1034
+ } else {
1035
+ moutput["IPv4 Layer"] = format("IP: {0}", m.v4addr); moutputCount++;
1036
+ }
1037
+ }
1038
+ if (moutputCount > 0) { minfo[m.name + (m.dnssuffix ? (', ' + m.dnssuffix) : '')] = moutput; info["Networking"] = minfo; }
1039
+ }
1040
+ }
1041
+
1042
+ // Intel AMT
1043
+ if (node.intelamt != null) {
1044
+ var output = {}, outputCount = 0;
1045
+ output["Version"] = (node.intelamt.ver) ? ('v' + node.intelamt.ver) : ('<i>' + "Unknown" + '</i>'); outputCount++;
1046
+ var provisioningStates = { 0: "Not Activated (Pre)", 1: "Not Activated (In)", 2: "Activated" };
1047
+ var provisioningMode = '';
1048
+ if ((node.intelamt.state == 2) && node.intelamt.flags) { if (node.intelamt.flags & 2) { provisioningMode = (', ' + "Client Control Mode (CCM)"); } else if (node.intelamt.flags & 4) { provisioningMode = (', ' + "Admin Control Mode (ACM)"); } }
1049
+ output["Provisioning State"] = ((node.intelamt.state) ? (provisioningStates[node.intelamt.state]) : ('<i>' + "Unknown" + '</i>')) + provisioningMode; outputCount++;
1050
+ output["Security"] = (node.intelamt.tls == 1) ? "Secured using TLS" : "TLS is not setup"; outputCount++;
1051
+ output["Admin Credentials"] = (node.intelamt.user == null || node.intelamt.user == '') ? "Not Known" : "Known"; outputCount++;
1052
+ if (outputCount > 0) { info["Intel Active Management Technology (Intel AMT)"] = output; }
1053
+ }
1054
+
1055
+ if (hardware.identifiers) {
1056
+ var output = {}, outputCount = 0, ident = hardware.identifiers;
1057
+ // BIOS
1058
+ if (ident.bios_vendor) { output["Vendor"] = ident.bios_vendor; outputCount++; }
1059
+ if (ident.bios_version) { output["Version"] = ident.bios_version; outputCount++; }
1060
+ if (outputCount > 0) { info["BIOS"] = output; }
1061
+ output = {}, outputCount = 0;
1062
+
1063
+ // Motherboard
1064
+ if (ident.board_vendor) { output["Vendor"] = ident.board_vendor; outputCount++; }
1065
+ if (ident.board_name) { output["Name"] = ident.board_name; outputCount++; }
1066
+ if (ident.board_serial && (ident.board_serial != '')) { output["Serial"] = ident.board_serial; outputCount++; }
1067
+ if (ident.board_version) { output["Version"] = ident.board_version; }
1068
+ if (ident.product_uuid) { output["Identifier"] = ident.product_uuid; }
1069
+ if (ident.cpu_name) { output["CPU"] = ident.cpu_name; }
1070
+ if (ident.gpu_name) { for (var i in ident.gpu_name) { output["GPU" + (parseInt(i) + 1)] = ident.gpu_name[i]; } }
1071
+ if (outputCount > 0) { info["Motherboard"] = output; }
1072
+ }
1073
+
1074
+ // Memory
1075
+ if (hardware.windows) {
1076
+ if (hardware.windows.memory) {
1077
+ var output = {}, outputCount = 0, minfo = {};
1078
+ hardware.windows.memory.sort(function (a, b) { if (a.BankLabel > b.BankLabel) return 1; if (a.BankLabel < b.BankLabel) return -1; return 0; });
1079
+ for (var i in hardware.windows.memory) {
1080
+ var m = hardware.windows.memory[i], moutput = {}, moutputCount = 0;
1081
+ if (m.Capacity) { moutput["Capacity/Speed"] = (m.Capacity / 1024 / 1024) + " Mb, " + m.Speed + " Mhz"; moutputCount++; }
1082
+ if (m.PartNumber) { moutput["Part Number"] = ((m.Manufacturer && m.Manufacturer != 'Undefined') ? (m.Manufacturer + ', ') : '') + m.PartNumber; moutputCount++; }
1083
+ if (moutputCount > 0) { minfo[m.BankLabel] = moutput; info["Memory"] = minfo; }
1084
+ }
1085
+ }
1086
+ }
1087
+
1088
+ // Storage
1089
+ if (hardware.identifiers && ident.storage_devices) {
1090
+ var output = {}, outputCount = 0, minfo = {};
1091
+ // Sort Storage
1092
+ ident.storage_devices.sort(function (a, b) { if (a.Caption > b.Caption) return 1; if (a.Caption < b.Caption) return -1; return 0; });
1093
+ for (var i in ident.storage_devices) {
1094
+ var m = ident.storage_devices[i], moutput = {};
1095
+ if (m.Size) {
1096
+ if (m.Model && (m.Model != m.Caption)) { moutput["Model"] = m.Model; outputCount++; }
1097
+ if ((typeof m.Size == 'string') && (parseInt(m.Size) == m.Size)) { m.Size = parseInt(m.Size); }
1098
+ if (typeof m.Size == 'number') { moutput["Capacity"] = Math.floor(m.Size / 1024 / 1024) + 'Mb'; outputCount++; }
1099
+ if (typeof m.Size == 'string') { moutput["Capacity"] = m.Size; outputCount++; }
1100
+ if (moutputCount > 0) { minfo[m.Caption] = moutput; info["Storage"] = minfo; }
1101
+ }
1102
+ }
1103
+ }
1104
+
1105
+ // Display everything
1106
+ if (args.json) {
1107
+ console.log(JSON.stringify(info, ' ', 2));
1108
+ } else {
1109
+ for (var i in info) {
1110
+ console.log('--- ' + i + ' ---');
1111
+ for (var j in info[i]) {
1112
+ if (typeof info[i][j] == 'string') {
1113
+ console.log(' ' + j + ': ' + info[i][j]);
1114
+ } else {
1115
+ console.log(' ' + j + ':');
1116
+ for (var k in info[i][j]) {
1117
+ console.log(' ' + k + ': ' + info[i][j][k]);
1118
+ }
1119
+ }
1120
+ }
1121
+ }
1122
+ }
1123
+}
\ No newline at end of file
meshuser.js
+17
-4
@@ -616,9 +616,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
616
}
617
case 'getsysinfo':
618
{
619
+ if (common.validateString(command.nodeid, 1, 1024) == false) break; // Check the nodeid
620
+ if (command.nodeid.indexOf('/') == -1) { command.nodeid = 'node/' + domain.id + '/' + command.nodeid; }
621
+ if ((command.nodeid.split('/').length != 3) || (command.nodeid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
622
+
623
// Get the node and the rights for this node
624
parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
621
- if (visible == false) return;
625
+ if (visible == false) { try { ws.send(JSON.stringify({ action: 'getsysinfo', nodeid: command.nodeid, tag: command.tag, noinfo: true, result: 'Invalid device id' })); } catch (ex) { } return; }
626
// Query the database system information
627
db.Get('si' + command.nodeid, function (err, docs) {
628
if ((docs != null) && (docs.length > 0)) {
@@ -629,9 +633,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
633
delete doc.type;
634
delete doc.domain;
635
delete doc._id;
636
+ if (command.nodeinfo === true) { doc.node = node; doc.rights = rights; }
637
try { ws.send(JSON.stringify(doc)); } catch (ex) { }
638
} else {
634
- try { ws.send(JSON.stringify({ action: 'getsysinfo', nodeid: node._id, tag: command.tag, noinfo: true })); } catch (ex) { }
639
+ try { ws.send(JSON.stringify({ action: 'getsysinfo', nodeid: node._id, tag: command.tag, noinfo: true, result: 'Invalid device id' })); } catch (ex) { }
640
}
641
});
642
});
@@ -639,13 +644,20 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
644
}
645
case 'lastconnect':
646
{
647
+ if (common.validateString(command.nodeid, 1, 1024) == false) break; // Check the nodeid
648
+ if (command.nodeid.indexOf('/') == -1) { command.nodeid = 'node/' + domain.id + '/' + command.nodeid; }
649
+ if ((command.nodeid.split('/').length != 3) || (command.nodeid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
650
+
651
// Get the node and the rights for this node
652
parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
644
- if (visible == false) return;
653
+ if (visible == false) { try { ws.send(JSON.stringify({ action: 'lastconnect', nodeid: command.nodeid, tag: command.tag, noinfo: true, result: 'Invalid device id' })); } catch (ex) { } return; }
654
+
655
// Query the database for the last time this node connected
656
db.Get('lc' + command.nodeid, function (err, docs) {
657
if ((docs != null) && (docs.length > 0)) {
658
try { ws.send(JSON.stringify({ action: 'lastconnect', nodeid: command.nodeid, time: docs[0].time, addr: docs[0].addr })); } catch (ex) { }
659
+ } else {
660
+ try { ws.send(JSON.stringify({ action: 'lastconnect', nodeid: command.nodeid, tag: command.tag, noinfo: true, result: 'No data' })); } catch (ex) { }
661
}
662
});
663
});
@@ -3187,11 +3199,12 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
3199
{
3200
// Argument validation
3201
if (common.validateString(command.nodeid, 1, 1024) == false) break; // Check nodeid
3202
+ if (command.nodeid.indexOf('/') == -1) { command.nodeid = 'node/' + domain.id + '/' + command.nodeid; }
3203
if ((command.nodeid.split('/').length != 3) || (command.nodeid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
3204
3205
// Get the node and the rights for this node
3206
parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
3194
- if (visible == false) return;
3207
+ if (visible == false) { try { ws.send(JSON.stringify({ action: 'getnetworkinfo', nodeid: command.nodeid, tag: command.tag, noinfo: true, result: 'Invalid device id' })); } catch (ex) { } return; }
3208
3209
// Get network information about this node
3210
db.Get('if' + node._id, function (err, netinfos) {
public/images/icon-film.png
Binary files /dev/null and b/public/images/icon-film.png differ
public/scripts/agent-desktop-0.0.2.js
+13
@@ -196,6 +196,7 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
196
if (obj.debugmode > 1) { console.log("KRecv(" + str.length + "): " + rstr2hex(str.substring(0, Math.min(str.length, 40)))); }
197
if (str.length < 4) return;
198
var cmdmsg = null, X = 0, Y = 0, command = ReadShort(str, 0), cmdsize = ReadShort(str, 2), jumboAdd = 0;
199
+ if (obj.recordedData != null) { obj.recordedData.push({ t: Date.now(), d: str }); }
200
if ((command == 27) && (cmdsize == 8)) {
201
// Jumbo packet
202
if (str.length < 12) return;
@@ -777,6 +778,18 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
778
return true;
779
}
780
781
+ obj.StartRecording = function () {
782
+ obj.recordedData = [];
783
+ obj.recordedStart = Date.now();
784
+ }
785
+
786
+ obj.StopRecording = function () {
787
+ var r = obj.recordedData;
788
+ delete obj.recordedData;
789
+ delete obj.recordedStart;
790
+ return r;
791
+ }
792
+
793
// Private method
794
obj.MuchTheSame = function (a, b) { return (Math.abs(a - b) < 4); }
795
obj.Debug = function (msg) { console.log(msg); }
public/styles/style.css
+1
-1
@@ -2339,7 +2339,7 @@ a {
2339
-ms-grid-row: 4;
2340
}
2341
2342
-#DeskChatButton, #DeskNotifyButton, #DeskOpenWebButton, #DeskBackgroundButton, #DeskSaveImageButton {
2342
+#DeskChatButton, #DeskNotifyButton, #DeskOpenWebButton, #DeskBackgroundButton, #DeskSaveImageButton, #DeskRecordButton {
2343
float: right;
2344
margin-top: 1px;
2345
margin-right: 4px;
views/default.handlebars
+19
-5
@@ -572,6 +572,7 @@
572
<span id=DeskOpenWebButton title="Open a web address on the remote computer"><img src='images/icon-url2.png' onclick=deviceUrlFunction() height=16 width=16 style=padding-top:2px /></span>
573
<span id=DeskBackgroundButton title="Toggle remote desktop background"><img src='images/icon-background.png' onclick=deviceToggleBackground(event) height=16 width=16 style=padding-top:2px /></span>
574
<span id=DeskSaveImageButton title="Save a screenshot of the remote desktop"><img src='images/icon-camera.png' onclick=deskSaveImage() height=16 width=16 style=padding-top:2px /></span>
575
+ <span id=DeskRecordButton title="Record remote desktop session to file" style="display:none"><img src='images/icon-film.png' onclick=deskRecordSession() height=16 width=16 style=padding-top:2px /></span>
576
</div>
577
<div>
578
<select id="deskkeys">
@@ -5759,9 +5760,6 @@
5760
desktopNode = currentNode;
5761
updateDesktopButtons();
5762
deskAdjust();
5762
-
5763
- // On some browsers like IE, we can't save screen shots. Hide the scheenshot/capture buttons.
5764
- if (!Q('Desk')['toBlob']) { QV('DeskSaveImageButton', false); }
5763
}
5764
5765
// Show and enable the right buttons
@@ -5797,7 +5795,6 @@
5795
QE('connectbutton1', online);
5796
var hwonline = ((currentNode.conn & 6) != 0); // If CIRA (2) or AMT (4) connected, enable hardware terminal
5797
QE('connectbutton1h', hwonline);
5800
- QE('DeskSaveImageButton', deskState == 3);
5798
QV('deskFocusBtn', (desktop != null) && (desktop.contype == 2) && (deskState != 0) && (desktopsettings.showfocus));
5799
QV('DeskClip', (currentNode.agent) && (currentNode.agent.id != 11) && (currentNode.agent.id != 16) && ((desktop == null) || (desktop.contype != 2))); // Clipboard not supported on MacOS
5800
QE('DeskClip', deskState == 3);
@@ -5808,6 +5805,8 @@
5805
QE('deskkeys', deskState == 3);
5806
5807
// Display this only if we have Chat & Notify permissions
5808
+ QV('DeskSaveImageButton', (deskState == 3) && (Q('Desk')['toBlob'] != null));
5809
+ QV('DeskRecordButton', (deskState == 3));
5810
QV('DeskChatButton', ((rights & 16384) != 0) && (browserfullscreen == false) && (inputAllowed) && (currentNode.agent) && online);
5811
QV('DeskNotifyButton', ((rights & 16384) != 0) && (browserfullscreen == false) && (currentNode.agent) && (currentNode.agent.id < 5) && (inputAllowed) && (currentNode.agent) && online);
5812
@@ -6526,6 +6525,21 @@
6525
// Toggle mouse and keyboard input
6526
function toggleKvmControl() { putstore('DeskControl', (Q('DeskControl').checked?1:0)); QS('DeskControlSpan').color = Q('DeskControl').checked?null:'red'; }
6527
6528
+ // Toggle desktop session recording
6529
+ function deskRecordSession() {
6530
+ if (desktop == null) return;
6531
+ if (desktop.m.recordedData == null) {
6532
+ // Start recording
6533
+ console.log('Start record');
6534
+ desktop.m.StartRecording();
6535
+ } else {
6536
+ // Stop recording
6537
+ console.log('Stop record');
6538
+ var rec = desktop.m.StopRecording();
6539
+ console.log('frames', rec.length);
6540
+ }
6541
+ }
6542
+
6543
// Save the desktop image to file
6544
function deskSaveImage() {
6545
if (xxdialogMode || desktop == null || desktop.State != 3) return;
@@ -7672,7 +7686,7 @@
7686
// Storage
7687
if (hardware.identifiers && ident.storage_devices) {
7688
var x = '';
7675
- // Sort Memory
7689
+ // Sort Storage
7690
ident.storage_devices.sort(function(a, b) { if (a.Caption > b.Caption) return 1; if (a.Caption < b.Caption) return -1; return 0; });
7691
7692
x += '<table style=width:100%>';