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">&nbsp;<input type="button" id="disconnectbutton2" value="Disconnect" onclick=connectTerminal(event,0) onkeypress="return false" onkeydown="return false" /></span>
932 + &nbsp;<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 + &nbsp;
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&reg; 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 //