<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Terminal</title> <link rel="stylesheet" href="/_ssh/xterm.css" /> <style> body { margin: 0; height: 100%; } #terminal-container { display: flex; flex-direction: column; width: 100%; height: 100vh; } #terminal-info-container { vertical-align: middle; padding: 3px; } #terminal-target { display: inline-block; height: 100%; vertical-align: middle; } #terminal-status { display: inline-block; font-weight: bold; height: 100%; vertical-align: middle; } #terminal-errmsg { display: inline-block; color: red; height: 100%; vertical-align: middle; } #terminal-control { display: inline-block; height: 100%; vertical-align: middle; } #terminal-control button { vertical-align: middle; } #terminal-view-container { flex: 1; width: 100%; min-height: 0; /*height: 100%;*/ } #login-container { display: flex; justify-content: center; align-items: center; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 99999; } #login-form-container { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); text-align: center; } #login-form-title { line-height: 2em; font-size: 2em; font-weight: bold; } </style> <script src="/_ssh/xterm.js"></script> <script src="/_ssh/xterm-addon-fit.js"></script> <script> window.onload = function(event) { const terminal_container = document.getElementById('terminal-container'); const terminal_target = document.getElementById('terminal-target'); const terminal_status = document.getElementById('terminal-status'); const terminal_errmsg = document.getElementById('terminal-errmsg'); const terminal_view_container = document.getElementById('terminal-view-container'); const terminal_disconnect = document.getElementById('terminal-disconnect'); const login_container = document.getElementById('login-container'); const login_form_title = document.getElementById('login-form-title'); const login_form = document.getElementById('login-form'); const username_field = document.getElementById('username'); const password_field= document.getElementById('password'); const term = new window.Terminal(); const fit_addon = new window.FitAddon.FitAddon(); const text_decoder = new TextDecoder(); term.loadAddon(fit_addon) term.open(terminal_view_container); let set_terminal_target = function(name) { terminal_target.innerHTML = name; login_form_title.innerHTML = name } let set_terminal_status = function(msg, errmsg) { if (msg != null) terminal_status.innerHTML = msg; if (errmsg != null) terminal_errmsg.innerHTML = errmsg } let adjust_terminal_size_unconnected = function() { fit_addon.fit(); } let fetch_session_info = async function() { let url = window.location.protocol + '//' + window.location.host + '/_ssh/server-conns/{{ .ConnId }}//routes/{{ .RouteId }}'; try { const resp = await fetch(url); if (!resp.ok) throw new Error(`HTTP error in getting route({{ .ConnId }}, {{ .RouteId }}) info - status ${resp.status}`); const route = await resp.json() if ('client-peer-name' in route) { set_terminal_target(route['client-peer-name']) // change to the name document.title = route['client-peer-name'] } } catch (e) { set_terminal_target(''); document.title = ''; set_terminal_status (null, e); } } let toggle_login_form = function(visible) { if (visible) fetch_session_info(); login_container.style.visibility = (visible? 'visible': 'hidden'); terminal_disconnect.style.visibility = (visible? 'hidden': 'visible'); if (visible) username_field.focus(); else term.focus(); } toggle_login_form(true); window.onresize = adjust_terminal_size_unconnected; adjust_terminal_size_unconnected() login_form.onsubmit = async function(event) { event.preventDefault(); toggle_login_form(false) const username = username_field.value.trim(); const password = password_field.value.trim(); let prefix = window.location.protocol === 'https:' ? 'wss://' : 'ws://'; const socket = new WebSocket(prefix + window.location.host + '/_ssh-ws/{{ .ConnId }}/{{ .RouteId }}'); socket.binaryType = 'arraybuffer'; set_terminal_status('Connecting...', ''); const adjust_terminal_size_connected = function() { fit_addon.fit(); if (socket.readyState == 1) // if open socket.send(JSON.stringify({ type: 'size', data: [term.rows.toString(), term.cols.toString()] })); }; socket.onopen = function () { socket.send(JSON.stringify({ type: 'open', data: [username, password]})); }; socket.onmessage = function(event) { try { let event_text; event_text = (typeof event.data === 'string')? event.data: text_decoder.decode(new Uint8Array(event.data)); const msg = JSON.parse(event_text); if (msg.type == "iov") { for (const data of msg.data) term.write(data); } else if (msg.type == "status") { if (msg.data.length >= 1) { if (msg.data[0] == 'opened') { set_terminal_status('Connected', ''); adjust_terminal_size_connected() term.clear() } else if (msg.data[0] == 'closed') { // doesn't really matter // socket.onclose() will be executed anyway } } } else if (msg.type == "error") { toggle_login_form(true); window.onresize = adjust_terminal_size_unconnected; set_terminal_status(null, msg.data.join(' ')) } } catch (e) { set_terminal_status('Disconnected', e); toggle_login_form(true) window.onresize = adjust_terminal_size_unconnected; } }; socket.onerror = function(event) { set_terminal_status('Disconnected', event); toggle_login_form(true) window.onresize = adjust_terminal_size_unconnected; }; socket.onclose = function() { set_terminal_status('Disconnected', null); toggle_login_form(true) window.onresize = adjust_terminal_size_unconnected; }; term.onData(function(data) { if (socket.readyState == 1) // if open socket.send(JSON.stringify({ type: 'iov', data: [data] })); }); window.onresize = adjust_terminal_size_connected; terminal_disconnect.onclick = function(event) { socket.send(JSON.stringify({ type: 'close', data: [""] })); //socket.close() }; } }; </script> </head> <body> <div id="login-container"> <div id="login-form-container"> <div id="login-form-title"></div> <form id="login-form"> <label> Username: <input type="text" id="username" required /> </label> <br /><br /> <label> Password: <input type="password" id="password" required /> </label> <br /><br /> <button type="submit">Connect</button> </form> </div> </div> <div id="terminal-container"> <div id="terminal-info-container"> <div id="terminal-target"></div> <div id="terminal-status"></div> <div id="terminal-errmsg"></div> <div id="terminal-control"> <button id="terminal-disconnect" type="button">Disconnect</button> </div> </div> <div id="terminal-view-container"></div> </div> </body> </html>