Draft addition of terminal to mobile site.
Ylian Saint-Hilaire committed
Apr 6, 2021 at 22:46 UTC
9269c29bf064d2bca0b1548c0bb041a8d9ffd965
1 file changed
+238
views/default-mobile.handlebars
+238
@@ -12,6 +12,7 @@
12
<link rel="icon" type="image/png" sizes="16x16" href="{{{domainurl}}}favicon-16x16.png">
13
<link rel="icon" type="image/png" sizes="32x32" href="{{{domainurl}}}favicon-32x32.png">
14
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
15
+ <link type="text/css" href="styles/xterm.css" media="screen" rel="stylesheet" title="CSS" />
16
<meta name="apple-mobile-web-app-capable" content="yes">
17
<meta name="apple-mobile-web-app-status-bar-style" content="#ffffff">
18
<meta name="apple-mobile-web-app-title" content="{{{title}}}">
@@ -22,6 +23,8 @@
23
<script type="text/javascript" src="scripts/amt-0.2.0{{{min}}}.js"></script>
24
<script type="text/javascript" src="scripts/amt-redir-ws-0.1.0{{{min}}}.js"></script>
25
<script type="text/javascript" src="scripts/amt-desktop-0.0.2{{{min}}}.js"></script>
26
+ <script type="text/javascript" src="scripts/xterm{{{min}}}.js"></script>
27
+ <script type="text/javascript" src="scripts/xterm-addon-fit{{{min}}}.js"></script>
28
<script type="text/javascript" src="scripts/zlib{{{min}}}.js"></script>
29
<script type="text/javascript" src="scripts/zlib-inflate{{{min}}}.js"></script>
30
<script type="text/javascript" src="scripts/zlib-adler32{{{min}}}.js"></script>
@@ -627,6 +630,33 @@
630
border-bottom:1px solid black;
631
}
632
633
+ #termTable {
634
+ width: 100%;
635
+ padding: 0px;
636
+ padding: 0px;
637
+ margin-top: 0px;
638
+ }
639
+
640
+ .fulldesk #termTable {
641
+ position: absolute;
642
+ top: 0;
643
+ bottom: 0;
644
+ left: 0;
645
+ right: 0;
646
+ }
647
+
648
+ #termarea3x {
649
+ background: black;
650
+ text-align: center;
651
+ height: 500px;
652
+ position: relative;
653
+ }
654
+
655
+ #Term {
656
+ background: black;
657
+ margin: 0;
658
+ padding: 0;
659
+ }
660
</style>
661
</head>
662
<body id="body" onload="if (typeof(startup) !== 'undefined') startup();" style="overflow-y:hidden;margin:0;padding:0;border:0;font-size:13px;font-family:\'Trebuchet MS\', Arial, Helvetica, sans-serif">
@@ -888,6 +918,41 @@
918
</div>
919
</div>
920
</div>
921
+ <div id=p10terminal style="overflow:hidden;position:absolute;top:55px;bottom:0px;width:100%;display:none">
922
+ <div id=termTable style="position:relative">
923
+ <table style="width:100%" cellpadding=0 cellspacing=0>
924
+ <tr>
925
+ <td class="areaHead">
926
+ <div class="toright2">
927
+ </div>
928
+ <div>
929
+ <input type="button" id="autoconnectbutton2" value="AutoConnect" onclick=autoConnectTerminal(event) onkeypress="return false" onkeydown="return false" style="display:none" />
930
+ <span id="connectbutton2span"><input type="button" id="connectbutton2" cmenu="termConnectButton" value="Connect" onclick=connectTerminal(event,1) onkeypress="return false" onkeydown="return false" disabled="disabled" /></span>
931
+ <span id="disconnectbutton2span"> <input type="button" id="disconnectbutton2" value="Disconnect" onclick=connectTerminal(event,0) onkeypress="return false" onkeydown="return false" /></span>
932
+ <span id="termstatus" style="line-height:22px">Disconnected</span><span id="termtitle"></span>
933
+ </div>
934
+ </td>
935
+ </tr>
936
+ <tr>
937
+ <td id="termarea3x">
938
+ <div id="termarea3xdiv" style="width:100%;height:100%"></div>
939
+ <pre id="Term"></pre>
940
+ </td>
941
+ </tr>
942
+ <tr>
943
+ <td class="areaFoot">
944
+ <div class="toright2">
945
+ </div>
946
+ <div>
947
+
948
+ <input id="termActionsBtn" type=button title="Perform power actions on the device" onkeypress="return false" onkeydown="return false" value=Actions onclick=deviceActionFunction() />
949
+ </div>
950
+ </td>
951
+ </tr>
952
+ </table>
953
+ <div id=p12TermConsoleMsg style="display:none;cursor:pointer;position:absolute;left:30px;top:45px;color:yellow;background-color:rgba(0,0,0,0.6);padding:10px;border-radius:5px" onclick=p12clearConsoleMsg()></div>
954
+ </div>
955
+ </div>
956
<div id=p10files style="position:absolute;top:55px;bottom:0px;width:100%;display:none">
957
<table id="p13toolbar" style="width:100%;height:111px" cellpadding="0" cellspacing="0">
958
<tr>
@@ -1179,15 +1244,20 @@
1244
var StatusStrs = ["Disconnected", "Connecting...", "Setup...", "Connected", "Intel® AMT Connected"];
1245
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 x86-32bit", "Android x86", "PogoPlug ARM", "Android", "Linux Poky x86-32bit", "macOS x86-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", "MIPS24KC (OpenWRT)", "Apple Silicon", "FreeBSD x86-64", "Unknown", "Linux ARM 64 bit (glibc/2.24 NOKVM)", "Alpine Linux x86 64 Bit (MUSL)", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown", "MIPSEL24KC (OpenWRT)", "ARMADA/CORTEX-A53/MUSL (OpenWRT)"];
1246
var files;
1247
+ var terminal;
1248
var passRequirements = '{{{passRequirements}}}';
1249
if (passRequirements != '') { passRequirements = JSON.parse(decodeURIComponent(passRequirements)); }
1250
var sessionActivity = Date.now();
1251
var deskPinchZoom;
1252
var deskKeyboardShortcuts = [];
1253
var nightMode = setNightMode();
1254
+ var xterm = null;
1255
+ var xtermfit = null;
1256
+ var xtermResizeTimer = null;
1257
1258
// Console Message Display Timers
1259
var p11DeskConsoleMsgTimer = null;
1260
+ var p12TermConsoleMsgTimer = null;
1261
var p13FilesConsoleMsgTimer = null;
1262
1263
function startup() {
@@ -3316,6 +3386,7 @@
3386
3387
// Show node last 7 days timeline
3388
//drawDeviceTimeline();
3389
+ setupTerminal();
3390
setupFiles();
3391
if (meshrights & 16) { setupConsole(); }
3392
@@ -3389,6 +3460,7 @@
3460
QV('p10files', currentDevicePanel == 2);
3461
QV('p10details', currentDevicePanel == 3);
3462
QV('p10console', currentDevicePanel == 4);
3463
+ QV('p10terminal', currentDevicePanel == 5);
3464
var menus = [];
3465
if (currentDevicePanel != 0) { menus.push({ n: "General", f: 'setupDeviceMenu(0)' }); }
3466
@@ -3398,6 +3470,13 @@
3470
(((currentNode.agent == null) && ((typeof currentNode.intelamt.sku !== 'number') || ((currentNode.intelamt.sku & 8) != 0))) || (currentNode.agent && (currentNode.agent.caps & 1)))
3471
) { menus.push({ n: "Desktop", f: 'setupDeviceMenu(1)' }); }
3472
3473
+ if ((currentDevicePanel != 5) &&
3474
+ (urlargs.term == 1) &&
3475
+ (currentNode != null) &&
3476
+ ((meshrights & 8) || (meshrights & 256)) && ((meshrights == 0xFFFFFFFF) || ((meshrights & 65536) == 0)) &&
3477
+ (((currentNode.agent == null) && ((typeof currentNode.intelamt.sku !== 'number') || ((currentNode.intelamt.sku & 8) != 0))) || (currentNode.agent && (currentNode.agent.caps & 2)))
3478
+ ) { menus.push({ n: "Terminal", f: 'setupDeviceMenu(5)' }); }
3479
+
3480
if ((currentDevicePanel != 2) && (currentNode != null) && (meshrights & 8) && ((meshrights == 0xFFFFFFFF) || ((meshrights & 1024) == 0)) && ((currentNode.mtype == 2) && (currentNode.agent.caps & 4))) { menus.push({ n: "Files", f: 'setupDeviceMenu(2)' }); }
3481
if ((currentDevicePanel != 3) && (currentNode != null)) { menus.push({ n: "Details", f: 'setupDeviceMenu(3)' }); }
3482
if ((currentDevicePanel != 4) && (currentNode != null) && (meshrights & 0x00000010)) { menus.push({ n: "Console", f: 'setupDeviceMenu(4)' }); }
@@ -3766,6 +3845,7 @@
3845
}
3846
3847
function p11clearConsoleMsg() { QH('p11DeskConsoleMsg', ''); QV('p11DeskConsoleMsg', false); if (p11DeskConsoleMsgTimer) { clearTimeout(p11DeskConsoleMsgTimer); p11DeskConsoleMsgTimer = null; } }
3848
+ function p12clearConsoleMsg() { QH('p12TermConsoleMsg', ''); QV('p12TermConsoleMsg', false); if (p12TermConsoleMsgTimer) { clearTimeout(p12TermConsoleMsgTimer); p12TermConsoleMsgTimer = null; } }
3849
function p13clearConsoleMsg() { QH('p13FilesConsoleMsg', ''); QV('p13FilesConsoleMsg', false); if (p13FilesConsoleMsgTimer) { clearTimeout(p13FilesConsoleMsgTimer); p13FilesConsoleMsgTimer = null; } }
3850
3851
function onDesktopStateChange(xdesktop, state) {
@@ -4201,6 +4281,164 @@
4281
function dmousewheel(e) { setSessionActivity(); if ((!xxdialogMode && desktop != null) && desktop.m.mousewheel) { if (fullscreen) { e.addx = Q('p10desktop').scrollLeft * (1 / fullscreenzoom); e.addy = Q('p10desktop').scrollTop * (1 / fullscreenzoom); } desktop.m.mousewheel(e); haltEvent(e); return true; } return false; }
4282
function drotate(x) { if (!xxdialogMode && desktop != null) { desktop.m.setRotation(desktop.m.rotation + x); deskAdjust(); } }
4283
4284
+
4285
+ //
4286
+ // TERMINAL
4287
+ //
4288
+
4289
+ var terminalNode;
4290
+ function setupTerminal() {
4291
+ // Setup the terminal
4292
+ if ((terminalNode != currentNode) && (terminal != null)) { terminal.Stop(); terminal = null; }
4293
+ terminalNode = currentNode;
4294
+ updateTerminalButtons();
4295
+ }
4296
+
4297
+ // Show and enable the right buttons
4298
+ function updateTerminalButtons() {
4299
+ var mtype = (currentNode.agent == 1) ? 1 : 2;
4300
+ var termState = ((terminal != null) && (terminal.state != 0));
4301
+
4302
+ // Show the right buttons
4303
+ QV('disconnectbutton2span', (termState == true));
4304
+ QV('connectbutton2span', (termState == false) && (currentNode.agent != null) && (currentNode.agent.caps & 2));
4305
+
4306
+ // Enable buttons
4307
+ var online = ((terminalNode.conn & 1) != 0); // If Agent (1) connected, enable Terminal
4308
+ QE('connectbutton2', online);
4309
+ }
4310
+
4311
+ // Called when the terminal state changes
4312
+ function onTerminalStateChange(xterminal, state) {
4313
+ var xstate = state;
4314
+ if ((xstate == 3) && (xterminal.contype == 2)) { xstate++; }
4315
+ var str = StatusStrs[xstate];
4316
+ if (terminal.webRtcActive == true) { str += ", WebRTC"; }
4317
+ QH('termstatus', str);
4318
+ switch (state) {
4319
+ case 0:
4320
+ // Disconnected, clear the terminal
4321
+ QH('termtitle', '');
4322
+ xterm.dispose();
4323
+ xterm = xtermfit = null;
4324
+ if (terminal != null) { terminal.Stop(); terminal = null; }
4325
+ break;
4326
+ case 3:
4327
+ xterm.focus();
4328
+ break;
4329
+ default:
4330
+ //console.log('Unhandled onTerminalStateChange state', state);
4331
+ break;
4332
+ }
4333
+ updateTerminalButtons();
4334
+ }
4335
+
4336
+ // Handles a tunnel to a remote shell
4337
+ function CreateRemoteTunnel(onTunnelUpdate, options) {
4338
+ var obj = { protocol: 1 };
4339
+ if ((options != null) && (typeof options.protocol == 'number')) { obj.protocol = options.protocol; }
4340
+ obj.onTunnelUpdate = onTunnelUpdate;
4341
+ obj.xxStateChange = function (state) { }
4342
+ obj.ProcessBinaryData = function (data) { obj.onTunnelUpdate(data); }
4343
+ obj.ProcessData = function (data) { obj.onTunnelUpdate(data); }
4344
+ obj.terminalEmulation = 1;
4345
+ obj.fxEmulation = 0;
4346
+ obj.lineFeed = '\r\n';
4347
+ return obj;
4348
+ }
4349
+
4350
+ function tunnelUpdate(data) { if (typeof data == 'string') { xterm.writeUtf8(data); } else { xterm.writeUtf8(new Uint8Array(data)); } }
4351
+
4352
+ // Send the new terminal size to the agent
4353
+ function xTermSendResize() {
4354
+ xtermResizeTimer = null;
4355
+ if ((xterm != null) && (terminal != null) && (terminal.sendCtrlMsg != null)) { terminal.sendCtrlMsg(JSON.stringify({ ctrlChannel: '102938', type: 'termsize', cols: xterm.cols, rows: xterm.rows })); }
4356
+ }
4357
+
4358
+ function connectTerminal(e, contype, options) {
4359
+ p12clearConsoleMsg();
4360
+ if (!terminal) {
4361
+ // Terminal setup
4362
+ var termoptions = { protocol: ((options != null) && (typeof options.protocol == 'number')) ? options.protocol : 1 };
4363
+ if (options && options.requireLogin) { termoptions.requireLogin = true; }
4364
+ /*
4365
+ if ([1, 2, 3, 4, 21, 22].indexOf(currentNode.agent.id) == -1) {
4366
+ if (Q('termSizeList').value == 1) { termoptions.cols = 80; termoptions.rows = 25; termoptions.xterm = true; }
4367
+ else if (Q('termSizeList').value == 2) { termoptions.cols = 100; termoptions.rows = 30; termoptions.xterm = true; }
4368
+ else if (Q('termSizeList').value == 3) {
4369
+ // TODO: Try to improve terminal auto-size.
4370
+ termoptions.cols = Math.floor((Q('column_l').clientWidth - 60) / 10);
4371
+ termoptions.rows = Math.floor((Q('column_l').clientHeight - 120) / 20);
4372
+ termoptions.xterm = true;
4373
+ }
4374
+ }
4375
+
4376
+ // If shift is pressed
4377
+ if ((e && (e.shiftKey == true))) {
4378
+ if (currentNode.agent.id > 4) {
4379
+ if (termoptions.protocol == 1) { termoptions.protocol = 7; } // Switch to user shell
4380
+ } else {
4381
+ if (termoptions.protocol == 1) { termoptions.protocol = 6; } // Switch to Powershell
4382
+ }
4383
+ }
4384
+
4385
+ // If the server requires a shell type
4386
+ if ((serverinfo.linuxshell) != null && (currentNode.agent.id > 4)) {
4387
+ if (serverinfo.linuxshell == 'root') { termoptions.protocol = 1; delete termoptions.requireLogin; }
4388
+ if (serverinfo.linuxshell == 'user') { termoptions.protocol = 8; delete termoptions.requireLogin; }
4389
+ if (serverinfo.linuxshell == 'login') { termoptions.protocol = 1; termoptions.requireLogin = true; }
4390
+ }
4391
+ */
4392
+ // Setup a mesh agent xterm terminal
4393
+ QV('termarea3xdiv', true);
4394
+ QV('Term', false);
4395
+
4396
+ // Setup the terminal with auto-fit
4397
+ if (xterm != null) { xterm.dispose(); }
4398
+ xtermfit = new FitAddon.FitAddon();
4399
+ xterm = new Terminal();
4400
+ if (xtermfit) { xterm.loadAddon(xtermfit); }
4401
+ xterm.open(Q('termarea3xdiv')); // termarea3x
4402
+ xterm.onData(function (data) { if (terminal != null) { terminal.sendText(data); } })
4403
+ if (xtermfit) { xtermfit.fit(); }
4404
+ xterm.onTitleChange(function (title) { QH('termtitle', ' - ' + EscapeHtml(title)); });
4405
+ xterm.onResize(function (size) {
4406
+ // Despam resize
4407
+ if (xtermResizeTimer) clearTimeout(xtermResizeTimer);
4408
+ xtermResizeTimer = setTimeout(xTermSendResize, 200);
4409
+ });
4410
+
4411
+ // Setup a terminal tunnel to the agent
4412
+ terminal = CreateAgentRedirect(meshserver, CreateRemoteTunnel(tunnelUpdate, termoptions), serverPublicNamePort, authCookie, authRelayCookie, domainUrl);
4413
+ terminal.debugmode = debugmode;
4414
+ terminal.m.debugmode = debugmode;
4415
+ terminal.options = termoptions;
4416
+ terminal.options = { cols: xterm.cols, rows: xterm.rows };
4417
+ if (termoptions.requireLogin) { terminal.options.requireLogin = true; }
4418
+ terminal.Start(terminalNode._id);
4419
+ terminal.onStateChanged = onTerminalStateChange;
4420
+ terminal.contype = 1;
4421
+ terminal.attemptWebRTC = false; // Never do WebRTC on terminal, because of a race condition we can't do it.
4422
+ terminal.onConsoleMessageChange = function (server, msg) {
4423
+ if (terminal.consoleMessage) {
4424
+ Q('p12TermConsoleMsg').innerHTML += formatAgentConsoleMessage(terminal.consoleMessage, terminal.consoleMessageId, terminal.consoleMessageArgs);
4425
+ QV('p12TermConsoleMsg', true);
4426
+ if (p12TermConsoleMsgTimer != null) { clearTimeout(p12TermConsoleMsgTimer); }
4427
+ if (terminal.consoleMessageTimeout) { p12TermConsoleMsgTimer = setTimeout(p12clearConsoleMsg, terminal.consoleMessageTimeout * 1000); }
4428
+ } else {
4429
+ p12clearConsoleMsg();
4430
+ }
4431
+ };
4432
+ } else {
4433
+ //QH('Term', '');
4434
+ terminal.Stop();
4435
+ terminal = null;
4436
+ if (fullscreen) { deskToggleFull(); }
4437
+ }
4438
+ Q('connectbutton2').blur(); // Deselect the connect button so the button does not get key presses.
4439
+ }
4440
+
4441
+
4442
//
4443
// FILES
4444
//