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>
377
+ <input id="DeskToolsButton" type="button" value="Tools" title="Toggle tools view" onkeypress="return false" onkeydown="return false" onclick="toggleDeskTools()">
378
</div>
379
<div>
380
@@ -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