diff --git a/vscode-plugin/media/wizard.html b/vscode-plugin/media/wizard.html index 7e7a0f72c..c18755ee9 100644 --- a/vscode-plugin/media/wizard.html +++ b/vscode-plugin/media/wizard.html @@ -1,6 +1,13 @@ + + @@ -13,47 +20,45 @@
-
-
- - -
-

- UTBot failed to establish connection with specified server. - If you wish to continue anyway, press "Continue" button. -

-

In any case, you will need to specify correct port and host of UTBot server to use the extension. - You can do it via Visual Studio Code Settings.

-
- -
+ defaultGRPCPort="{{defaultGRPCPort}}" + defaultSFTPPort="{{defaultSFTPPort}}" + serverHost="{{serverHost}}" + serverDir="{{serverDir}}">

UTBot: Quickstart

-
+

πŸ‘‹ Welcome to "UTBot: Quickstart" Wizard!

UTBot discovered that this is the first time you use it with this project. - The Wizard will help you to configure the extension appropriatly. + The Wizard will help you to configure the extension appropriately. In case you don't wish to proceed, you can close this wizard at any time.

In order to learn more about UTBot C/C++, please, refer to this - manual.

+ manual.

+

To work correctly, UTBot need the external plugins + + + + + + + + + +
+ SFTP + + βœ”οΈSFTP plugin is configured + ❌ Please, install or enable the SFTP plugin and restart the wizard! +
+ Sarif Viewer + + βœ”οΈSARIF plugin is configured + ❌ Please, install or enable the SARIF plugin and restart the wizard! +
+ Please, install or activate the plugins before continuing. +

@@ -86,18 +91,10 @@

πŸ–₯️ Server Setup

πŸ“Ά Connection - - ⏳ Connecting... - βœ”οΈ Successfully pinged server! - - ❌ Failed to establish connection! -
Fill the parameters below accordingly to the ones specified during the - UTBot Server installation. - Please make sure that in case of remote host the SFTP plugin - has a consistent host. + UTBot Server installation.
-
- - + + + + + + + + + +
+ + + + ⏳ Connecting... + βœ”οΈ Successfully pinged GRPC server! + + ❌ Failed to establish GRPC connection! +
+ + + + ⏳ Connecting SFTP... + βœ”οΈ Successfully pinged SFTP server! + SFTP synchronization is not used. + ❌ Failed to establish SFTP connection! +
+
+ ATTENTION! Please leave the SFTP port field empty if you do not want to synchronize + the project file system between client and server. By clicking the "Next" button with + the SFTP port field filled in, you will start the synchronization of the project file system + between the client and the server.

- - πŸ“ Project Path On Server - - - - - - - -
- Project path on server specifies the path to the project on a server host. - On the remote server the path need to be synchronized with the project path on the client (local) by - SFTP plugin. - Please make sure that the SFTP plugin has a consistent remotePath. - The localhost server don't need the synchronization. -
- + + Project path on server specifies the path to the project's copy on the server host. + May point to a directory shared by the client and server. In this case, SFTP synchronization + must be disabled by leaving the SFTP port field empty.
@@ -186,12 +211,12 @@

πŸ“‘οΈ Project Setup

diff --git a/vscode-plugin/media/wizard.js b/vscode-plugin/media/wizard.js deleted file mode 100644 index 78429a81c..000000000 --- a/vscode-plugin/media/wizard.js +++ /dev/null @@ -1,377 +0,0 @@ -window.addEventListener('message', event => { - const message = event.data; // The JSON data our extension sent - switch (message.command) { - case 'test_connection_success': - showConnectionSuccessStatus(message.clientVersion, message.serverVersion); - break; - case 'test_connection_failure': - showConnectionFailureStatus(); - break; - case 'check_connection_success': - showConnectionSuccessStatus(message.clientVersion, message.serverVersion); - nextStep(); - break; - case 'check_connection_failure': - showConnectionFailureStatus(); - openModal(); - break; - default: - console.log(`ERROR: Unknown message: ${message.command}`); - break; - } -}); - -function $(id) { - return document.getElementById(id); -} - -/* - * Communication with VSCode via API - */ -const vscode = acquireVsCodeApi(); -function DbgMessage(message) { - vscode.postMessage({ - command: 'dbg_message', - message: message - }); -} - -const os = getVarValue("os"); -console.log(`OS Platform: ${os}`); - -let nonLocalGRPCPort = getVarValue("defaultPort"); -let nonLocalHost = getVarValue("sftpHost"); -let nonLocalRemoteDir = getVarValue("sftpDir"); -let currentTabIndex = 0; - -function onStart() { - DbgMessage("OnStart"); - const hostInput = $('hostInput'); - const portInput = $('portInput'); - const mappingInput = $('mappingInput'); - if (hostInput.value === "localhost" - && portInput.value === getVarValue("defaultPort") - && mappingInput.value === getVarValue("projectDir")) { - $('useLocalHost').checked = true; - hostInput.disabled = true; - portInput.disabled = true; - mappingInput.disabled = true; - } - if (os === 'win32') { - removeElementByClassName('installer-tab'); - removeElementByClassName('installer-step'); - } - showTab(currentTabIndex); -} - -onStart(); - -function showTab(tabIndex) { - const prevButton = $("prevBtn"); - const nextButton = $("nextBtn"); - - const tabElements = document.getElementsByClassName("utbot-form__tab"); - const currentTabElement = tabElements[tabIndex]; - currentTabElement.classList.add('active'); - - // Show previous button if necessary - if (tabIndex === 0) { - prevButton.classList.remove('active'); - } else { - prevButton.classList.add('active'); - } - - // Set nextButton title according to tab number - if (tabIndex === tabElements.length - 1) { - nextButton.innerHTML = "Finish"; - } else { - nextButton.innerHTML = "Next"; - } - fixStepIndicator(tabIndex); -} - -let checkHostTimer = null; -function showProgress(toShow) { - showElement($("connection_loader"), toShow); - if (toShow) { - showElement($("connection_success"), false); - showElement($("connection_warning"), false); - showElement($("connection_failure"), false); - } -} - -function showElement(el, toShow) { - if (toShow) { - el.classList.add('active'); - } - else { - el.classList.remove('active'); - } -} - -function restartCheckingConnection() { - if (checkHostTimer != null) { - clearTimeout(checkHostTimer); - checkHostTimer = null; - showProgress(false); - } - checkHostTimer = setTimeout(testConnection, 250, false); - showProgress(true); -} - -function isConnectionTab() { - const tab = document.getElementsByClassName("connection-tab"); - if (!tab || tab.length !== 1) { - console.log("Something is wrong: connection-tab"); - return; - } - return tab[0].classList.contains("active"); -} - -function nextButtonHandler() { - if (isConnectionTab()) { - testConnection(true); - } - else { - nextStep(); - if (isConnectionTab()) { - testConnection(false); - } - } -} - -function prevButtonHandler() { - prevStep(); -} - -function nextStep() { - return nextPrev(1); -} - -function prevStep() { - return nextPrev(-1); -} - -function showConnectionSuccessStatus(clientVersion, serverVersion) { - showProgress(false); - if (clientVersion !== serverVersion) { - const connectionWarning = $("connection_warning"); - connectionWarning.innerText = - connectionWarning.getAttribute("format").toString() - + " " - + `Server: ${serverVersion}, Client: ${clientVersion}`; - showElement($("connection_success"), false); - showElement(connectionWarning, true); - } - else { - showElement($("connection_success"), true); - showElement($("connection_warning"), false); - } - showElement($("connection_failure"), false); -} - -function showConnectionFailureStatus() { - showProgress(false); - showElement($("connection_success"), false); - showElement($("connection_warning"), false); - showElement($("connection_failure"), true); -} - -function showConnectionLoadingStatus() { - showProgress(true); -} - -function nextPrev(tabShift) { - if (tabShift !== 1 && tabShift !== -1) { - return false; - } - let tabElements = document.getElementsByClassName("utbot-form__tab"); - let currentTabElement = tabElements[currentTabIndex]; - if (tabShift === 1 && !validateForm()) { - return false; - } - let actionToPerform = currentTabElement.getAttribute("vs-message-callback"); - if (actionToPerform) { - eval(actionToPerform) - } - currentTabElement.classList.remove('active'); - - currentTabIndex = currentTabIndex + tabShift; - if (currentTabIndex >= tabElements.length) { - closeWizard(); - return false; - } - - restoreOriginalStateOfSomeElements(); - showTab(currentTabIndex); -} - -function validateForm() { - // This function deals with validation of the form fields - let valid = true; - let tabElements = document.getElementsByClassName("utbot-form__tab"); - let tabInputs = tabElements[currentTabIndex].getElementsByTagName("input"); - for (let i = 0; i < tabInputs.length; i++) { - let tabInput = tabInputs[i]; - if (tabInput.value === "") { - tabInput.value = tabInput.placeholder; - } - if (tabInput.value === "" || !tabInput.checkValidity()) { - // TODO: custom validators - tabInput.classList.add("invalid"); - valid = false; - } - } - if (valid) { - document.getElementsByClassName("utbot-form__steps_step")[currentTabIndex].classList.add("finish"); - } - return valid; -} - -function fixStepIndicator(tabIndex) { - let steps = document.getElementsByClassName("utbot-form__steps_step"); - for (let i = 0; i < steps.length; i++) { - steps[i].classList.remove("active"); - if (i > tabIndex) { - steps[tabIndex].classList.remove('finish'); - } - } - steps[tabIndex].classList.add("active"); -} - -function closeModal() { - let modal = document.getElementsByClassName("utbot-modal"); - if (!modal || modal.length !== 1) { - console.log("Something is wrong, can't close modal"); - return; - } - modal[0].classList.remove("active"); -} - -function openModal() { - let modal = document.getElementsByClassName("utbot-modal"); - if (!modal || modal.length !== 1) { - console.log("Something is wrong, can't open modal"); - return; - } - modal[0].classList.add("active"); -} - -function closeModalAndGoToNextStep() { - closeModal(); - nextStep(); -} - -function restoreOriginalStateOfSomeElements() { - const button = $("runInstallerBtn"); - const messageBlock = document.getElementsByClassName("utbot-form__tab_installer_message")[0] - if (button !== undefined && button !== null) { - button.disabled = false; - } - if (messageBlock !== undefined && messageBlock !== null) { - messageBlock.classList.remove("active"); - } -} - -function getVarValue(name) { - DbgMessage("var = " + document.getElementsByClassName("utbot-vars")[0].getAttribute(name)); - return document.getElementsByClassName("utbot-vars")[0].getAttribute(name); -} - -function removeElementByClassName(className) { - const removableElements = document.getElementsByClassName(className); - for (let i = 0; i < removableElements.length; i++) { - const parentNode = removableElements[i].parentNode; - if (parentNode !== undefined || parentNode !== null) { - parentNode.removeChild(removableElements[i]); - } - } -} - -function handleOnOffDefaultConfigurationOnLocalhost() { - const mappingInput = $('mappingInput'); - const hostInput = $('hostInput'); - const portInput = $('portInput'); - const useLocalhost = $('useLocalHost').checked; - if (useLocalhost) { - DbgMessage("Local host is used!"); - - nonLocalHost = hostInput.value; - hostInput.value = "localhost"; - - nonLocalGRPCPort = portInput.value; - portInput.value = getVarValue("defaultPort"); - - nonLocalRemoteDir = mappingInput.value; - mappingInput.value = getVarValue("projectDir"); - } - else { - DbgMessage("Local host is not used!"); - hostInput.value = nonLocalHost; - portInput.value = nonLocalGRPCPort; - mappingInput.value = nonLocalRemoteDir; - } - mappingInput.disabled= useLocalhost; - hostInput.disabled = useLocalhost; - portInput.disabled = useLocalhost; - - restartCheckingConnection(); -} - -function sendServerSetup() { - vscode.postMessage({ - command: 'set_server_setup', - host: $('hostInput').value, - port: parseInt($('portInput').value), - mappingPath: $('mappingInput').value, - }); -} - -function sendBuildInfo() { - const buildDirInputElement = $('buildDirectory'); - const cmakeOptionsInputElement = $('cmakeOptions'); - vscode.postMessage({ - command: 'set_build_info', - buildDirectory: buildDirInputElement.value, - cmakeOptions: cmakeOptionsInputElement.value - }); -} - -/** - * Request to test the connection with host - */ -function testConnection(withNextContinuation) { - const hostInputElement = $('hostInput'); - const portInputElement = $('portInput'); - showProgress(true); - vscode.postMessage({ - command: (withNextContinuation ? 'check_connection' : 'test_connection'), - host: hostInputElement.value, - port: parseInt(portInputElement.value) - }); -} - -/** - * Request to run installation script. - */ -function runInstaller() { - const button = $("runInstallerBtn"); - const messageBlock = document.getElementsByClassName("utbot-form__tab_installer_message")[0] - button.disabled = true; - messageBlock.classList.add("active"); - vscode.postMessage({ - command: 'run_installer' - }); -} - -function closeWizard() { - vscode.postMessage({ command: 'close_wizard' }); -} - -function openSFTPSettings(paramKey) { - DbgMessage("openSFTPSettings"); - vscode.postMessage({ - command: 'openSFTPSettings', - key: paramKey - }); -} \ No newline at end of file diff --git a/vscode-plugin/media/wizard.css b/vscode-plugin/media/wizardHTML.css similarity index 86% rename from vscode-plugin/media/wizard.css rename to vscode-plugin/media/wizardHTML.css index 9512ccd10..a088d8763 100644 --- a/vscode-plugin/media/wizard.css +++ b/vscode-plugin/media/wizardHTML.css @@ -61,20 +61,44 @@ } /* By default turn off connection status */ -#connection_loader, -#connection_success, -#connection_warning, -#connection_failure { +#GRPC_connection_loader, +#GRPC_connection_success, +#GRPC_connection_warning, +#GRPC_connection_failure, +#SFTP_connection_loader, +#SFTP_connection_success, +#SFTP_connection_warning, +#SFTP_connection_failure { display: none; } -#connection_loader.active, -#connection_success.active, -#connection_warning.active, -#connection_failure.active { +#GRPC_connection_loader.active, +#GRPC_connection_success.active, +#GRPC_connection_warning.active, +#GRPC_connection_failure.active, +#SFTP_connection_loader.active, +#SFTP_connection_success.active, +#SFTP_connection_warning.active, +#SFTP_connection_failure.active { display: inline-block; } +/* plugin status */ +#SFTP_installed, +#SFTP_not_installed, +#SARIF_installed, +#SARIF_not_installed { + display: none; +} + +#SFTP_installed.active, +#SFTP_not_installed.active, +#SARIF_installed.active, +#SARIF_not_installed.active { + display: inline-block; +} + + .utbot-form__steps_step.finish { background-color: var(--vscode-editorInfo-foreground); } @@ -286,7 +310,14 @@ display: block; } - .utbot-vars { display: none; } + +a.invalid, +input.invalid, +textarea.invalid{ + outline-style: solid; + outline-width: thin; + outline-color: #FF4136 +} diff --git a/vscode-plugin/media/wizardHTML.js b/vscode-plugin/media/wizardHTML.js new file mode 100644 index 000000000..479ab253a --- /dev/null +++ b/vscode-plugin/media/wizardHTML.js @@ -0,0 +1,482 @@ + +// external declaration in HTML +// const GRPC_PREFIX = "GRPC_"; +// const SFTP_PREFIX = "SFTP_"; + +window.addEventListener('message', event => { + const message = event.data; // The JSON data our extension sent + //DbgMessage("addEventListener : " + message.command); + if (message.command === "checkPlugins") { + showPluginStatus("SFTP_", message.sftpPluginInstalled); + showPluginStatus("SARIF_", message.sarifPluginInstalled); + return; + } + let prefix = ""; + if (message.command.startsWith(GRPC_PREFIX)) { + prefix = GRPC_PREFIX; + } + else if (message.command.startsWith(SFTP_PREFIX)) { + prefix = SFTP_PREFIX; + } + //DbgMessage("addEventListener prefix : " + prefix); + if (message.command.endsWith('test_connection_success')) { + showConnectionSuccessStatus(prefix, message.clientVersion, message.serverVersion); + //DbgMessage("addEventListener res : test_connection_success"); + } + else if (message.command.endsWith('test_connection_failure')) { + showConnectionFailureStatus(prefix); + //DbgMessage("addEventListener res : test_connection_failure"); + } + else { + console.log(`ERROR: Unknown message: ${message.command}`); + } +}); + +function $(id) { + return document.getElementById(id); +} + +/* + * Communication with VSCode via API + */ +const vscode = acquireVsCodeApi(); +function DbgMessage(message) { + vscode.postMessage({ + command: 'dbg_message', + message: message + }); +} + +const os = getVarValue("os"); +console.log(`OS Platform: ${os}`); + +let nonLocalGRPCPort = getVarValue("defaultGRPCPort"); +let nonLocalSFTPPort = getVarValue("defaultSFTPPort"); +let nonLocalHost = getVarValue("serverHost"); +let nonLocalRemoteDir = getVarValue("serverDir"); +let currentTabIndex = 0; + +function showProgress(prefix, toShow) { + showElement($(prefix + "connection_loader"), toShow); + if (toShow) { + showElement($(prefix + "connection_success"), false); + showElement($(prefix + "connection_warning"), false); + showElement($(prefix + "connection_failure"), false); + } +} + +function showElement(el, toShow) { + if (!el) + return; + + if (toShow) { + el.classList.add('active'); + } + else { + el.classList.remove('active'); + } +} + +function isActive(el) { + if (!el) + return true; + return el.classList.contains("active"); +} + +function isConnectionPortValid(el, prefix) { + let valid = isActive($(prefix + "connection_success")) || isActive($(prefix + "connection_warning")) + setValid(el, valid); + return valid; +} + + +const mapTimerId = {}; +function restartCheckingConnection(prefix, pause) { + if (mapTimerId[prefix] != null) { + clearTimeout(mapTimerId[prefix]); + showProgress(prefix, false); + } + mapTimerId[prefix] = setTimeout(testConnection, pause, prefix); +} + +const pluginId = "plugin"; +function restartCheckingPlugin(pause) { + if (mapTimerId[pluginId] != null) { + clearTimeout(mapTimerId[pluginId]); + } + mapTimerId[pluginId] = setTimeout(testPlugin, pause); +} + +function testPlugin() { + vscode.postMessage({ + command: 'check_plugins' + }); +} + +function isTabWithClass(clazz) { + const tab = document.getElementsByClassName(clazz); + if (!tab || tab.length !== 1) { + DbgMessage("Something is wrong: " + clazz); + return false; + } + return isActive(tab[0]); +} + +function isConnectionTab() { + return isTabWithClass("connection-tab"); +} + +function isStartTab() { + return isTabWithClass("start-tab"); +} + +function onStart() { + const hostInput = $('hostInput'); + const portGRPCInput = $('portGRPCInput'); + const portSFTPInput = $('portSFTPInput'); + const serverPath = $('serverPath'); + + // DbgMessage("hostInput.value : " + hostInput.value); + // DbgMessage("portGRPCInput.value : " + portGRPCInput.value + " " + getVarValue("defaultGRPCPort")); + // DbgMessage("serverPath.value : " + serverPath.value + " " + getVarValue("projectDir")); + + if (hostInput.value === "localhost" + && portGRPCInput.value === getVarValue("defaultGRPCPort") + && serverPath.value === getVarValue("projectDir")) { + $('useLocalHost').checked = true; + hostInput.disabled = true; + portGRPCInput.disabled = true; + + portSFTPInput.disabled = true; + portSFTPInput.value = ""; + + serverPath.disabled = true; + } + if (os === 'win32') { + removeElementByClassName('installer-tab'); + removeElementByClassName('installer-step'); + } + restartCheckingPlugin(0); + showTab(currentTabIndex); +} + +onStart(); + +function showTab(tabIndex) { + const prevButton = $("prevBtn"); + const nextButton = $("nextBtn"); + + const tabElements = document.getElementsByClassName("utbot-form__tab"); + const currentTabElement = tabElements[tabIndex]; + + showElement(currentTabElement, true); + // Show previous button if necessary + showElement(prevButton, tabIndex === 0); + + // Set nextButton title according to tab number + if (tabIndex === tabElements.length - 1) { + nextButton.innerHTML = "Finish"; + } else { + nextButton.innerHTML = "Next"; + } + fixStepIndicator(tabIndex); +} + +function nextStep() { + let res = nextPrev(1); + if (isConnectionTab()) { + restartCheckingConnection(GRPC_PREFIX, 0); + restartCheckingConnection(SFTP_PREFIX, 0); + } else if (isStartTab()) { + restartCheckingPlugin(0); + } + return res; +} + +function prevStep() { + return nextPrev(-1); +} + +function showConnectionSuccessStatus(prefix, clientVersion, serverVersion) { + showProgress(prefix, false); + if (clientVersion !== serverVersion) { + const connectionWarning = $(prefix + "connection_warning"); + if (connectionWarning.getAttribute("format") !== null) { + connectionWarning.innerText = + connectionWarning.getAttribute("format").toString() + + " " + + `Server: ${serverVersion}, Client: ${clientVersion}`; + } + showElement($(prefix + "connection_success"), false); + showElement(connectionWarning, true); + } + else { + showElement($(prefix + "connection_success"), true); + showElement($(prefix + "connection_warning"), false); + } + showElement($(prefix + "connection_failure"), false); +} + +function showConnectionFailureStatus(prefix) { + showProgress(prefix, false); + showElement($(prefix + "connection_success"), false); + showElement($(prefix + "connection_warning"), false); + showElement($(prefix + "connection_failure"), true); + // just ping if failed, maybe server down + //restartCheckingConnection(prefix, 1500); +} + +function showPluginStatus(prefix, installed) { + showElement($(prefix + "installed"), installed); + showElement($(prefix + "not_installed"), !installed); + if (!installed) { + restartCheckingPlugin(1000); + } +} + +function isPluginInstalledAndMarkValid(prefix) { + let active = isActive($(prefix + "installed")); + setValid($(prefix + "ref"), active); + return active; +} + +function nextPrev(tabShift) { + if (tabShift !== 1 && tabShift !== -1) { + return false; + } + let tabElements = document.getElementsByClassName("utbot-form__tab"); + let currentTabElement = tabElements[currentTabIndex]; + if (tabShift === 1 && !validateForm()) { + return false; + } + let actionToPerform = currentTabElement.getAttribute("vs-message-callback"); + if (tabShift === 1 && actionToPerform) { + // make setup action only on the way forward + eval(actionToPerform) + } + currentTabElement.classList.remove('active'); + + currentTabIndex = currentTabIndex + tabShift; + if (currentTabIndex >= tabElements.length) { + closeWizard(); + return false; + } + + restoreOriginalStateOfSomeElements(); + showTab(currentTabIndex); + return true; +} + +function setValid(el, valid) { + if (!el) + return; + + if (!valid) { + el.classList.add("invalid"); + } else { + el.classList.remove("invalid"); + } +} + +function checkInputValidAsNotEmpty(el, syncValidStatus) { + if (!el) + return; + + let valid = !!el.value; + if (syncValidStatus) { + setValid(el, valid); + } + return valid; +} + +function defaultValidateForEmptyValue() { + let valid = true; + let tabElements = document.getElementsByClassName("utbot-form__tab"); + let tabInputs = tabElements[currentTabIndex].getElementsByTagName("input"); + for (let i = 0; i < tabInputs.length; i++) { + valid &= checkInputValidAsNotEmpty(tabInputs[i], true); + } + return valid; +} + +function validateForm() { + let valid = true; + if (isStartTab()) { + // call them all - we need to mark all invalid input + valid &= isPluginInstalledAndMarkValid("SFTP_") + valid &= isPluginInstalledAndMarkValid("SARIF_"); + } else if (isConnectionTab()) { + // call them all - we need to mark all invalid input + valid &= isConnectionPortValid($("portGRPCInput"), GRPC_PREFIX); + valid &= isConnectionPortValid($("portSFTPInput"), SFTP_PREFIX); + valid &= checkInputValidAsNotEmpty($("hostInput"), true); + valid &= checkInputValidAsNotEmpty($("serverPath"), true); + } else { + valid = this.defaultValidateForEmptyValue(); + } + + if (valid) { + document.getElementsByClassName("utbot-form__steps_step")[currentTabIndex].classList.add("finish"); + } + return valid; +} + +function fixStepIndicator(tabIndex) { + let steps = document.getElementsByClassName("utbot-form__steps_step"); + for (let i = 0; i < steps.length; i++) { + steps[i].classList.remove("active"); + if (i > tabIndex) { + steps[tabIndex].classList.remove('finish'); + } + } + steps[tabIndex].classList.add("active"); +} + +function restoreOriginalStateOfSomeElements() { + const button = $("runInstallerBtn"); + const messageBlock = document.getElementsByClassName("utbot-form__tab_installer_message")[0]; + if (button !== undefined && button !== null) { + button.disabled = false; + } + if (messageBlock !== undefined && messageBlock !== null) { + messageBlock.classList.remove("active"); + } +} + +function getVarValue(name) { + //DbgMessage(name + " = " + document.getElementsByClassName("utbot-vars")[0].getAttribute(name)); + return document.getElementsByClassName("utbot-vars")[0].getAttribute(name); +} + +function removeElementByClassName(className) { + const removableElements = document.getElementsByClassName(className); + for (let i = 0; i < removableElements.length; i++) { + const parentNode = removableElements[i].parentNode; + if (parentNode !== undefined || parentNode !== null) { + parentNode.removeChild(removableElements[i]); + } + } +} + +function handleOnOffDefaultConfigurationOnLocalhost() { + const serverPath = $('serverPath'); + const hostInput = $('hostInput'); + const portGRPCInput = $('portGRPCInput'); + const portSFTPInput = $('portSFTPInput'); + const useLocalhost = $('useLocalHost').checked; + if (useLocalhost) { + DbgMessage("Local host is used!"); + + nonLocalHost = hostInput.value; + hostInput.value = "localhost"; + + nonLocalGRPCPort = portGRPCInput.value; + portGRPCInput.value = getVarValue("defaultGRPCPort"); + + nonLocalSFTPPort = portSFTPInput.value; + portSFTPInput.value = ""; // Not Used + + nonLocalRemoteDir = serverPath.value; + serverPath.value = getVarValue("projectDir"); + } + else { + DbgMessage("Local host is not used!"); + hostInput.value = nonLocalHost; + portGRPCInput.value = nonLocalGRPCPort; + portSFTPInput.value = nonLocalSFTPPort; + serverPath.value = nonLocalRemoteDir; + } + serverPath.disabled= useLocalhost; + hostInput.disabled = useLocalhost; + portGRPCInput.disabled = useLocalhost; + portSFTPInput.disabled = useLocalhost; + + restartCheckingConnection(GRPC_PREFIX, 0); + restartCheckingConnection(SFTP_PREFIX, 0); +} + +// user from eval +function sendServerSetup() { + vscode.postMessage({ + command: 'set_server_setup', + host: $('hostInput').value, + portGRPC: parseInt($('portGRPCInput').value), + portSFTP: parseInt($('portSFTPInput').value), + serverPath: $('serverPath').value, + }); +} + +// used from eval call +function sendBuildInfo() { + const buildDirInputElement = $('buildDirectory'); + const cmakeOptionsInputElement = $('cmakeOptions'); + vscode.postMessage({ + command: 'set_build_info', + buildDirectory: buildDirInputElement.value, + cmakeOptions: cmakeOptionsInputElement.value + }); +} + + +/** + * Request to test the connection with host + */ +function testConnection(prefix) { + //DbgMessage(">>>>> testConnection " + prefix) + let port = ""; + switch (prefix) { + case GRPC_PREFIX: + port = $('portGRPCInput').value; + break; + case SFTP_PREFIX: + let el = $('portSFTPInput'); + if (!checkInputValidAsNotEmpty(el, false)) { + // show `Not Used` connection status + showProgress(prefix, true); + showConnectionSuccessStatus(prefix, 1, -1); + return; + } + port = el.value; + break; + } + + let letHostEl = $('hostInput'); + if (!checkInputValidAsNotEmpty(letHostEl, false)) { + // show `Failed` connection status + showProgress(prefix, true); + showConnectionFailureStatus(prefix); + return; + } + + showProgress(prefix, true); + vscode.postMessage({ + command: prefix + 'test_connection', + host: letHostEl.value, + port: parseInt(port) + }); +} + +/** + * Request to run installation script. + */ +function runInstaller() { + const button = $("runInstallerBtn"); + const messageBlock = document.getElementsByClassName("utbot-form__tab_installer_message")[0] + button.disabled = true; + messageBlock.classList.add("active"); + vscode.postMessage({ + command: 'run_installer' + }); +} + +function closeWizard() { + vscode.postMessage({ command: 'close_wizard' }); +} + +function openSFTPSettings(paramKey) { + //DbgMessage("openSFTPSettings"); + vscode.postMessage({ + command: 'openSFTPSettings', + key: paramKey + }); +} \ No newline at end of file diff --git a/vscode-plugin/package.json b/vscode-plugin/package.json index 6cad3071a..e04ed1c0d 100644 --- a/vscode-plugin/package.json +++ b/vscode-plugin/package.json @@ -560,25 +560,29 @@ "@types/mocha": "9.1.0", "@typescript-eslint/eslint-plugin": "2.34.0", "@typescript-eslint/parser": "2.34.0", + "@vscode/test-electron": "2.1.2", "eslint": "6.8.0", "glob": "7.2.0", - "mocha": "9.2.0", - "@vscode/test-electron": "2.1.2" + "mocha": "9.2.0" }, "dependencies": { "@types/google-protobuf": "3.15.5", "@types/node": "15.6.0", "@types/randomstring": "1.1.8", + "@types/ssh2": "1.11.6", "@types/vscode": "1.64.0", + "@types/ssh2-streams": "0.1.9", "emittery": "0.10.1", "filepath": "1.1.0", "google-protobuf": "3.12.4", "grpc": "npm:@grpc/grpc-js@1.5.5", - "grpc-tools": "1.11.2", "grpc_tools_node_protoc_ts": "5.3.2", + "grpc-tools": "1.11.2", "log4js": "6.5.2", "randomstring": "1.2.2", + "ssh2-streams": "0.4.10", "source-map-support": "0.5.21", - "typescript": "3.9.4" + "typescript": "3.9.4", + "node-ssh": "^13.0.0" } } diff --git a/vscode-plugin/src/client/client.ts b/vscode-plugin/src/client/client.ts index 7bec9ac1a..82e1ef5f2 100644 --- a/vscode-plugin/src/client/client.ts +++ b/vscode-plugin/src/client/client.ts @@ -101,7 +101,7 @@ export class Client { } this.host = Prefs.getHost(); - this.port = +Prefs.getPort(); + this.port = parseInt(Prefs.getGRPCPort()); this.logLevel = this.DEFAULT_LOG_LEVEL; @@ -395,7 +395,7 @@ export class Client { const response = this.testsService.configureProject(projectConfigRequest, this.metadata); await this.handleServerResponse(response, progressKey, token, resolve, reject, responseHandler); } catch (err) { - messages.showErrorMessage(err.message); + messages.showErrorMessage(err); utbotUI.channels().outputServerLogChannel.show(true); reject(err); } diff --git a/vscode-plugin/src/config/defaultValues.ts b/vscode-plugin/src/config/defaultValues.ts index bb4f2cd71..1d03768ea 100644 --- a/vscode-plugin/src/config/defaultValues.ts +++ b/vscode-plugin/src/config/defaultValues.ts @@ -8,7 +8,8 @@ import {isWin32} from "../utils/utils"; export class DefaultConfigValues { public static readonly DEFAULT_HOST = "localhost"; - public static readonly DEFAULT_PORT = 2121; + public static readonly DEFAULT_GRPC_PORT = 2121; + public static readonly DEFAULT_SFTP_PORT = 2020; public static readonly POSSIBLE_BUILD_DIR_NAMES = ['out', 'build']; public static readonly POSSIBLE_TEST_DIR_NAMES = ['test']; @@ -39,8 +40,16 @@ export class DefaultConfigValues { return host; } - public static getDefaultPort(): number { - return parseInt(Prefs.getGlobalPort()); + public static getDefaultGRPCPort(): number { + return parseInt(Prefs.getGlobalGRPCPort()); + } + + public static getDefaultSFTPPort(): number { + const sftpPort = vsUtils.getFromSftpConfig("port"); + if (sftpPort !== undefined) { + return parseInt(sftpPort); + } + return DefaultConfigValues.DEFAULT_SFTP_PORT; } public static getDefaultRemotePath(): string { diff --git a/vscode-plugin/src/config/notificationMessages.ts b/vscode-plugin/src/config/notificationMessages.ts index 7b012d404..f54097ad9 100644 --- a/vscode-plugin/src/config/notificationMessages.ts +++ b/vscode-plugin/src/config/notificationMessages.ts @@ -11,9 +11,14 @@ export const serverIsDeadError = "UTBot server doesn't respond. Check the connec export const grpcConnectionLostError = "No connection established"; export const targetNotUsed = "There is no used target. Use any in UTBot Targets window, please."; -// {SARIF +// {SFTP +export const defaultSFTP = "Natizyskunk.sftp"; +export const installSFTP = "Please, install Natizyskunk SFTP from https://marketplace.visualstudio.com/items?itemName=" + defaultSFTP; +// }SFTP + +// {SARIF export const defaultSARIFViewer = "MS-SarifVSCode.sarif-viewer"; -export const intstallSARIFViewer = "Please, install MS Sarif Viewer from https://marketplace.visualstudio.com/items?itemName=" + defaultSARIFViewer; +export const installSARIFViewer = "Please, install MS Sarif Viewer from https://marketplace.visualstudio.com/items?itemName=" + defaultSARIFViewer; // }SARIF diff --git a/vscode-plugin/src/config/prefs.ts b/vscode-plugin/src/config/prefs.ts index b3fed36e0..6c168e505 100644 --- a/vscode-plugin/src/config/prefs.ts +++ b/vscode-plugin/src/config/prefs.ts @@ -116,24 +116,6 @@ export class Prefs { return !this.isInitialized(context); } - public static async setPredictedSettings(): Promise { - logger.debug("Setting default settings"); - await this.setPredictedHost(); - await this.setPredictedPort(); - } - - public static async setPredictedHost(): Promise { - const predictedHost = defcfg.DefaultConfigValues.getDefaultHost(); - await Prefs.setHost(predictedHost); - logger.debug(`Host is automatically set to '${predictedHost}'`); - } - - public static async setPredictedPort(): Promise { - const predictedPort = defcfg.DefaultConfigValues.getDefaultPort(); - await Prefs.setPort(predictedPort); - logger.debug(`Port is automatically set to '${predictedPort}'`); - } - /** * Marks current workspace as unconfigured for UTBot. * @param context extension's context @@ -334,15 +316,15 @@ export class Prefs { return this.getAsset(Prefs.HOST_PREF); } - public static async setPort(port: number): Promise { + public static async setGRPCPort(port: number): Promise { await this.setGlobalAsset(Prefs.PORT_PREF, port, false); } - public static getGlobalPort(): string { - return this.getGlobalAsset(Prefs.PORT_PREF, `${defcfg.DefaultConfigValues.DEFAULT_PORT}`); + public static getGlobalGRPCPort(): string { + return this.getGlobalAsset(Prefs.PORT_PREF, `${defcfg.DefaultConfigValues.DEFAULT_GRPC_PORT}`); } - public static getPort(): string { + public static getGRPCPort(): string { return this.getAsset(Prefs.PORT_PREF); } diff --git a/vscode-plugin/src/emitter/UTBotEventEmitter.ts b/vscode-plugin/src/emitter/UTBotEventEmitter.ts index 32ad34ed8..71af2f345 100644 --- a/vscode-plugin/src/emitter/UTBotEventEmitter.ts +++ b/vscode-plugin/src/emitter/UTBotEventEmitter.ts @@ -1,4 +1,4 @@ -import * as Emittery from "emittery"; +import Emittery from "emittery"; export class UTBotEventEmitter { private emittery: Emittery> = new Emittery(); diff --git a/vscode-plugin/src/responses/responseHandler.ts b/vscode-plugin/src/responses/responseHandler.ts index 32f599077..47f9e55c9 100644 --- a/vscode-plugin/src/responses/responseHandler.ts +++ b/vscode-plugin/src/responses/responseHandler.ts @@ -93,7 +93,7 @@ export class TestsResponseHandler implements ResponseHandler { const sarifExt = vs.extensions.getExtension(messages.defaultSARIFViewer); // eslint-disable-next-line eqeqeq if (sarifExt == null) { - messages.showWarningMessage(messages.intstallSARIFViewer); + messages.showWarningMessage(messages.installSARIFViewer); } else { if (!sarifExt.isActive) { await sarifExt.activate(); diff --git a/vscode-plugin/src/test/suite/index.ts b/vscode-plugin/src/test/suite/index.ts index 3a577d8c1..2d5a3502a 100644 --- a/vscode-plugin/src/test/suite/index.ts +++ b/vscode-plugin/src/test/suite/index.ts @@ -1,5 +1,5 @@ -import * as glob from 'glob'; -import * as Mocha from 'mocha'; +import glob from 'glob'; +import Mocha from 'mocha'; import * as path from 'path'; export function run(): Promise { diff --git a/vscode-plugin/src/wizard/wizard.ts b/vscode-plugin/src/wizard/wizard.ts index 30d4e9c9f..e055562f5 100644 --- a/vscode-plugin/src/wizard/wizard.ts +++ b/vscode-plugin/src/wizard/wizard.ts @@ -12,6 +12,7 @@ import * as pathUtils from '../utils/pathUtils'; import {WizardEventsEmitter} from './wizardEventsEmitter'; import * as fs from "fs"; import * as vsUtils from "../utils/vscodeUtils"; +import {NodeSSH} from "node-ssh"; const { logger } = ExtensionLogger; @@ -55,71 +56,59 @@ export class UtbotWizardPanel { constructor(private readonly panel: vs.WebviewPanel, private readonly context: vs.ExtensionContext) { void this.update(); this.panel.onDidDispose(() => this.dispose(), null, this.disposables); - - this.panel.onDidChangeViewState( - _event => { - if (this.panel.visible) { - void this.update(); - } - }, - null, - this.disposables - ); - this.panel.webview.onDidReceiveMessage( async message => { switch (message.command) { - case 'openSFTPSettings': - if (message.key !== undefined) { - const keyName = message.key; - //await vs.commands.executeCommand('workbench.action.openSettings', 'Natizyskunk.sftp.remotePath'); - await vs.commands.getCommands(true).then( - async (commands: string[]) => { - if (commands.includes("sftp.config")) { - await vs.commands.executeCommand("sftp.config").then( - async () => { - // TODO: positioning at keyname/value line - // await vs.commands.executeCommand('actions.find', `${keyName}`).then( - // () => messages.showErrorMessage(`query: @${keyName}`) - // ); - } - ); - } else { - messages.showErrorMessage("SFTP plugin isn't installed!"); - } - } - ); - } - break; case 'run_installer': this.runInstallationScript(); break; case 'set_server_setup': await Prefs.setHost(message.host); - await Prefs.setPort(message.port); - await Prefs.setRemotePath(message.mappingPath); + await Prefs.setGRPCPort(message.portGRPC); + await Prefs.setRemotePath(message.serverPath); + await this.setupSFTP( + !!message.portSFTP, + message.host, + message.portSFTP, + message.serverPath); break; case 'set_build_info': await Prefs.setBuildDirectory(message.buildDirectory); await Prefs.setCmakeOptions(message.cmakeOptions); break; - case 'check_connection': - case 'test_connection': - this.pingAction( - message.host, - message.port, - message.command + '_success', - message.command + '_failure'); - break; case 'close_wizard': this.panel.dispose(); break; case 'dbg_message': logger.info(`dbg_message: ${message.message}`); break; - default: + case 'check_plugins': + //logger.info(`check_plugins`); + this.checkPlugins(); + break; + default: { + if (message.command.endsWith('test_connection')) { + //messages.showErrorMessage(`!!!!!! message (${message.command}) from WizardWebView: ${message}`); + if (message.command.startsWith(`GRPC_`)) { + this.pingAction( + message.host, + message.port, + message.command + '_success', + message.command + '_failure'); + break; + } + else if (message.command.startsWith(`SFTP_`)) { + this.pingSFTPAction( + message.host, + message.port, + message.command + '_success', + message.command + '_failure'); + break; + } + } messages.showErrorMessage(`Unknown message (${message.command}) from WizardWebView: ${message}`); break; + } } }, null, @@ -127,6 +116,89 @@ export class UtbotWizardPanel { ); } + private checkPlugins(): void { + void this.panel.webview.postMessage({ + command: "checkPlugins", + sftpPluginInstalled: !!vs.extensions.getExtension(messages.defaultSFTP), + sarifPluginInstalled: !!vs.extensions.getExtension(messages.defaultSARIFViewer) + }); + } + + private async setupSFTP( + activate: boolean, + host: string, + portSFTP: string, + remotePath: string + ): Promise { + const sftpExt = vs.extensions.getExtension(messages.defaultSFTP); + if (!sftpExt) { + messages.showWarningMessage(messages.installSFTP); + } else { + if (!sftpExt.isActive) { + await sftpExt.activate(); + } + const workspaceFolderUrl = vs.workspace.workspaceFolders?.[0].uri; + if (workspaceFolderUrl) { + const workspaceFolder = workspaceFolderUrl.fsPath; + const sftpConfigPath = pathUtils.fsJoin(workspaceFolder, '.vscode', 'sftp.json'); + try { + if (activate) { + const configContent = +`{ + "name": "UTBot Server", + "host": "${host}", + "protocol": "sftp", + "port": ${portSFTP}, + "username": "utbot", + "password": "utbot", + "remotePath": "${remotePath}", + "uploadOnSave": true, + "useTempFile": false, + "openSsh": false +}`; + if (!fs.existsSync(sftpConfigPath)) { + fs.writeFileSync(sftpConfigPath, ' '); + } + const doc = await vs.workspace.openTextDocument(sftpConfigPath); + const editor = await vs.window.showTextDocument(doc, {preview: true, preserveFocus: false}); + // we need to generate the `onDidSaveTextDocument` event + // it is the only event that is processed by SFTP pluging to change the preload configuration + void editor.edit( builder => { + builder.delete(new vs.Range(0, 0, 10000, 10000)); + builder.insert(new vs.Position(0, 0), configContent); + }) + .then( () => { + void editor.document.save().then( saved => { + if (saved) { + messages.showWarningMessage(`New configuration ".vscode/sftp.json" was saved!`); + } + void vs.commands.executeCommand('workbench.action.closeActiveEditor'); + const postponedSync = (): void => { + void vs.commands.executeCommand("sftp.sync.localToRemote", workspaceFolderUrl).then( + () => messages.showWarningMessage(`Project copy was created on UTBot Server at "${remotePath}"`), + (err) => messages.showWarningMessage(`Project copy was not created on UTBot Server at "${remotePath}" with error ` + err) + ); + }; + setTimeout(postponedSync, 300); + }); + }); + } else { + fs.unlink(sftpConfigPath, function (err) { + if (err) { + console.error(err); + console.log('File ".vscode/sftp.json" not found'); + }else{ + console.log('File ".vscode/sftp.json" was deleted successfully'); + } + }); + } + } catch (error) { + messages.showWarningMessage("Error while SFTP configuration: " + error); + } + } + } + } + private runInstallationScript(): void { const onDiskPath = vs.Uri.file(pathUtils.fsJoin(this.context.extensionPath, 'scripts', 'utbot_installer.sh')); const terminal = vs.window.createTerminal('UTBot Server Installation'); @@ -134,7 +206,7 @@ export class UtbotWizardPanel { terminal.sendText(onDiskPath.fsPath, true); } - private lastRequest: Promise | null = null; + private lastRequest: Promise | null = null; private pingAction(host: string, port: number, successCmd: string, failureCmd: string): void { const servicePing = new GrpcServicePing( this.context.extension.packageJSON.version, @@ -156,10 +228,10 @@ export class UtbotWizardPanel { command: successCmd, clientVersion: this.context.extension.packageJSON.version, serverVersion: (serverVer.length === 0 - ? "undefined" - : serverVer)}); + ? "undefined" + : serverVer)}); } else { - await this.panel.webview.postMessage({ command: failureCmd }); + await this.panel.webview.postMessage({ command: failureCmd}); } }).catch(async err => { logger.error(`Error! ${err}`); @@ -170,6 +242,34 @@ export class UtbotWizardPanel { return; } + private lastSFTPRequest: Promise | null = null; + private pingSFTPAction(host: string, port: number, successCmd: string, failureCmd: string): void { + const ssh = new NodeSSH(); + const capturedPingPromiseForLambda = ssh.connect({ + host: host, + port: port, + username: 'utbot', + password: 'utbot' + }); + this.lastSFTPRequest = capturedPingPromiseForLambda; + capturedPingPromiseForLambda.then( async () => { + ssh.dispose(); + if (this.lastSFTPRequest !== capturedPingPromiseForLambda) { + return; + } + await this.panel.webview.postMessage({ + command: successCmd, + clientVersion: "1", + serverVersion: "1"}); + }, async (error) => { + ssh.dispose(); + if (this.lastSFTPRequest === capturedPingPromiseForLambda) { + await this.panel.webview.postMessage({ command: failureCmd}); + } + }); + return; + } + public async update(): Promise { this.panel.webview.html = await this.getHtmlForWebview(this.panel.webview); } @@ -223,7 +323,7 @@ export class UtbotWizardPanel { const predictedBuildDirectory = await defcfg.DefaultConfigValues.getDefaultBuildDirectoryPath(); const initVars: {param: string; value: string|undefined}[] = [ // UI javascript - {param: 'scriptUri', value: mediaPath('wizard.js').toString()}, + {param: 'scriptUri', value: mediaPath('wizardHTML.js').toString()}, // Security (switched off) //{param: 'wvscriptUri', value: webview.cspSource}, @@ -231,18 +331,20 @@ export class UtbotWizardPanel { // CSS in header {param: 'vscodeUri', value: mediaPath('vscode.css').toString()}, - {param: 'stylesUri', value: mediaPath('wizard.css').toString()}, + {param: 'stylesUri', value: mediaPath('wizardHTML.css').toString()}, // vars {param: 'os', value: os.platform()}, {param: 'projectDir', value: defcfg.DefaultConfigValues.toWSLPathOnWindows(vsUtils.getProjectDirByOpenedFile().fsPath)}, - {param: 'defaultPort', value: defcfg.DefaultConfigValues.DEFAULT_PORT.toString()}, - {param: 'sftpHost', value: vsUtils.getFromSftpConfig("host")}, - {param: 'sftpDir', value: vsUtils.getFromSftpConfig("remotePath")}, + {param: 'defaultGRPCPort', value: defcfg.DefaultConfigValues.DEFAULT_GRPC_PORT.toString()}, + {param: 'defaultSFTPPort', value: defcfg.DefaultConfigValues.DEFAULT_SFTP_PORT.toString()}, + {param: 'serverHost', value: vsUtils.getFromSftpConfig("host")}, + {param: 'serverDir', value: vsUtils.getFromSftpConfig("remotePath")}, // connection tab {param: 'predictedHost', value: defcfg.DefaultConfigValues.getDefaultHost()}, - {param: 'predictedPort', value: Prefs.getGlobalPort()}, + {param: 'predictedGRPCPort', value: Prefs.getGlobalGRPCPort()}, + {param: 'predictedSFTPPort', value: defcfg.DefaultConfigValues.getDefaultSFTPPort().toString()}, {param: 'predictedRemotePath', value: defcfg.DefaultConfigValues.getDefaultRemotePath()}, // project tab @@ -257,8 +359,8 @@ export class UtbotWizardPanel { // @ts-ignore content = content.replaceAll(`{{${p2v.param}}}`, `${p2v.value === undefined - ? '' - : p2v.value}`); + ? '' + : p2v.value}`); } const uninitVars = UtbotWizardPanel.checkNonInitializedVars.exec(content); diff --git a/vscode-plugin/tsconfig.json b/vscode-plugin/tsconfig.json index 833903d44..8ad8f4cb0 100644 --- a/vscode-plugin/tsconfig.json +++ b/vscode-plugin/tsconfig.json @@ -9,6 +9,7 @@ ], "sourceMap": true, "rootDir": "src", + "esModuleInterop": true, "strict": true /* enable all strict type-checking options */ /* Additional Checks */ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */