Added remote process control

Ylian Saint-Hilaire committed Apr 11, 2018 at 13:49 UTC fb55e44edfd07da58f2d19953c855a6a4327a9d8
14 files changed +447 -46
agents/MeshCmd-signed.exe
Binary files a/agents/MeshCmd-signed.exe and b/agents/MeshCmd-signed.exe differ
agents/MeshCmd64-signed.exe
Binary files a/agents/MeshCmd64-signed.exe and b/agents/MeshCmd64-signed.exe differ
agents/MeshService-signed.exe
Binary files a/agents/MeshService-signed.exe and b/agents/MeshService-signed.exe differ
agents/MeshService.exe
Binary files a/agents/MeshService.exe and b/agents/MeshService.exe differ
agents/MeshService64-signed.exe
Binary files a/agents/MeshService64-signed.exe and b/agents/MeshService64-signed.exe differ
agents/MeshService64.exe
Binary files a/agents/MeshService64.exe and b/agents/MeshService64.exe differ
agents/meshcore.js
+65 -31
@@ -30,6 +30,7 @@ function createMeshCore(agent) {
30 var net = require('net');
31 var fs = require('fs');
32 var rtc = require('ILibWebRTC');
33 + var processManager = require('process-manager');
34 var SMBiosTables = require('smbios');
35 var amtMei = null, amtLms = null, amtLmsState = 0;
36 var amtMeiConnected = 0, amtMeiTmpState = null;
@@ -327,39 +328,55 @@ function createMeshCore(agent) {
328 // If this is a console command, parse it and call the console handler
329 switch (data.action) {
330 case 'msg': {
330 - if (data.type == 'console') { // Process a console command
331 - if (data.value && data.sessionid) {
332 - var args = splitArgs(data.value);
333 - processConsoleCommand(args[0].toLowerCase(), parseArgs(args), data.rights, data.sessionid);
331 + switch (data.type) {
332 + case 'console': { // Process a console command
333 + if (data.value && data.sessionid) {
334 + var args = splitArgs(data.value);
335 + processConsoleCommand(args[0].toLowerCase(), parseArgs(args), data.rights, data.sessionid);
336 + }
337 + break;
338 }
335 - }
336 - else if ((data.type == 'tunnel') && (data.value != null)) { // Process a new tunnel connection request
337 - // Create a new tunnel object
338 - var xurl = getServerTargetUrlEx(data.value);
339 - if (xurl != null) {
340 - var woptions = http.parseUri(xurl);
341 - woptions.rejectUnauthorized = 0;
342 - //sendConsoleText(JSON.stringify(woptions));
343 - var tunnel = http.request(woptions);
344 - tunnel.upgrade = onTunnelUpgrade;
345 - tunnel.onerror = function (e) { sendConsoleText('ERROR: ' + JSON.stringify(e)); }
346 - tunnel.sessionid = data.sessionid;
347 - tunnel.rights = data.rights;
348 - tunnel.state = 0;
349 - tunnel.url = xurl;
350 - tunnel.protocol = 0;
351 - tunnel.tcpaddr = data.tcpaddr;
352 - tunnel.tcpport = data.tcpport;
353 - tunnel.end();
354 - // Put the tunnel in the tunnels list
355 - var index = nextTunnelIndex++;;
356 - tunnel.index = index;
357 - tunnels[index] = tunnel;
358 -
359 - sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid);
339 + case 'tunnel': {
340 + if (data.value != null) { // Process a new tunnel connection request
341 + // Create a new tunnel object
342 + var xurl = getServerTargetUrlEx(data.value);
343 + if (xurl != null) {
344 + var woptions = http.parseUri(xurl);
345 + woptions.rejectUnauthorized = 0;
346 + //sendConsoleText(JSON.stringify(woptions));
347 + var tunnel = http.request(woptions);
348 + tunnel.upgrade = onTunnelUpgrade;
349 + tunnel.onerror = function (e) { sendConsoleText('ERROR: ' + JSON.stringify(e)); }
350 + tunnel.sessionid = data.sessionid;
351 + tunnel.rights = data.rights;
352 + tunnel.state = 0;
353 + tunnel.url = xurl;
354 + tunnel.protocol = 0;
355 + tunnel.tcpaddr = data.tcpaddr;
356 + tunnel.tcpport = data.tcpport;
357 + tunnel.end();
358 + // Put the tunnel in the tunnels list
359 + var index = nextTunnelIndex++;;
360 + tunnel.index = index;
361 + tunnels[index] = tunnel;
362 +
363 + sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid);
364 + }
365 + }
366 + break;
367 + }
368 + case 'ps': {
369 + if (data.sessionid) {
370 + processManager.getProcesses(function (plist) { mesh.SendCommand({ "action": "msg", "type": "ps", "value": JSON.stringify(plist), "sessionid": data.sessionid }); });
371 + }
372 + break;
373 + }
374 + case 'pskill': {
375 + sendConsoleText(JSON.stringify(data));
376 + try { process.kill(data.value); } catch (e) { sendConsoleText(JSON.stringify(e)); }
377 + break;
378 }
379 }
362 - break;
380 }
381 case 'wakeonlan': {
382 // Send wake-on-lan on all interfaces for all MAC addresses in data.macs array. The array is a list of HEX MAC addresses.
@@ -819,7 +836,7 @@ function createMeshCore(agent) {
836 var response = null;
837 switch (cmd) {
838 case 'help': { // Displays available commands
822 - response = 'Available commands: help, info, args, print, type, dbget, dbset, dbcompact, eval, parseuri, httpget,\r\nwslist, wsconnect, wssend, wsclose, notify, ls, amt, netinfo, location, power, wakeonlan, scanwifi,\r\nscanamt, setdebug, smbios, rawsmbios.';
839 + response = 'Available commands: help, info, args, print, type, dbget, dbset, dbcompact, eval, parseuri, httpget,\r\nwslist, wsconnect, wssend, wsclose, notify, ls, ps, kill, amt, netinfo, location, power, wakeonlan, scanwifi,\r\nscanamt, setdebug, smbios, rawsmbios.';
840 break;
841 }
842 case 'setdebug': {
@@ -827,6 +844,23 @@ function createMeshCore(agent) {
844 else { if (args['_'][0] == '*') { console.setDestination(1); } else { console.setDestination(parseInt(args['_'][0]), sessionid); } }
845 break;
846 }
847 + case 'ps': {
848 + processManager.getProcesses(function (plist) {
849 + var x = '';
850 + for (var i in plist) { x += i + ', ' + plist[i].cmd + ((plist[i].user) ? (', ' + plist[i].user):'') + '\r\n'; }
851 + sendConsoleText(x, sessionid);
852 + });
853 + break;
854 + }
855 + case 'kill': {
856 + if ((args['_'].length < 1)) {
857 + response = 'Proper usage: kill [pid]'; // Display correct command usage
858 + } else {
859 + process.kill(parseInt(args['_'][0]));
860 + response = 'Killed process ' + args['_'][0] + '.';
861 + }
862 + break;
863 + }
864 case 'smbios': {
865 if (SMBiosTables != null) {
866 SMBiosTables.get(function (data) {
agents/modules_meshcmd/UserSessions.js new
+102
@@ -0,0 +1,102 @@
1 +
2 +
3 +function UserSessions()
4 +{
5 + this._ObjectID = 'UserSessions';
6 +
7 + if (process.platform == 'win32') {
8 + this._marshal = require('_GenericMarshal');
9 + this._kernel32 = this._marshal.CreateNativeProxy('Kernel32.dll');
10 + this._kernel32.CreateMethod('GetLastError');
11 + this._wts = this._marshal.CreateNativeProxy('Wtsapi32.dll');
12 + this._wts.CreateMethod('WTSEnumerateSessionsA');
13 + this._wts.CreateMethod('WTSQuerySessionInformationA');
14 + this._wts.CreateMethod('WTSFreeMemory');
15 + this.SessionStates = ['Active', 'Connected', 'ConnectQuery', 'Shadow', 'Disconnected', 'Idle', 'Listening', 'Reset', 'Down', 'Init'];
16 + this.InfoClass =
17 + {
18 + 'WTSInitialProgram': 0,
19 + 'WTSApplicationName': 1,
20 + 'WTSWorkingDirectory': 2,
21 + 'WTSOEMId': 3,
22 + 'WTSSessionId': 4,
23 + 'WTSUserName': 5,
24 + 'WTSWinStationName': 6,
25 + 'WTSDomainName': 7,
26 + 'WTSConnectState': 8,
27 + 'WTSClientBuildNumber': 9,
28 + 'WTSClientName': 10,
29 + 'WTSClientDirectory': 11,
30 + 'WTSClientProductId': 12,
31 + 'WTSClientHardwareId': 13,
32 + 'WTSClientAddress': 14,
33 + 'WTSClientDisplay': 15,
34 + 'WTSClientProtocolType': 16,
35 + 'WTSIdleTime': 17,
36 + 'WTSLogonTime': 18,
37 + 'WTSIncomingBytes': 19,
38 + 'WTSOutgoingBytes': 20,
39 + 'WTSIncomingFrames': 21,
40 + 'WTSOutgoingFrames': 22,
41 + 'WTSClientInfo': 23,
42 + 'WTSSessionInfo': 24,
43 + 'WTSSessionInfoEx': 25,
44 + 'WTSConfigInfo': 26,
45 + 'WTSValidationInfo': 27,
46 + 'WTSSessionAddressV4': 28,
47 + 'WTSIsRemoteSession': 29
48 + };
49 +
50 + this.getSessionAttribute = function getSessionAttribute(sessionId, attr)
51 + {
52 + var buffer = this._marshal.CreatePointer();
53 + var bytesReturned = this._marshal.CreateVariable(4);
54 +
55 + if (this._wts.WTSQuerySessionInformationA(0, sessionId, attr, buffer, bytesReturned).Val == 0)
56 + {
57 + throw ('Error calling WTSQuerySessionInformation: ' + this._kernel32.GetLastError.Val);
58 + }
59 +
60 + var retVal = buffer.Deref().String;
61 +
62 + this._wts.WTSFreeMemory(buffer.Deref());
63 + return (retVal);
64 + };
65 +
66 + this.Current = function Current()
67 + {
68 + var retVal = {};
69 + var pinfo = this._marshal.CreatePointer();
70 + var count = this._marshal.CreateVariable(4);
71 + if (this._wts.WTSEnumerateSessionsA(0, 0, 1, pinfo, count).Val == 0)
72 + {
73 + throw ('Error calling WTSEnumerateSessionsA: ' + this._kernel32.GetLastError().Val);
74 + }
75 +
76 + for (var i = 0; i < count.toBuffer().readUInt32LE() ; ++i)
77 + {
78 + var info = pinfo.Deref().Deref(i * (this._marshal.PointerSize == 4 ? 12 : 24), this._marshal.PointerSize == 4 ? 12 : 24);
79 + var j = { SessionId: info.toBuffer().readUInt32LE() };
80 + j.StationName = info.Deref(this._marshal.PointerSize == 4 ? 4 : 8, this._marshal.PointerSize).Deref().String;
81 + j.State = this.SessionStates[info.Deref(this._marshal.PointerSize == 4 ? 8 : 16, 4).toBuffer().readUInt32LE()];
82 + if (j.State == 'Active') {
83 + j.Username = this.getSessionAttribute(j.SessionId, this.InfoClass.WTSUserName);
84 + j.Domain = this.getSessionAttribute(j.SessionId, this.InfoClass.WTSDomainName);
85 + }
86 + retVal[j.SessionId] = j;
87 + }
88 +
89 + this._wts.WTSFreeMemory(pinfo.Deref());
90 + return (retVal);
91 + };
92 + }
93 + else
94 + {
95 + this.Current = function Current()
96 + {
97 + return ({});
98 + }
99 + }
100 +}
101 +
102 +module.exports = new UserSessions();
\ No newline at end of file
agents/modules_meshcore/UserSessions.js new
+102
@@ -0,0 +1,102 @@
1 +
2 +
3 +function UserSessions()
4 +{
5 + this._ObjectID = 'UserSessions';
6 +
7 + if (process.platform == 'win32') {
8 + this._marshal = require('_GenericMarshal');
9 + this._kernel32 = this._marshal.CreateNativeProxy('Kernel32.dll');
10 + this._kernel32.CreateMethod('GetLastError');
11 + this._wts = this._marshal.CreateNativeProxy('Wtsapi32.dll');
12 + this._wts.CreateMethod('WTSEnumerateSessionsA');
13 + this._wts.CreateMethod('WTSQuerySessionInformationA');
14 + this._wts.CreateMethod('WTSFreeMemory');
15 + this.SessionStates = ['Active', 'Connected', 'ConnectQuery', 'Shadow', 'Disconnected', 'Idle', 'Listening', 'Reset', 'Down', 'Init'];
16 + this.InfoClass =
17 + {
18 + 'WTSInitialProgram': 0,
19 + 'WTSApplicationName': 1,
20 + 'WTSWorkingDirectory': 2,
21 + 'WTSOEMId': 3,
22 + 'WTSSessionId': 4,
23 + 'WTSUserName': 5,
24 + 'WTSWinStationName': 6,
25 + 'WTSDomainName': 7,
26 + 'WTSConnectState': 8,
27 + 'WTSClientBuildNumber': 9,
28 + 'WTSClientName': 10,
29 + 'WTSClientDirectory': 11,
30 + 'WTSClientProductId': 12,
31 + 'WTSClientHardwareId': 13,
32 + 'WTSClientAddress': 14,
33 + 'WTSClientDisplay': 15,
34 + 'WTSClientProtocolType': 16,
35 + 'WTSIdleTime': 17,
36 + 'WTSLogonTime': 18,
37 + 'WTSIncomingBytes': 19,
38 + 'WTSOutgoingBytes': 20,
39 + 'WTSIncomingFrames': 21,
40 + 'WTSOutgoingFrames': 22,
41 + 'WTSClientInfo': 23,
42 + 'WTSSessionInfo': 24,
43 + 'WTSSessionInfoEx': 25,
44 + 'WTSConfigInfo': 26,
45 + 'WTSValidationInfo': 27,
46 + 'WTSSessionAddressV4': 28,
47 + 'WTSIsRemoteSession': 29
48 + };
49 +
50 + this.getSessionAttribute = function getSessionAttribute(sessionId, attr)
51 + {
52 + var buffer = this._marshal.CreatePointer();
53 + var bytesReturned = this._marshal.CreateVariable(4);
54 +
55 + if (this._wts.WTSQuerySessionInformationA(0, sessionId, attr, buffer, bytesReturned).Val == 0)
56 + {
57 + throw ('Error calling WTSQuerySessionInformation: ' + this._kernel32.GetLastError.Val);
58 + }
59 +
60 + var retVal = buffer.Deref().String;
61 +
62 + this._wts.WTSFreeMemory(buffer.Deref());
63 + return (retVal);
64 + };
65 +
66 + this.Current = function Current()
67 + {
68 + var retVal = {};
69 + var pinfo = this._marshal.CreatePointer();
70 + var count = this._marshal.CreateVariable(4);
71 + if (this._wts.WTSEnumerateSessionsA(0, 0, 1, pinfo, count).Val == 0)
72 + {
73 + throw ('Error calling WTSEnumerateSessionsA: ' + this._kernel32.GetLastError().Val);
74 + }
75 +
76 + for (var i = 0; i < count.toBuffer().readUInt32LE() ; ++i)
77 + {
78 + var info = pinfo.Deref().Deref(i * (this._marshal.PointerSize == 4 ? 12 : 24), this._marshal.PointerSize == 4 ? 12 : 24);
79 + var j = { SessionId: info.toBuffer().readUInt32LE() };
80 + j.StationName = info.Deref(this._marshal.PointerSize == 4 ? 4 : 8, this._marshal.PointerSize).Deref().String;
81 + j.State = this.SessionStates[info.Deref(this._marshal.PointerSize == 4 ? 8 : 16, 4).toBuffer().readUInt32LE()];
82 + if (j.State == 'Active') {
83 + j.Username = this.getSessionAttribute(j.SessionId, this.InfoClass.WTSUserName);
84 + j.Domain = this.getSessionAttribute(j.SessionId, this.InfoClass.WTSDomainName);
85 + }
86 + retVal[j.SessionId] = j;
87 + }
88 +
89 + this._wts.WTSFreeMemory(pinfo.Deref());
90 + return (retVal);
91 + };
92 + }
93 + else
94 + {
95 + this.Current = function Current()
96 + {
97 + return ({});
98 + }
99 + }
100 +}
101 +
102 +module.exports = new UserSessions();
\ No newline at end of file
agents/modules_meshcore/process-manager.js new
+96
@@ -0,0 +1,96 @@
1 +/*
2 +Copyright 2018 Intel Corporation
3 +
4 +Licensed under the Apache License, Version 2.0 (the "License");
5 +you may not use this file except in compliance with the License.
6 +You may obtain a copy of the License at
7 +
8 +http://www.apache.org/licenses/LICENSE-2.0
9 +
10 +Unless required by applicable law or agreed to in writing, software
11 +distributed under the License is distributed on an "AS IS" BASIS,
12 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 +See the License for the specific language governing permissions and
14 +limitations under the License.
15 +*/
16 +
17 +var GM = require('_GenericMarshal');
18 +
19 +function processManager() {
20 + this._ObjectID = 'processManager';
21 + switch (process.platform) {
22 + case 'win32':
23 + this._kernel32 = GM.CreateNativeProxy('kernel32.dll');
24 + this._kernel32.CreateMethod('GetLastError');
25 + this._kernel32.CreateMethod('CreateToolhelp32Snapshot');
26 + this._kernel32.CreateMethod('Process32First');
27 + this._kernel32.CreateMethod('Process32Next');
28 + break;
29 + case 'linux':
30 + this._childProcess = require('child_process');
31 + break;
32 + default:
33 + throw (process.platform + ' not supported');
34 + }
35 + this.getProcesses = function getProcesses(callback) {
36 + switch (process.platform) {
37 + case 'win32':
38 + var h = this._kernel32.CreateToolhelp32Snapshot(2, 0), info = GM.CreateVariable(304), retVal = {};
39 + info.toBuffer().writeUInt32LE(304, 0);
40 + var nextProcess = this._kernel32.Process32First(h, info);
41 + while (nextProcess.Val) {
42 + retVal[info.Deref(8, 4).toBuffer().readUInt32LE(0)] = { cmd: info.Deref(GM.PointerSize == 4 ? 36 : 44, 260).String };
43 + nextProcess = this._kernel32.Process32Next(h, info);
44 + }
45 + if (callback) { callback.apply(this, [retVal]); }
46 + break;
47 + case 'linux':
48 + if (!this._psp) { this._psp = {}; }
49 + var p = this._childProcess.execFile("/bin/ps", ["ps", "-uxa"], { type: this._childProcess.SpawnTypes.TERM });
50 + this._psp[p.pid] = p;
51 + p.Parent = this;
52 + p.ps = '';
53 + p.callback = callback;
54 + p.args = [];
55 + for (var i = 1; i < arguments.length; ++i) { p.args.push(arguments[i]); }
56 + p.on('exit', function onGetProcesses() {
57 + delete this.Parent._psp[this.pid];
58 + var retVal = {}, lines = this.ps.split('\x0D\x0A'), key = {}, keyi = 0;
59 + for (var i in lines) {
60 + var tokens = lines[i].split(' '), tokenList = [];
61 + for (var x in tokens) {
62 + if (i == 0 && tokens[x]) { key[tokens[x]] = keyi++; }
63 + if (i > 0 && tokens[x]) { tokenList.push(tokens[x]); }
64 + }
65 + if ((i > 0) && (tokenList[key.PID])) {
66 + retVal[tokenList[key.PID]] = { user: tokenList[key.USER], cmd: tokenList[key.COMMAND] };
67 + }
68 + }
69 + if (this.callback) {
70 + this.args.unshift(retVal);
71 + this.callback.apply(this.parent, this.args);
72 + }
73 + });
74 + p.stdout.on('data', function (chunk) { this.parent.ps += chunk.toString(); });
75 + break;
76 + default:
77 + throw ('Enumerating processes on ' + process.platform + ' not supported');
78 + }
79 + };
80 + this.getProcessInfo = function getProcessInfo(pid) {
81 + switch (process.platform) {
82 + case 'linux':
83 + var status = require('fs').readFileSync('/proc/' + pid + '/status'), lines = status.toString().split('\n'), info = {};
84 + for (var i in lines) {
85 + var tokens = lines[i].split(':');
86 + if (tokens.length > 1) { tokens[1] = tokens[1].trim(); }
87 + info[tokens[0]] = tokens[1];
88 + }
89 + return info;
90 + default:
91 + throw ('getProcessInfo() not supported for ' + process.platform);
92 + }
93 + };
94 +}
95 +
96 +module.exports = new processManager();
\ No newline at end of file
meshuser.js
+5 -4
@@ -651,9 +651,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
651 if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
652 if (obj.common.validateString(command.devicename, 1, 256) == false) break; // Check device name
653 if (obj.common.validateString(command.hostname, 1, 256) == false) break; // Check hostname
654 - if (obj.common.validateString(command.amtusername, 1, 16) == false) break; // Check username
655 - if (obj.common.validateString(command.amtpassword, 1, 16) == false) break; // Check password
656 - if (obj.common.validateInt(command.amttls, 0, 1) == false) break; // Check TLS flag
654 + if (obj.common.validateString(command.amtusername, 0, 16) == false) break; // Check username
655 + if (obj.common.validateString(command.amtpassword, 0, 16) == false) break; // Check password
656 + if (command.amttls == '0') { command.amttls = 0; } else if (command.amttls == '1') { command.amttls = 1; } // Check TLS flag
657 + if ((command.amttls != 1) && (command.amttls != 0)) break;
658
659 // Get the mesh
660 var mesh = obj.parent.meshes[command.meshid];
@@ -667,7 +668,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
668 obj.parent.crypto.randomBytes(48, function (err, buf) {
669 // create the new node
670 var nodeid = 'node/' + domain.id + '/' + buf.toString('base64').replace(/\+/g, '@').replace(/\//g, '$');;
670 - var device = { type: 'node', mtype: 1, _id: nodeid, meshid: command.meshid, name: command.devicename, host: command.hostname, domain: domain.id, intelamt: { user: command.amtusername, pass: command.amtpassword, tls: parseInt(command.amttls) } };
671 + var device = { type: 'node', mtype: 1, _id: nodeid, meshid: command.meshid, name: command.devicename, host: command.hostname, domain: domain.id, intelamt: { user: command.amtusername, pass: command.amtpassword, tls: command.amttls } };
672 obj.db.Set(device);
673
674 // Event the new node
package.json
+1 -1
@@ -1,6 +1,6 @@
1 {
2 "name": "meshcentral",
3 - "version": "0.1.6-a",
3 + "version": "0.1.6-c",
4 "keywords": [
5 "Remote Management",
6 "Intel AMT",
public/styles/style.css
+8
@@ -513,4 +513,12 @@ a {
513
514 .notification:hover {
515 background-color: #EFE8B6;
516 +}
517 +
518 +.deskToolsBar {
519 + padding:3px;
520 +}
521 +
522 +.deskToolsBar:hover {
523 + background-color: #EFE8B6;
524 }
\ No newline at end of file
views/default.handlebars
+68 -10
@@ -360,12 +360,21 @@
360 <div id="DeskParent">
361 <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>
362 </div>
363 + <div id="DeskTools" style="position:absolute;width:400px;height:100%;background-color:gray;top:0;right:0;border-left:2px solid lightgray;display:none">
364 + <a id="DeskToolsRefreshButton" style="float:right;padding:3px;cursor:pointer" onclick="refreshDeskTools()">Refresh</a>
365 + <div id="DeskToolsBar" style="position:absolute;padding:3px;border-radius: 3px 3px 0px 0px;top:5px;left:4px;bottom:26px;background-color:lightgray;cursor:pointer">Processes</div>
366 + <div style="position:absolute;top:26px;left:4px;right:4px;bottom:4px;background-color:lightgray;text-align:left">
367 + <div style="border-bottom:1px solid darkgray;padding:3px"><a style=width:50px;padding-right:5px;float:left;cursor:pointer title="Sort by process id" onclick=sortProcess(0)>PID</a><a style=cursor:pointer title="Sort by name" onclick=sortProcess(1)>Name</a></div>
368 + <div id="DeskToolsProcesses" style="overflow-y:scroll;position:absolute;top:24px;bottom:0px;width:100%"></div>
369 + </div>
370 + </div>
371 </td>
372 </tr>
373 <tr id=deskarea4>
374 <td style="padding-top:2px;padding-bottom:2px;background:#C0C0C0">
375 <div style="float:right;text-align:right">
376 <select id="termdisplays" style="display:none" onchange="deskSetDisplay(event)" onclick="deskGetDisplayNumbers(event)"></select>&nbsp;
377 + <input id="DeskToolsButton" type="button" value="Tools" title="Toggle tools view" onkeypress="return false" onkeydown="return false" onclick="toggleDeskTools()">&nbsp;
378 </div>
379 <div>
380 &nbsp;
@@ -969,11 +978,13 @@
978 if (index != -1) {
979 // Node was found, dispatch the message
980 if (message.type == 'console') { p15consoleReceive(nodes[index], message.value); } // This is a console message.
972 - if (message.type == 'notify') { // This is a notification message.
981 + else if (message.type == 'notify') { // This is a notification message.
982 var n = { text:message.value };
983 if (message.nodeid != null) { n.nodeid = message.nodeid; }
984 if (message.tag != null) { n.tag = message.tag; }
985 addNotification(n);
986 + } else if (message.type == 'ps') {
987 + showDeskToolsProcesses(message);
988 }
989 }
990 } else {
@@ -2823,6 +2834,10 @@
2834
2835 // Request the power timeline
2836 if ((powerTimelineNode != currentNode._id) && (powerTimelineReq != currentNode._id)) { powerTimelineReq = currentNode._id; meshserver.send({ action: 'powertimeline', nodeid: currentNode._id }); }
2837 +
2838 + // Reset the desktop tools
2839 + QV('DeskTools', false);
2840 + showDeskToolsProcesses();
2841 }
2842 setupDesktop(); // Always refresh the desktop, even if we are on the same device, we need to do some canvas switching.
2843 if (!panel) panel = 10;
@@ -3168,15 +3183,16 @@
3183 // Show and enable the right buttons
3184 function updateDesktopButtons() {
3185 var mesh = meshes[currentNode.meshid];
3171 - var deskState = ((desktop != null) && (desktop.state != 0));
3186 + var deskState = 0;
3187 + if (desktop != null) { deskState = desktop.State; }
3188
3189 // Show the right buttons
3174 - QV('disconnectbutton1span', (deskState == true));
3175 - QV('connectbutton1span', (deskState == false) && (mesh.mtype == 2));
3176 - QV('connectbutton1hspan', (deskState == false) && (currentNode.intelamt != null && ((currentNode.intelamt.ver != null) || (mesh.mtype == 1))));
3190 + QV('disconnectbutton1span', (deskState != 0));
3191 + QV('connectbutton1span', (deskState == 0) && (mesh.mtype == 2));
3192 + QV('connectbutton1hspan', (deskState == 0) && (currentNode.intelamt != null && ((currentNode.intelamt.ver != null) || (mesh.mtype == 1))));
3193
3194 // Show the right settings
3179 - QV('d7amtkvm', (currentNode.intelamt != null && ((currentNode.intelamt.ver != null) || (mesh.mtype == 1))) && ((deskState == false) || (desktop.contype == 2)));
3195 + QV('d7amtkvm', (currentNode.intelamt != null && ((currentNode.intelamt.ver != null) || (mesh.mtype == 1))) && ((deskState == 0) || (desktop.contype == 2)));
3196 QV('d7meshkvm', (mesh.mtype == 2) && ((deskState == false) || (desktop.contype == 1)));
3197
3198 // Enable buttons
@@ -3184,6 +3200,11 @@
3200 QE('connectbutton1', online);
3201 var hwonline = ((currentNode.conn & 6) != 0); // If CIRA (2) or AMT (4) connected, enable hardware terminal
3202 QE('connectbutton1h', hwonline);
3203 + QE('deskSaveBtn', deskState == 3);
3204 + QV('deskFocusBtn', (desktop != null) && (desktop.contype == 2) && (deskState != 0) && (desktopsettings.showfocus));
3205 + QE('DeskCAD', deskState == 3);
3206 + QE('DeskToolsButton', online);
3207 + if (online == false) QV('DeskTools', false);
3208 }
3209
3210 // Debug
@@ -3232,12 +3253,9 @@
3253 var xstate = state;
3254 if ((xstate == 3) && (xdesktop.contype == 2)) { xstate++; }
3255 var str = StatusStrs[xstate];
3235 - if (desktop.webRtcActive == true) { str += ', WebRTC'; }
3256 + if ((desktop != null) && (desktop.webRtcActive == true)) { str += ', WebRTC'; }
3257 //if (desktop.m.stopInput == true) { str += ', Loopback'; }
3258 QH('deskstatus', str);
3238 - QE('deskSaveBtn', state == 3);
3239 - QV('deskFocusBtn', (desktop != null) && (desktop.contype == 2) && (state != 0) && (desktopsettings.showfocus));
3240 - QE('DeskCAD', state == 3);
3259 switch (state) {
3260 case 0:
3261 // Disconnect and clean up the remote desktop
@@ -3364,6 +3382,44 @@
3382 desktop.m.sendcad();
3383 }
3384
3385 + // Show process dialogs
3386 + function toggleDeskTools() {
3387 + if (xxdialogMode) return;
3388 + if (QS('DeskTools').display == 'none') {
3389 + QV('DeskTools', true);
3390 + Q('DeskTools').nodeid = currentNode._id;
3391 + refreshDeskTools();
3392 + } else {
3393 + QV('DeskTools', false);
3394 + }
3395 + }
3396 +
3397 + // Refresh all of the desktop tool panels
3398 + function refreshDeskTools() {
3399 + QV('DeskToolsRefreshButton', false);
3400 + setTimeout(refreshDeskToolsEx, 500);
3401 + meshserver.send({ action: 'msg', type:'ps', nodeid: currentNode._id });
3402 + }
3403 + function refreshDeskToolsEx() { QV('DeskToolsRefreshButton', true); }
3404 + var deskTools = { sort: 1, msg: null };
3405 + function sortProcess(sort) { deskTools.sort = sort; showDeskToolsProcesses(deskTools.msg); }
3406 + function sortProcessPid(a, b) { if (a.p > b.p) return 1; if (a.p < b.p) return (-1); return 0; }
3407 + function sortProcessName(a, b) { if (a.d > b.d) return 1; if (a.d < b.d) return (-1); return 0; }
3408 + function showDeskToolsProcesses(message) {
3409 + deskTools.msg = message;
3410 + if (message == null) { QH('DeskToolsProcesses', ''); return; }
3411 + if (Q('DeskTools').nodeid != message.nodeid) return;
3412 + var p = [], processes = null;
3413 + try { processes = JSON.parse(message.value); } catch (e) { }
3414 + if (processes != null) {
3415 + for (var pid in processes) { p.push( { p:parseInt(pid), c:processes[pid].cmd, d:processes[pid].cmd.toLowerCase(), u: processes[pid].user } ); }
3416 + if (deskTools.sort == 0) { p.sort(sortProcessPid); } else if (deskTools.sort == 1) { p.sort(sortProcessName); }
3417 + var x = '';
3418 + for (var i in p) { if (p[i].p != 0) { x += '<div class=deskToolsBar><div style=width:50px;float:left;text-align:right;padding-right:5px>' + p[i].p + '</div><a style=float:right;padding-right:5px;cursor:pointer title="Stop process" onclick=stopProcess(' + p[i].p + ',"' + p[i].c + '")><img width=10 height=10 src="images/trash.png"></a><div style=float:right;padding-right:5px>' + (p[i].u?p[i].u:'') + '</div><div>' + p[i].c + '</div></div>'; } }
3419 + QH('DeskToolsProcesses', x);
3420 + }
3421 + }
3422 +
3423 // Toggle mouse and keyboard input
3424 function toggleKvmControl() { putstore('DeskControl', (Q("DeskControl").checked?1:0)); }
3425
@@ -3393,6 +3449,8 @@
3449 function dmousemove(e) { if (!xxdialogMode && desktop != null && Q('DeskControl').checked) desktop.m.mousemove(e) }
3450 function dmousewheel(e) { if (!xxdialogMode && desktop != null && Q('DeskControl').checked) { desktop.m.mousewheel(e); haltEvent(e); return true; } return false; }
3451 function drotate(x) { if (!xxdialogMode && desktop != null) { desktop.m.setRotation(desktop.m.rotation + x); deskAdjust(); deskAdjust(); } }
3452 + function stopProcess(id, name) { setDialogMode(2, "Process Control", 3, stopProcessEx, 'Stop process #' + id + ' "' + name + '"?', id); }
3453 + function stopProcessEx(buttons, tag) { meshserver.send({ action: 'msg', type:'pskill', nodeid: currentNode._id, value: tag }); setTimeout(refreshDeskTools, 300); }
3454
3455 //
3456 // TERMINAL