/* * Copyright 2022, Digi International Inc. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ // Constants. const CONSOLE_CURSOR_BLINK = true; const CONSOLE_NUM_ROWS = 30; const CONSOLE_SCROLLBACK = 1000; const CONSOLE_TAB_STOP_WIDTH = 8; const CONSOLE_MAX_SEND_COMMANDS = 5; const CONSOLE_SEND_COMMANDS_DELAY = 150; const ID_CONSOLE_CLOSE_BUTTON = "console_close_button"; const ID_CONSOLE_CONTAINER = "console_container"; const ID_CONSOLE_ERROR_RECONNECT = "console_error_reconnect"; const ID_CONSOLE_ERROR_TEXT = "console_error_text"; const ID_CONSOLE_HEADER = "console_header"; const ID_CONSOLE_HOVER_BACKGROUND = "console_hover_background"; const ID_CONSOLE_LOADING = "console_loading"; const ID_CONSOLE_PANEL = "console_panel"; const ID_CONSOLE_RECONNECT_BUTTON = "console_reconnect_button"; const ID_CONSOLE_VIEWPORT = "xterm-viewport"; const CLI_MESSAGE_TYPE_DATA = "data"; const CLI_MESSAGE_TYPE_ERROR = "error"; const CLI_MESSAGE_TYPE_START = "start"; const CLI_MESSAGE_TYPE_TERMINATE = "terminate"; const ERROR_SESSION_CLOSED_REMOTELY = "Session closed from the remote end"; const ERROR_SESSION_CLOSING = "Previous console session is closing, please wait a moment..."; // Variables. var term; var cliSessionID; var cliSocket; var cliSessionTerminating = false; var fitAddon; var commandsToSend = []; var cliTerminateFlagTimer = null; var consoleResizeObserver = null; // Opens the console. function openConsole() { // Check if console is showing. if (isConsoleShowing()) return; // Check if previous session is terminating. if (cliSessionTerminating) { toastr.info(ERROR_SESSION_CLOSING); return; } // Initialize variables. var consoleDiv = document.getElementById(ID_CONSOLE); // Remove any residual terminals. while (consoleDiv.firstChild) consoleDiv.removeChild(consoleDiv.lastChild); // Create the terminal instance. term = new Terminal({ cursorBlink: CONSOLE_CURSOR_BLINK, scrollback: CONSOLE_SCROLLBACK, tabStopWidth: CONSOLE_TAB_STOP_WIDTH, rows: CONSOLE_NUM_ROWS }); // Configure terminal input data event. term.onData( (data) => { sendCLIData(data); }); // Load Fit addon. fitAddon = new FitAddon.FitAddon(); term.loadAddon(fitAddon); // Attach the terminal to the div. term.open(consoleDiv); // Setup resize observer. if (consoleResizeObserver == null || consoleResizeObserver == "undefined") { consoleResizeObserver = new ResizeObserver((entries) => { adjustConsoleSize(); }) consoleResizeObserver.observe(document.getElementById(ID_CONSOLE_CONTAINER)); } // Show the console. var consolePanel = document.getElementById(ID_CONSOLE_PANEL); consolePanel.style.visibility = "visible"; consolePanel.style.display = "flex"; // Adjust console size after some time to give time to the terminal to load. window.setTimeout(function () { adjustConsoleSize(); }, 100); adjustConsoleSize(); // Initialize the CLI session. initCLISession(); } // Adjusts the console size. function adjustConsoleSize() { // Sanity checks. if (!isConsoleShowing()) return; // Initialize variables. var consoleContainer = document.getElementById(ID_CONSOLE_CONTAINER); var consoleViewPort = document.getElementsByClassName(ID_CONSOLE_VIEWPORT)[0]; var consoleHeader = document.getElementById(ID_CONSOLE_HEADER); var consoleHoverBackground = document.getElementById(ID_CONSOLE_HOVER_BACKGROUND); // Adjust terminal size. if (fitAddon != null && fitAddon != "undefined") fitAddon.fit(); // Obtain dimensions. var consoleHeight = consoleViewPort.clientHeight; var headerHeight = consoleHeader.clientHeight; // Set size values. consoleContainer.style.height = (consoleHeight + headerHeight).toString() + "px"; consoleHoverBackground.style.height = (consoleContainer.clientHeight - headerHeight).toString() + "px"; consoleViewPort.style.width = "initial"; } // Returns whether the console window is open or not. function isConsoleShowing() { // Sanity checks. if (!isDashboardShowing()) return false; // Initialize variables. var consolePanel = document.getElementById(ID_CONSOLE_PANEL); // Return whether the console is showing or not. return consolePanel.style.visibility == "visible"; } // Shows/hides the console loading panel. function showConsoleLoading(visible) { // Initialize variables. var consoleHoverBackground = document.getElementById(ID_CONSOLE_HOVER_BACKGROUND); var consoleLoading = document.getElementById(ID_CONSOLE_LOADING); // Show/hide the loading panel. if (visible && isConsoleShowing()) { consoleHoverBackground.style.visibility = "visible"; consoleLoading.style.visibility = "visible"; } else { consoleHoverBackground.style.visibility = "hidden"; consoleLoading.style.visibility = "hidden"; } } // Shows/hides the console error with reconnect panel. function showConsoleErrorReconnect(visible, error) { // Initialize variables. var consoleHoverBackground = document.getElementById(ID_CONSOLE_HOVER_BACKGROUND); var consoleErrorReconnect = document.getElementById(ID_CONSOLE_ERROR_RECONNECT); var consoleErrorText = document.getElementById(ID_CONSOLE_ERROR_TEXT); var reconnectButton = document.getElementById(ID_CONSOLE_RECONNECT_BUTTON); var closeButton = document.getElementById(ID_CONSOLE_CLOSE_BUTTON); // Show/hide the error panel. if (visible && isConsoleShowing()) { consoleHoverBackground.style.visibility = "visible"; consoleErrorReconnect.style.visibility = "visible"; reconnectButton.style.visibility = "visible"; closeButton.style.visibility = "visible"; if (error == null) error = ERROR_UNKNOWN_ERROR; consoleErrorText.innerText = error; } else { consoleHoverBackground.style.visibility = "hidden"; consoleErrorReconnect.style.visibility = "hidden"; reconnectButton.style.visibility = "hidden"; closeButton.style.visibility = "hidden"; } } // Initializes a CLI session. function initCLISession() { // Hide error with reconnect panel. showConsoleErrorReconnect(false); // Clear the terminal. term.clear(); // Show loading panel. showConsoleLoading(true); $.post( "../ajax/cli_init_session", function(data) { // Process only if the console is showing. if (!isConsoleShowing()) return; // Process answer. processInitCLISessionResponse(data); } ).fail(function(response) { // Process only if the console is showing. if (!isConsoleShowing()) return; // Process error. var error = processAjaxErrorResponse(response); // Hide the loading status. showConsoleLoading(false); // Show error with reconnect panel. showConsoleErrorReconnect(true, error); }); } // Processes the init CLI session response. function processInitCLISessionResponse(response) { // Check if there was any error in the request. if (checkErrorResponse(response, false)) { // Hide the loading status. showConsoleLoading(false); // Show error with reconnect panel. showConsoleErrorReconnect(true, error); } else { // Store session ID. cliSessionID = response[ID_SESSION_ID]; // Register the TCP monitor. subscribeCLISession(cliSessionID); } } // Sends the given CLI data. function sendCLIData(data) { // Check if CLI session exists. if (cliSessionID == null) return; // Add the command to the buffer. commandsToSend.push(data); // If the send timer is scheduled and there is enough data to send, // return to let the timer to complete. if (sendTimerScheduled && commandsToSend.length > CONSOLE_MAX_SEND_COMMANDS) return; if (sendTimer != null && sendTimer != "undefined") { clearTimeout(sendTimer); sendTimer = null; } // Schedule the timer. sendTimer = setTimeout(function() { // Clear timer flag. sendTimerScheduled = false; // Build data. var dataToSend = btoa(commandsToSend.join("")); commandsToSend = []; $.post( "../ajax/cli_data", JSON.stringify({ "session_id": cliSessionID, "data": dataToSend }), function(data) { // Process only if the console is showing. if (!isConsoleShowing()) return; // Process answer. processCLISendDataResponse(data); } ).fail(function(response) { // Process only if the console is showing. if (!isConsoleShowing()) return; // Process error. processAjaxErrorResponse(response); }); }, CONSOLE_SEND_COMMANDS_DELAY); // Set timer flag. sendTimerScheduled = true; } // Processes the send CLI data response. function processCLISendDataResponse(response) { // Check if there was any error in the request. checkErrorResponse(response, false); } // Closes the console. function closeConsole() { // Check if console is showing. if (!isConsoleShowing()) return; // Hide the loading panel. showConsoleLoading(false); // Hide the error reconnect panel. showConsoleErrorReconnect(false); // Terminate the CLI session. terminateCLIsession(); // Hide the console. var consolePanel = document.getElementById(ID_CONSOLE_PANEL); consolePanel.style.visibility = "collapse"; consolePanel.style.display = "none"; } // Terminates the CLI session. function terminateCLIsession() { // Check if CLI session exists. if (cliSessionID == null) return; // Flag session terminating. cliSessionTerminating = true; cliTerminateFlagTimer = window.setTimeout(function () { cliSessionTerminating = false; }, 30000); // In the case the terminate request hangs, set a timeout to manually remove the flag. // Send request to terminate CLI session. $.post( "../ajax/cli_terminate_session", JSON.stringify({ "session_id": cliSessionID }), function(data) { // Process only if the console is showing. if (!isConsoleShowing()) return; // Process answer. processTerminateCLISessionResponse(data); } ).fail(function(response) { // Un-flag session terminating. cliSessionTerminating = false; window.clearTimeout(cliTerminateFlagTimer); // Process only if the console is showing. if (!isConsoleShowing()) return; // Process error. processAjaxErrorResponse(response); }); // Clear CLI session ID. cliSessionID = null; } // Processes the terminate CLI session response. function processTerminateCLISessionResponse(response) { // Check if there was any error in the request. if (checkErrorResponse(response, false)) { // Un-flag session terminating. cliSessionTerminating = false; window.clearTimeout(cliTerminateFlagTimer); } } // Subscribes to any CLI session change. function subscribeCLISession(sessionID) { // Create the web socket. //var socketPrefix = window.location.protocol == "https:" ? "wss" : "ws"; //cliSocket = new WebSocket(socketPrefix + "://" + window.location.host + "/ws/cli/" + device.getDeviceID() + "/" + sessionID); // Define the callback to be notified when data is received in the web socket. cliSocket.onmessage = function(e) { if (isDashboardShowing() && term != null && term != "undefined") { var event = JSON.parse(e.data); var type = event[ID_TYPE]; switch (type) { case CLI_MESSAGE_TYPE_DATA: var data = event[ID_DATA]; var decodedData = atob(data); if (isConsoleShowing()) term.write(decodedData); break; case CLI_MESSAGE_TYPE_TERMINATE: case CLI_MESSAGE_TYPE_ERROR: cliSessionID = null; if (cliSocket != null && cliSocket != "undefined") cliSocket.close(); // Hide the loading status. showConsoleLoading(false); // Check specific type. var error = null; if (type == CLI_MESSAGE_TYPE_TERMINATE && isConsoleShowing()) error = ERROR_SESSION_CLOSED_REMOTELY; else if (type == CLI_MESSAGE_TYPE_ERROR) error = event[ID_ERROR]; if (isConsoleShowing()) showConsoleErrorReconnect(true, error); if (error != null && (type == CLI_MESSAGE_TYPE_ERROR || (type == CLI_MESSAGE_TYPE_TERMINATE && isConsoleShowing()))) toastr.error(error); // Un-flag session terminating. cliSessionTerminating = false; window.clearTimeout(cliTerminateFlagTimer); break; case CLI_MESSAGE_TYPE_START: // Hide the loading status. showConsoleLoading(false); break; } } }; }