diff --git a/package-lock.json b/package-lock.json index 341ac69e..20bf5856 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-couchbase", - "version": "2.1.6", + "version": "2.1.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vscode-couchbase", - "version": "2.1.6", + "version": "2.1.7", "license": "SEE LICENSE IN LICENSE", "dependencies": { "@aws-sdk/client-dynamodb": "^3.602.0", diff --git a/package.json b/package.json index 5d5dc03e..66b1ed59 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-couchbase", "displayName": "Couchbase", "description": "", - "version": "2.1.6", + "version": "2.1.7", "engines": { "vscode": "^1.63.1" }, @@ -1323,5 +1323,4 @@ } ] } -} - +} \ No newline at end of file diff --git a/src/handlers/dependenciesUtil.ts b/src/handlers/dependenciesUtil.ts index b6a0573c..78937795 100644 --- a/src/handlers/dependenciesUtil.ts +++ b/src/handlers/dependenciesUtil.ts @@ -18,6 +18,9 @@ class DependenciesUtil { public static readonly CBIMPORT_EXPORT_VERSION = config.CBIMPORT_EXPORT_VERSION; + public static readonly SDK_DOCTOR_VERSION = config.SDK_DOCTOR_VERSION; + public static readonly SDK_DOCTOR_KEY = config.SDK_DOCTOR_KEY; + public static createVersioningFile(directoryPath: string): void { const filePath = path.join( directoryPath, diff --git a/src/handlers/handleCLIDownloader.ts b/src/handlers/handleCLIDownloader.ts index 0b15a250..e1896e5d 100644 --- a/src/handlers/handleCLIDownloader.ts +++ b/src/handlers/handleCLIDownloader.ts @@ -21,6 +21,7 @@ class DependenciesDownloader { private readonly TOOL_IMPORT_EXPORT = "import_export"; private readonly ALL_TOOLS = "all_tools"; private readonly TOOL_MDB_MIGRATE = "cb_migrate"; + private readonly SDK_DOCTOR = "sdk-doctor"; private getToolInstallPath(toolKey: string): string { if (toolKey === this.TOOL_SHELL) { @@ -31,7 +32,10 @@ class DependenciesDownloader { return "cbmigrate"; } else if (toolKey === this.ALL_TOOLS) { return "cbtools"; - } else { + } else if(toolKey === this.SDK_DOCTOR) { + return "sdk-doctor"; + } + else { throw new Error("Not Implemented yet"); } } @@ -39,6 +43,7 @@ class DependenciesDownloader { private getToolsMap(toolKey: string, os: string): Map { const suffix = os.includes("mac") || os.includes("linux") ? "" : ".exe"; const pathPrefix = "bin" + path.sep; + console.log(toolKey); const map = new Map(); @@ -64,6 +69,8 @@ class DependenciesDownloader { CBToolsType.MCTIMINGS, path.join(pathPrefix, "mctimings" + suffix) ); + } else if (toolKey === this.SDK_DOCTOR) { + map.set(CBToolsType.SDK_DOCTOR, 'sdk-doctor'+ suffix); } else { throw new Error("Not implemented yet"); } @@ -80,6 +87,38 @@ class DependenciesDownloader { } public getDownloadList(os: string): Map { const map = new Map(); + // SDK Doctor Download configuration + if (os === OSUtil.MACOS_64 || os === OSUtil.MACOS_ARM) { + map.set( + this.SDK_DOCTOR, + this.getToolSpec( + "https://intellij-plugin-dependencies.s3.us-east-2.amazonaws.com/sdkdoctor/1.0.8-sdk-doctor-macos.zip", + this.SDK_DOCTOR, + os + ) + ); + } else if (os === OSUtil.WINDOWS_64 || os === OSUtil.WINDOWS_ARM) { + map.set( + this.SDK_DOCTOR, + this.getToolSpec( + "https://intellij-plugin-dependencies.s3.us-east-2.amazonaws.com/sdkdoctor/1.0.8-sdk-doctor-windows.zip", + this.SDK_DOCTOR, + os + ) + ); + } else if (os === OSUtil.LINUX_64 || os === OSUtil.LINUX_ARM) { + map.set( + this.SDK_DOCTOR, + this.getToolSpec( + "https://intellij-plugin-dependencies.s3.us-east-2.amazonaws.com/sdkdoctor/1.0.8-sdk-doctor-linux.zip", + this.SDK_DOCTOR, + os + ) + ); + } else { + throw new Error("OS not supported."); + } + // CB Tools Download configuration if (os === OSUtil.MACOS_64) { map.set( this.TOOL_SHELL, @@ -283,7 +322,7 @@ class DependenciesDownloader { ) { logger.info("Cleaning up older tools versions if update required"); const shell: ToolSpec | undefined = downloads.get(this.TOOL_SHELL); - if (shell == undefined) { + if (shell === undefined) { return; } // Checks if CB shell is installed and requires update by comparing current version of tool with version config value @@ -307,7 +346,7 @@ class DependenciesDownloader { } const cbimport_export: ToolSpec | undefined = downloads.get(this.TOOL_IMPORT_EXPORT); - if (cbimport_export == undefined) { + if (cbimport_export === undefined) { return; } // Checks if CB Import/Export is installed and requires update by comparing current version of tool with version config value @@ -347,6 +386,7 @@ class DependenciesDownloader { this.manageShellInstallation(downloads, toolsPath, extensionPath); this.manageCbMigrateInstallation(downloads, toolsPath, extensionPath); this.manageDataImportExportInstallation(downloads, toolsPath, extensionPath); + this.manageSdkDoctorInstallation(downloads, toolsPath, extensionPath); }; private setToolActive( @@ -524,6 +564,39 @@ class DependenciesDownloader { } } + public manageSdkDoctorInstallation( + downloads: Map, + toolsPath: string, + extensionPath: string + ): void { + const sdkDoctor = downloads.get(this.SDK_DOCTOR); + if (sdkDoctor === undefined) { + return; + } + const sdkDoctorPath = path.join(toolsPath, sdkDoctor.getInstallationPath()); + const sdkDoctorTool = CBTools.getTool(CBToolsType.SDK_DOCTOR); + const sdkDoctorStatus = sdkDoctorTool.status; + const sdkDoctorDownloadsMap = downloads.get(this.SDK_DOCTOR); + if (sdkDoctorDownloadsMap === undefined) { + return; + } + if ( + sdkDoctorStatus === ToolStatus.NOT_AVAILABLE && + !this.isInstalled( + toolsPath, + sdkDoctorDownloadsMap, + CBToolsType.SDK_DOCTOR + ) + ) { + logger.info("Downloading SDK Doctor."); + sdkDoctorTool.status = ToolStatus.DOWNLOADING; + this.downloadAndUnzip(sdkDoctorPath, sdkDoctor, extensionPath, DependenciesUtil.SDK_DOCTOR_KEY, DependenciesUtil.SDK_DOCTOR_VERSION); + } else { + logger.debug("SDK Doctor is already installed"); + this.setToolActive(ToolStatus.AVAILABLE, sdkDoctorPath, sdkDoctor); + } + } + // This is a test function, keeping this here to check for any specific downloaded CLI tool /* public runFile(targetDir:string) { const scriptPath = path.join(targetDir, 'cbsh'); diff --git a/src/handlers/versionConfig.ts b/src/handlers/versionConfig.ts index fbc9fe0b..1c024367 100644 --- a/src/handlers/versionConfig.ts +++ b/src/handlers/versionConfig.ts @@ -8,4 +8,6 @@ export const config = { CBMIGRATE_VERSION: "2", SHELL_VERSION: "1.0.0", CBIMPORT_EXPORT_VERSION: "7.6", + SDK_DOCTOR_VERSION: "1.0.8", + SDK_DOCTOR_KEY: "sdk-doctor", }; \ No newline at end of file diff --git a/src/tools/SDKDocterRunner.ts b/src/tools/SDKDocterRunner.ts new file mode 100644 index 00000000..2a5f17f7 --- /dev/null +++ b/src/tools/SDKDocterRunner.ts @@ -0,0 +1,67 @@ +import * as childProcess from 'child_process'; +import { CBTools, ToolStatus, Type } from '../util/DependencyDownloaderUtils/CBTool'; + +export class SdkDoctorRunner { + static async run( + host: string, + ssl: boolean, + bucket: string, + username: string, + password: string, + outputCallback: (line: string) => void + ): Promise { + try { + const sdkDoctorTool = CBTools.getTool(Type.SDK_DOCTOR); + if (sdkDoctorTool.status !== ToolStatus.AVAILABLE) { + throw new Error('SDK Doctor is not available. Please ensure it is installed.'); + } + + const sdkDoctorExecutable = sdkDoctorTool.path; + const clusterUrl = SdkDoctorRunner.adjustClusterProtocol(host, ssl); + const args = [ + 'diagnose', + `${clusterUrl}/${bucket}`, + '-u', username, + '-p', password + ]; + + const process = childProcess.spawn(sdkDoctorExecutable, args); + + process.stdout.on('data', (data) => { + const lines = data.toString().split('\n'); + lines.forEach((line: string) => { + if (line.trim()) { + outputCallback(line.trim()); + } + }); + }); + + process.stderr.on('data', (data) => { + console.error(`SDK Doctor error: ${data}`); + }); + + await new Promise((resolve, reject) => { + process.on('close', (code) => { + if (code !== 0) { + console.warn(`SDK Doctor exited with code ${code}`); + } + resolve(); + }); + + process.on('error', (err) => { + reject(err); + }); + }); + + } catch (error) { + console.error('Error while running the SDK Doctor', error); + } + } + + private static adjustClusterProtocol(host: string, ssl: boolean): string { + const protocol = ssl ? 'couchbases://' : 'couchbase://'; + return host.startsWith('couchbase://') || host.startsWith('couchbases://') + ? host + : `${protocol}${host}`; + } +} \ No newline at end of file diff --git a/src/util/DependencyDownloaderUtils/CBTool.ts b/src/util/DependencyDownloaderUtils/CBTool.ts index bed06f7d..3d256103 100644 --- a/src/util/DependencyDownloaderUtils/CBTool.ts +++ b/src/util/DependencyDownloaderUtils/CBTool.ts @@ -20,7 +20,8 @@ export enum Type { CB_EXPORT = "CB_EXPORT", CBC_PILLOW_FIGHT = "CBC_PILLOW_FIGHT", MCTIMINGS = "MCTIMINGS", - CB_MIGRATE = "CB_MIGRATE" + CB_MIGRATE = "CB_MIGRATE", + SDK_DOCTOR = "SDK_DOCTOR" } diff --git a/src/util/connections.ts b/src/util/connections.ts index 25c44243..01c660e3 100644 --- a/src/util/connections.ts +++ b/src/util/connections.ts @@ -27,6 +27,7 @@ import { CouchbaseRestAPI } from "./apis/CouchbaseRestAPI"; import { hasQueryService, hasSearchService } from "./common"; import { SecretService } from "./secretService"; import ConnectionEvents from "./events/connectionEvents"; +import { SdkDoctorRunner } from "../tools/SDKDocterRunner"; export function getConnectionId(connection: IConnection) { const { url, username } = connection; @@ -151,6 +152,43 @@ export async function addConnection(clusterConnectionTreeProvider: ClusterConnec currentPanel.dispose(); break; + case 'testConnection': + const { connectionUrl, username, password, bucketName, isSecure } = message; + if(bucketName === "") { + try { + await connect(connectionUrl, { username: username, password: password, configProfile: 'wanDevelopment' }); + currentPanel.webview.postMessage({ + command: 'testConnectionResult', + result: 'Connection was successfull!' + }); + } + catch (err) { + currentPanel.webview.postMessage({ + command: 'testConnectionResult', + result: '[ERRO]: Connection Failed ' + err + }); + } + return; + } + const results: string[] = []; + + await SdkDoctorRunner.run( + connectionUrl, + isSecure, + bucketName, + username, + password, + (line) => { + results.push(line); + } + ); + + currentPanel.webview.postMessage({ + command: 'testConnectionResult', + result: results.join('\n') + }); + break; + default: console.error('Unrecognized command'); } @@ -163,10 +201,11 @@ async function handleConnectionError(err: any) { if (err instanceof AuthenticationFailureError) { answer = await vscode.window.showErrorMessage(` Authentication Failed: Please check your credentials and try again \n - If you're still having difficulty, please check out this helpful troubleshooting link`, { modal: true }, "Troubleshoot Link"); + or inform a Bucket on Troubleshooting to inspect your connection \n + or check out this helpful troubleshooting link`, { modal: true }, "Troubleshoot Link"); } else { - answer = await vscode.window.showErrorMessage(`Could not establish a connection \n ${err} \n If you're having difficulty, please check out this helpful troubleshooting link`, { modal: true }, "Troubleshoot Link"); + answer = await vscode.window.showErrorMessage(`Could not establish a connection \n ${err} \n Inform a Bucket on Troubleshooting to inspect your connection \n or check out this helpful troubleshooting link`, { modal: true }, "Troubleshoot Link"); } if (answer === "Troubleshoot Link") { diff --git a/src/webViews/connectionScreen.webview.ts b/src/webViews/connectionScreen.webview.ts index cb936ebe..6992d595 100644 --- a/src/webViews/connectionScreen.webview.ts +++ b/src/webViews/connectionScreen.webview.ts @@ -15,21 +15,24 @@ */ export const getClusterConnectingFormView = (message: any) => { + // @ts-ignore return /*HTML*/` - - - - - Add Connection - - - -
-
-

Connect to Couchbase

- Enter your connection details below

-
+ } + + .secure-box { + margin-top: 5px; + gap: 2px; + display: flex; + align-items: center; + } + + .advanced-settings { + margin-top: 20px; + } + + .advanced-settings summary { + cursor: pointer; + font-weight: bold; + margin-bottom: 10px; + } + + .troubleshooting-message { + margin-bottom: 15px; + } + + .bucket-input { + margin-bottom: 10px; + } + + #testConnectionButton { + margin-top: 10px; + } + + #testConnectionResults { + margin-top: 15px; + border: 1px solid var(--vscode-input-border); + padding: 10px; + display: none; + background-color: var(--vscode-terminal-background); + color: var(--vscode-terminal-foreground); + font-family: var(--vscode-editor-font-family); + font-size: var(--vscode-editor-font-size); + max-height: 200px; + overflow-y: auto; + white-space: pre-wrap; + word-break: break-all; + } + .warning { + color: #f0ad4e; + font-weight: bold; + } + + .error { + color: #d9534f; + font-weight: bold; + } + + + + +
+
+

Connect to Couchbase

+ Enter your connection details below

+

- +
- - + +
-
-
+
+

- -

-
- -

+ +

+
+ +


- -

+ +

+
+
+ Troubleshooting +
+

If you can't successfully connect to your cluster, try informing an existing bucket in + the field below and click on Test Connection. It will run the + SDK Doctor, a utility that tries to identify potential issues. +

+
+
+
+ +
+ +
+
+
+
- +
-
-
- -
-

+

+
+ +
+

New to Couchbase and don't have a cluster? -

-
-
-

- If you don't already have a cluster you can install couchbase locally or create a cluster using capella -

-
- + +
+
+

+ If you don't already have a cluster you can install couchbase locally or create a cluster using + capella +

+
+
- -
- + +
+ - -
+
- - - + + + + `; }; \ No newline at end of file