dey-examples/connectcore-demo-example/static/js/console.js

388 lines
14 KiB
JavaScript

/*
* 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;
}
}
};
}