'use strict'; class CliEngine { constructor(currentTab) { this._currentTab = currentTab; this._lineDelayMs = 15; this._profileSwitchDelayMs = 100; this._cliBuffer = ""; this._window = null; this._windowWrapper = null; this._cliErrorsCount = 0; this._sendCommandsProgress = 0; this._onSendCommandsProgressChange = undefined; this._responseCallback = undefined; this._onRowCameCallback = undefined; } setUi(window, windowWrapper, textarea) { this._window = window; this._windowWrapper = windowWrapper; this._setTextareaListen(textarea); } get errorsCount() { return this._cliErrorsCount; } setProgressCallback(sendCommandsProgressCallBack) { this._onSendCommandsProgressChange = sendCommandsProgressCallBack; } _reportSendCommandsProgress(value) { this._sendCommandsProgress = value; this._onSendCommandsProgressChange?.(value); } enterCliMode() { const bufferOut = new ArrayBuffer(1); const bufView = new Uint8Array(bufferOut); this.cliBuffer = ""; bufView[0] = 0x23; serial.send(bufferOut); } _setTextareaListen(textarea) { // Tab key detection must be on keydown, // `keypress`/`keyup` happens too late, as `textarea` will have already lost focus. textarea.keydown(event => { if (event.which === CliEngine.s_tabCode) { // prevent default tabbing behaviour event.preventDefault(); } }); textarea.keypress(event => { if (event.which === CliEngine.s_enterKeyCode) { event.preventDefault(); // prevent the adding of new line const outString = textarea.val(); this.executeCommands(outString); textarea.val(''); } }); // give input element user focus textarea.focus(); } close(callback) { this.send(this.getCliCommand('exit\r', ""), function () { //this.cliBuffer if (callback) { callback(); } }); } executeCommands(outString) { const outputArray = outString.split("\n"); return this.executeCommandsArray(outputArray); } executeCommandsArray(strings) { this._reportSendCommandsProgress(0); const totalCommandsCount = strings.length; return Promise.reduce(strings, (delay, line, index) => { return new Promise((resolve) => { GUI.timeout_add('CLI_send_slowly', () => { let processingDelay = this.lineDelayMs; line = line.trim(); if (line.toLowerCase().startsWith('profile')) { processingDelay = this.profileSwitchDelayMs; } const isLastCommand = totalCommandsCount === index + 1; if (isLastCommand && this.cliBuffer) { line = this.getCliCommand(line, this.cliBuffer); } this.sendLine(line, ()=>{ /* empty on-send callback */ }, () => { resolve(processingDelay); this._reportSendCommandsProgress(100.0 * index / totalCommandsCount); }); }, delay); }); }, 0).then(() => { this._reportSendCommandsProgress(100); }); } removePromptHash(promptText) { return promptText.replace(/^# /, ''); } cliBufferCharsToDelete(command, buffer) { let commonChars = 0; for (let i = 0; i < buffer.length; i++) { if (command[i] === buffer[i]) { commonChars++; } else { break; } } return buffer.length - commonChars; } commandWithBackSpaces(command, buffer, noOfCharsToDelete) { const backspace = String.fromCharCode(127); return backspace.repeat(noOfCharsToDelete) + command.substring(buffer.length - noOfCharsToDelete, command.length); } getCliCommand(command, cliBuffer) { const buffer = this.removePromptHash(cliBuffer); const bufferRegex = new RegExp(`^${buffer}`, 'g'); if (command.match(bufferRegex)) { return command.replace(bufferRegex, ''); } const noOfCharsToDelete = this.cliBufferCharsToDelete(command, buffer); return this.commandWithBackSpaces(command, buffer, noOfCharsToDelete); } writeToOutput(text) { this._windowWrapper.append(text); this._window.scrollTop(this._windowWrapper.height()); } writeLineToOutput(text) { if (text.startsWith("###ERROR")) { this.writeToOutput(`${text}
`); this._cliErrorsCount++; } else { this.writeToOutput(`${text}
`); } this._responseCallback?.(); this._onRowCameCallback?.(text); } subscribeOnRowCame(callback) { this._onRowCameCallback = callback; } unsubscribeOnRowCame() { this._onRowCameCallback = undefined; } readSerial(readInfo) { /* Some info about handling line feeds and carriage return line feed = LF = \n = 0x0A = 10 carriage return = CR = \r = 0x0D = 13 MAC only understands CR Linux and Unix only understand LF Windows understands (both) CRLF Chrome OS currently unknown */ const data = new Uint8Array(readInfo.data); let validateText = ""; let sequenceCharsToSkip = 0; for (const charCode of data) { const currentChar = String.fromCharCode(charCode); if (!CONFIGURATOR.cliEngineValid) { // try to catch part of valid CLI enter message validateText += currentChar; this.writeToOutput(currentChar); continue; } const escapeSequenceCode = 27; const escapeSequenceCharLength = 3; if (charCode === escapeSequenceCode && !sequenceCharsToSkip) { // ESC + other sequenceCharsToSkip = escapeSequenceCharLength; } if (sequenceCharsToSkip) { sequenceCharsToSkip--; continue; } this._adjustCliBuffer(charCode); if (this.cliBuffer === 'Rebooting' && CliEngine.s_backspaceCode !== charCode) { CONFIGURATOR.cliEngineActive = false; CONFIGURATOR.cliEngineValid = false; GUI.log(i18n.getMessage('cliReboot')); reinitializeConnection(this._currentTab); } } if (!CONFIGURATOR.cliEngineValid && validateText.indexOf('CLI') !== -1) { GUI.log(i18n.getMessage('cliEnter')); CONFIGURATOR.cliEngineValid = true; } } sendLine(line, callback, responseCallback) { this.send(`${line}\n`, callback, responseCallback); } send(line, callback, responseCallback) { this._responseCallback = responseCallback; const bufferOut = new ArrayBuffer(line.length); const bufView = new Uint8Array(bufferOut); for (let cKey = 0; cKey < line.length; cKey++) { bufView[cKey] = line.charCodeAt(cKey); } serial.send(bufferOut, callback); } _adjustCliBuffer(newCharacterCode) { const currentChar = String.fromCharCode(newCharacterCode); switch (newCharacterCode) { case CliEngine.s_lineFeedCode: if (GUI.operating_system === "Windows") { this.writeLineToOutput(this.cliBuffer); this.cliBuffer = ""; } break; case CliEngine.s_carriageReturnCode: if (GUI.operating_system !== "Windows") { this.writeLineToOutput(this.cliBuffer); this.cliBuffer = ""; } break; case 60: this.cliBuffer += '<'; break; case 62: this.cliBuffer += '>'; break; case CliEngine.s_backspaceCode: this.cliBuffer = this.cliBuffer.slice(0, -1); break; default: this.cliBuffer += currentChar; } } } CliEngine.s_backspaceCode = 8; CliEngine.s_lineFeedCode = 10; CliEngine.s_carriageReturnCode = 13; CliEngine.s_tabCode = 9; CliEngine.s_enterKeyCode = 13; CliEngine.s_commandDiffAll = "diff all"; CliEngine.s_commandDefaultsNoSave = "defaults nosave"; CliEngine.s_commandSave = "save"; CliEngine.s_commandExit = "exit";