Added local device terminal SSH support to mobile app.
Ylian Saint-Hilaire committed
May 9, 2021 at 10:20 UTC
83c49a440a264cfcf0ffe02b7a81d028cf2337e2
2 files changed
+118
-30
views/default-mobile.handlebars
+106
-15
@@ -913,9 +913,10 @@
913
<div id=p10terminal style="overflow:hidden;position:absolute;top:55px;bottom:0px;width:100%;display:none;background-color:#333">
914
<div id=termTable style="position:absolute;top:0;bottom:0;left:0;right:0">
915
<div id="termarea1" style="position:absolute;top:0;height:32px;left:0;right:0">
916
- <div class="areaHead">
916
+ <div class="areaHead" style="line-height:24px">
917
<div class="toright2">
918
<input type=button id=termFullScreen value="Full Screen" onclick=deskToggleFull(event) onkeypress="return false" onkeydown="return false" disabled="disabled" style="height:28px;margin-right:3px;">
919
+ <div id="terminalCustomUpperRight" style="float:left;margin-right:6px"></div>
920
</div>
921
<div>
922
<span id="connectbutton2span" style="margin-left:3px"><input type="button" id="connectbutton2" cmenu="termConnectButton" value="Connect" style="height:28px" onclick=connectTerminal(event,1) onkeypress="return false" onkeydown="return false" disabled="disabled" /></span>
@@ -932,8 +933,8 @@
933
<div id="termarea4" style="position:absolute;bottom:0;height:32px;left:0;right:0">
934
<div class="areaFoot">
935
<div class="toright2"></div>
935
- <div>
936
- <input id="termActionsBtn" style="margin-left:3px;height:28px" type=button title="Perform power actions on the device" onkeypress="return false" onkeydown="return false" value=Actions onclick=deviceActionFunction() style="height:28px" />
936
+ <div style="height:28px">
937
+ <input id="termActionsBtn" style="margin-left:3px;height:28px" type=button title="Perform power actions on the device" onkeypress="return false" onkeydown="return false" value=Actions onclick=deviceActionFunction() />
938
</div>
939
</div>
940
</div>
@@ -1874,6 +1875,8 @@
1875
node.tags = message.event.node.tags;
1876
node.userloc = message.event.node.userloc;
1877
node.rdpport = message.event.node.rdpport;
1878
+ node.rfbport = message.event.node.rfbport;
1879
+ node.sshport = message.event.node.sshport;
1880
node.consent = message.event.node.consent;
1881
node.pmt = message.event.node.pmt;
1882
if (message.event.node.agent != null) {
@@ -2787,7 +2790,15 @@
2790
Q('softKeyboard').value = '';
2791
var k = 0;
2792
if (e.charCode != 0) { k = e.charCode; } else if (e.keyCode != 0) { k = e.keyCode; }
2790
- if (k != 0) { terminal.sendText(String.fromCharCode(k)); }
2793
+ if (k != 0) {
2794
+ if (terminal.urlname == 'sshterminalrelay.ashx') {
2795
+ // SSH
2796
+ terminal.socket.send('~' + String.fromCharCode(k));
2797
+ } else {
2798
+ // Agent
2799
+ terminal.sendText(String.fromCharCode(k));
2800
+ }
2801
+ }
2802
return false;
2803
}
2804
}
@@ -2814,7 +2825,16 @@
2825
var k = 0;
2826
if (e.charCode != 0) { k = e.charCode; } else if (e.keyCode != 0) { k = e.keyCode; }
2827
if (k == 8) { terminal.sendText(String.fromCharCode(k)); } // Enter and backspace
2817
- else if (e.ctrlKey && (k >= 64) && (k <= 95)) { console.log(k - 64); terminal.sendText(String.fromCharCode(k - 64)); } // Ctrl keys
2828
+ else if (e.ctrlKey && (k >= 64) && (k <= 95)) {
2829
+ // Ctrl keys
2830
+ if (terminal.urlname == 'sshterminalrelay.ashx') {
2831
+ // SSH
2832
+ terminal.socket.send('~' + String.fromCharCode(k - 64));
2833
+ } else {
2834
+ // Agent
2835
+ terminal.sendText(String.fromCharCode(k - 64));
2836
+ }
2837
+ }
2838
}
2839
}
2840
@@ -2842,7 +2862,13 @@
2862
}
2863
if (terminal && !xxdialogMode && (xxcurrentView == 10) && (currentDevicePanel == 5) && (gotKeyPressEvent == false) && (t !== 1)) {
2864
if (e.altKey == true) { return true; }
2845
- terminal.sendText(Q('softKeyboard').value);
2865
+ if (terminal.urlname == 'sshterminalrelay.ashx') {
2866
+ // SSH
2867
+ terminal.socket.send('~' + Q('softKeyboard').value);
2868
+ } else {
2869
+ // Agent
2870
+ terminal.sendText(Q('softKeyboard').value);
2871
+ }
2872
Q('softKeyboard').value = '';
2873
return false;
2874
}
@@ -3284,22 +3310,25 @@
3310
}
3311
3312
// Attribute: Mesh Agent
3287
- if ((node.agent != null) && (node.agent.id != null) && (node.agent.ver != null)) {
3313
+ if ((node.agent != null) && (node.agent.id != null) && (mesh.mtype == 3)) {
3314
+ if (node.agent.id == 4) { x += addDeviceAttribute("Device Type", "Windows"); }
3315
+ if (node.agent.id == 6) { x += addDeviceAttribute("Device Type", "Linux"); }
3316
+ if (node.agent.id == 29) { x += addDeviceAttribute("Device Type", "macOS"); }
3317
+ } else if ((node.agent != null) && (node.agent.id != null) && (node.agent.ver != null)) {
3318
var str = '';
3319
if (node.agent.id <= agentsStr.length) { str = agentsStr[node.agent.id]; } else { str = agentsStr[0]; }
3320
if (node.agent.ver != 0) { str += ' v' + node.agent.ver; }
3321
if (node.agent.id == 14) { str = node.agent.core; }
3322
if ((node.agent.root === false) && ((node.conn & 1) != 0)) { str += ', ' + "Restricted"; }
3293
- x += addDeviceAttribute("Agent", str);
3323
+ x += addDeviceAttribute("Mesh Agent", str);
3324
}
3325
3326
// Attribute: Intel AMT
3327
if (node.intelamt != null) {
3328
var str = '';
3329
var provisioningStates = { 0: nobreak("Not Activated (Pre)"), 1: nobreak("Not Activated (In)"), 2: nobreak("Activated") };
3300
- if (node.intelamt.ver != null && node.intelamt.state == null) { str += '<i>' + nobreak("Unknown State") + '</i>, v' + EscapeHtml(node.intelamt.ver); } else
3301
-
3302
- if ((node.intelamt.ver == null) && (node.intelamt.state == 2)) { str += '<i>' + "Activated" + '</i>'; }
3330
+ if (node.intelamt.ver != null && node.intelamt.state == null) { str += '<i>' + nobreak("Unknown State") + '</i>, v' + EscapeHtml(node.intelamt.ver); }
3331
+ else if ((node.intelamt.ver == null) && (node.intelamt.state == 2)) { str += '<i>' + "Activated" + '</i>'; }
3332
else if ((node.intelamt.ver == null) || (node.intelamt.state == null)) { str += '<i>' + "Unknown Version & State" + '</i>'; }
3333
else {
3334
str += provisioningStates[node.intelamt.state];
@@ -3405,6 +3434,9 @@
3434
3435
QH('p10html', x);
3436
3437
+ // If we are looking at a local non-windows device, enable terminal capability.
3438
+ if ((mesh.mtype == 3) && (node.agent != null) && (node.agent.id > 4) && (features2 & 0x00000200)) { node.agent.caps = 2; }
3439
+
3440
// Show node last 7 days timeline
3441
//drawDeviceTimeline();
3442
setupTerminal();
@@ -4454,13 +4486,36 @@
4486
var termState = ((terminal != null) && (terminal.state != 0));
4487
QE('termFullScreen', (termState != 0));
4488
4489
+ // If we are looking at a local non-windows device, enable terminal capability.
4490
+ if ((terminalNode.mtype == 3) && (terminalNode.agent != null) && (terminalNode.agent.id > 4) && (features2 & 0x00000200)) { terminalNode.agent.caps = 2; }
4491
+
4492
// Show the right buttons
4493
QV('disconnectbutton2span', (termState == true));
4494
QV('connectbutton2span', (termState == false) && (currentNode.agent != null) && (currentNode.agent.caps & 2));
4495
4496
// Enable buttons
4462
- var online = ((terminalNode.conn & 1) != 0); // If Agent (1) connected, enable Terminal
4497
+ var online = ((terminalNode.conn & 1) != 0) || (terminalNode.mtype == 3); // If Agent (1) connected, enable Terminal
4498
QE('connectbutton2', online);
4499
+
4500
+ // Enable action button if mesh type is not "local devices"
4501
+ QV('termActionsBtn', terminalNode.mtype != 3);
4502
+ if (terminalNode.mtype != 3) {
4503
+ QH('terminalCustomUpperRight', '');
4504
+ } else {
4505
+ QH('terminalCustomUpperRight', '<a style=cursor:pointer onclick=cmsshportaction(1,event)>' + format("SSH Port {0}", (terminalNode.sshport ? terminalNode.sshport : 22)) + '</a>');
4506
+ }
4507
+ }
4508
+
4509
+ function cmsshportaction(action) {
4510
+ if (xxdialogMode) return;
4511
+ var x = "SSH remote connection port:" + '<br /><br /><input type=text placeholder="22" inputmode="numeric" pattern="[0-9]*" onkeypress="return (event.keyCode == 8) || (event.charCode >= 48 && event.charCode <= 57)" maxlength=5 id=d10sshport type=text>';
4512
+ setDialogMode(2, "SSH Connection", 3, function () {
4513
+ // Save the new SSH port to the server
4514
+ var sshport = ((Q('d10sshport').value.length > 0) ? parseInt(Q('d10sshport').value) : 22);
4515
+ meshserver.send({ action: 'changedevice', nodeid: currentNode._id, sshport: sshport });
4516
+ }, x, currentNode);
4517
+ Q('d10sshport').focus();
4518
+ if (currentNode.sshport != null) { Q('d10sshport').value = currentNode.sshport; }
4519
}
4520
4521
// Called when the terminal state changes
@@ -4503,6 +4558,32 @@
4558
4559
function tunnelUpdate(data) { if (typeof data == 'string') { xterm.writeUtf8(data); } else { xterm.writeUtf8(new Uint8Array(data)); } }
4560
4561
+ function sshTunnelUpdate(data) {
4562
+ if (typeof data == 'string') {
4563
+ if (data[0] == '{') {
4564
+ var j = JSON.parse(data);
4565
+ switch (j.action) {
4566
+ case 'sshauth': {
4567
+ var x = '';
4568
+ x += addHtmlValue("Username", '<input id=dp2user style=width:190px maxlength=64 autocomplete=off onkeyup=sshAuthKeyUp(event) />');
4569
+ x += addHtmlValue("Password", '<input type=password id=dp2pass style=width:190px maxlength=64 autocomplete=off onkeyup=sshAuthKeyUp(event) />');
4570
+ setDialogMode(2, "Authentication", 11, sshConnectEx, x, 'ssh');
4571
+ setTimeout(sshAuthKeyUp, 50);
4572
+ }
4573
+ }
4574
+ } else if (data[0] == '~') { xterm.writeUtf8(data.substring(1)); }
4575
+ }
4576
+ }
4577
+
4578
+ function sshAuthKeyUp(e) { QE('idx_dlgOkButton', (Q('dp2user').value.length > 0) && (Q('dp2pass').value.length > 0)); }
4579
+ function sshConnectEx(b) {
4580
+ if (b == 0) {
4581
+ if (terminal != null) { connectTerminal(); } // Disconnect
4582
+ } else {
4583
+ terminal.socket.send(JSON.stringify({ action: 'sshauth', username: Q('dp2user').value, password: Q('dp2pass').value, cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight }));
4584
+ }
4585
+ }
4586
+
4587
function connectTerminal(e, contype, options) {
4588
p12clearConsoleMsg();
4589
if (!terminal) {
@@ -4548,7 +4629,7 @@
4629
xterm.setOption('scrollback', 0);
4630
//xterm.setOption('fontSize', 15);
4631
xterm.open(Q('termarea3xdiv'));
4551
- xterm.onData(function (data) { if (terminal != null) { terminal.sendText(data); } })
4632
+ xterm.onData(function (data) { if (terminal.urlname == 'sshterminalrelay.ashx') { terminal.socket.send('~' + data); } else { terminal.sendText(data); } })
4633
xterm.resize(80,34);
4634
4635
// Remove terminal textarea and scrollbar.
@@ -4556,7 +4637,8 @@
4637
document.getElementsByClassName('xterm-viewport')[0].style.overflow = 'hidden';
4638
4639
// Setup a terminal tunnel to the agent
4559
- terminal = CreateAgentRedirect(meshserver, CreateRemoteTunnel(tunnelUpdate, termoptions), serverPublicNamePort, authCookie, authRelayCookie, domainUrl);
4640
+ terminal = CreateAgentRedirect(meshserver, CreateRemoteTunnel((currentNode.mtype == 3) ? sshTunnelUpdate : tunnelUpdate, termoptions), serverPublicNamePort, authCookie, authRelayCookie, domainUrl);
4641
+ if (currentNode.mtype == 3) { terminal.urlname = 'sshterminalrelay.ashx'; } // If this is a SSH session, change the URL to the SSH application relay.
4642
terminal.debugmode = debugmode;
4643
terminal.m.debugmode = debugmode;
4644
terminal.options = termoptions;
@@ -4594,7 +4676,16 @@
4676
QH('termButtonMenu', x);
4677
}
4678
4597
- function termMenuButton(c) { toggleMenu(true); terminal.sendText(String.fromCharCode(c - 64)); }
4679
+ function termMenuButton(c) {
4680
+ toggleMenu(true);
4681
+ if (terminal.urlname == 'sshterminalrelay.ashx') {
4682
+ // SSH
4683
+ terminal.socket.send('~' + String.fromCharCode(c - 64));
4684
+ } else {
4685
+ // Agent
4686
+ terminal.sendText(String.fromCharCode(c - 64));
4687
+ }
4688
+ }
4689
4690
4691
//
views/default.handlebars
+12
-15
@@ -5420,7 +5420,6 @@
5420
if (xxdialogMode) return;
5421
var x = "RDP remote connection port:" + '<br /><br /><input type=text placeholder="3389" inputmode="numeric" pattern="[0-9]*" onkeypress="return (event.keyCode == 8) || (event.charCode >= 48 && event.charCode <= 57)" maxlength=5 id=d10rdpport type=text>';
5422
setDialogMode(2, "RDP Connection", 3, function() {
5423
- setDialogMode(0);
5423
// Save the new RDP port to the server
5424
var rdpport = ((Q('d10rdpport').value.length > 0) ? parseInt(Q('d10rdpport').value) : 3389);
5425
meshserver.send({ action: 'changedevice', nodeid: currentNode._id, rdpport: rdpport });
@@ -5434,7 +5433,6 @@
5433
if (xxdialogMode) return;
5434
var x = "SSH remote connection port:" + '<br /><br /><input type=text placeholder="22" inputmode="numeric" pattern="[0-9]*" onkeypress="return (event.keyCode == 8) || (event.charCode >= 48 && event.charCode <= 57)" maxlength=5 id=d10sshport type=text>';
5435
setDialogMode(2, "SSH Connection", 3, function() {
5437
- setDialogMode(0);
5436
// Save the new SSH port to the server
5437
var sshport = ((Q('d10sshport').value.length > 0) ? parseInt(Q('d10sshport').value) : 22);
5438
meshserver.send({ action: 'changedevice', nodeid: currentNode._id, sshport: sshport });
@@ -5448,7 +5446,6 @@
5446
if (xxdialogMode) return;
5447
var x = "VNC remote connection port:" + '<br /><br /><input type=text placeholder="5900" inputmode="numeric" pattern="[0-9]*" onkeypress="return (event.keyCode == 8) || (event.charCode >= 48 && event.charCode <= 57)" maxlength=5 id=d10rfbport type=text>';
5448
setDialogMode(2, "VNC Connection", 3, function() {
5451
- setDialogMode(0);
5449
// Save the new RFB port to the server
5450
var rfbport = ((Q('d10rfbport').value.length > 0) ? parseInt(Q('d10rfbport').value) : 3389);
5451
meshserver.send({ action: 'changedevice', nodeid: currentNode._id, rfbport: rfbport });
@@ -6275,6 +6272,7 @@
6272
if (node.agent.id <= agentsStr.length) { str = agentsStr[node.agent.id]; } else { str = agentsStr[0]; }
6273
if (node.agent.ver != 0) { str += ' v' + node.agent.ver; }
6274
if (node.agent.id == 14) { str = node.agent.core; }
6275
+ if ((node.agent.root === false) && ((node.conn & 1) != 0)) { str += ', ' + "Restricted"; }
6276
x += addDeviceAttribute("Mesh Agent", str);
6277
}
6278
@@ -6282,15 +6280,14 @@
6280
if (node.intelamt != null) {
6281
var str = '';
6282
var provisioningStates = { 0: nobreak("Not Activated (Pre)"), 1: nobreak("Not Activated (In)"), 2: nobreak("Activated") };
6285
- if (node.intelamt.ver != null && node.intelamt.state == null) { str += '<i>' + "Unknown State" + '</i>, v' + EscapeHtml(node.intelamt.ver); } else
6286
-
6287
- if ((node.intelamt.ver == null) && (node.intelamt.state == 2)) { str += '<i>' + "Activated" + '</i>'; }
6288
- else if ((node.intelamt.ver == null) || (node.intelamt.state == null)) { str += '<i>' + "Unknown Version & State" + '</i>'; }
6289
- else {
6290
- str += provisioningStates[node.intelamt.state];
6291
- if ((node.intelamt.state == 2) && node.intelamt.flags) { if (node.intelamt.flags & 2) { str += ' <span title="' + "Intel® AMT is activated in Client Control Mode" + '">' + "CCM" + '</span>'; } else if (node.intelamt.flags & 4) { str += ' <span title="' + "Intel® AMT is activated in Admin Control Mode" + '">' + "ACM" + '</span>'; } }
6292
- str += (', v' + EscapeHtml(node.intelamt.ver));
6293
- }
6283
+ if (node.intelamt.ver != null && node.intelamt.state == null) { str += '<i>' + "Unknown State" + '</i>, v' + EscapeHtml(node.intelamt.ver); }
6284
+ else if ((node.intelamt.ver == null) && (node.intelamt.state == 2)) { str += '<i>' + "Activated" + '</i>'; }
6285
+ else if ((node.intelamt.ver == null) || (node.intelamt.state == null)) { str += '<i>' + "Unknown Version & State" + '</i>'; }
6286
+ else {
6287
+ str += provisioningStates[node.intelamt.state];
6288
+ if ((node.intelamt.state == 2) && node.intelamt.flags) { if (node.intelamt.flags & 2) { str += ' <span title="' + "Intel® AMT is activated in Client Control Mode" + '">' + "CCM" + '</span>'; } else if (node.intelamt.flags & 4) { str += ' <span title="' + "Intel® AMT is activated in Admin Control Mode" + '">' + "ACM" + '</span>'; } }
6289
+ str += (', v' + EscapeHtml(node.intelamt.ver));
6290
+ }
6291
6292
// If Intel AMT is activated, show additional options
6293
if (node.intelamt.state == 2) {
@@ -6548,15 +6545,15 @@
6545
Q('MainComputerImage').setAttribute('src', 'images/icons256-' + node.icon + '-1.png');
6546
Q('MainComputerImage').className = ((((!node.conn) || (node.conn == 0)) && (node.mtype != 3))?'gray':'');
6547
6548
+ // If we are looking at a local non-windows device, enable terminal capability.
6549
+ if ((mesh.mtype == 3) && (node.agent != null) && (node.agent.id > 4) && (features2 & 0x00000200)) { node.agent.caps = 2; }
6550
+
6551
// Setup/Refresh the desktop tab
6552
if (terminalAccess) { setupTerminal(); }
6553
if (fileAccess) { setupFiles(); }
6554
var consoleRights = ((meshrights & 16) != 0);
6555
if (consoleRights) { setupConsole(); } else { if (panel == 15) { panel = 10; } }
6556
6557
- // If we are looking at a local non-windows device, enable terminal capability.
6558
- if ((mesh.mtype == 3) && (node.agent != null) && (node.agent.id > 4) && (features2 & 0x00000200)) { node.agent.caps = 2; }
6559
-
6557
// Show or hide the tabs
6558
// mesh.mtype: 1 = Intel AMT only, 2 = Mesh Agent, 3 = Local Device
6559
// node.agent.caps (bitmask): 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console