diff --git a/src/Explorer/Tabs/CloudShellTab/CloudShellTabComponent.tsx b/src/Explorer/Tabs/CloudShellTab/CloudShellTabComponent.tsx index 4059d9102..5de6a617b 100644 --- a/src/Explorer/Tabs/CloudShellTab/CloudShellTabComponent.tsx +++ b/src/Explorer/Tabs/CloudShellTab/CloudShellTabComponent.tsx @@ -3,7 +3,7 @@ import { Terminal } from "xterm"; import { FitAddon } from 'xterm-addon-fit'; import "xterm/css/xterm.css"; import { TerminalKind } from "../../../Contracts/ViewModels"; -import { startCloudShellterminal } from "./UseTerminal"; +import { startCloudShellTerminal } from "./UseTerminal"; export interface CloudShellTerminalProps { shellType: TerminalKind; @@ -37,7 +37,7 @@ export const CloudShellTerminalComponent: React.FC = ({ const handleResize = () => fitAddon.fit(); window.addEventListener('resize', handleResize); - socketRef.current = startCloudShellterminal(term, shellType); + socketRef.current = startCloudShellTerminal(term, shellType); term.onData((data) => { if (socketRef.current && socketRef.current.readyState === WebSocket.OPEN) { diff --git a/src/Explorer/Tabs/CloudShellTab/Commands.tsx b/src/Explorer/Tabs/CloudShellTab/Commands.tsx index 4ac42eeb6..bd336143f 100644 --- a/src/Explorer/Tabs/CloudShellTab/Commands.tsx +++ b/src/Explorer/Tabs/CloudShellTab/Commands.tsx @@ -6,20 +6,20 @@ import { TerminalKind } from "../../../Contracts/ViewModels"; import { userContext } from "../../../UserContext"; export const getCommands = (terminalKind: TerminalKind, key: string) => { - let databaseacc = userContext.databaseAccount; + let dbAccount = userContext.databaseAccount; let endpoint; switch (terminalKind) { case TerminalKind.Postgres: - endpoint = databaseacc.properties.postgresqlEndpoint; + endpoint = dbAccount.properties.postgresqlEndpoint; break; case TerminalKind.Mongo: - endpoint = databaseacc.properties.mongoEndpoint; + endpoint = dbAccount.properties.mongoEndpoint; break; case TerminalKind.VCoreMongo: - endpoint = databaseacc.properties.vcoreMongoEndpoint; + endpoint = dbAccount.properties.vcoreMongoEndpoint; break; case TerminalKind.Cassandra: - endpoint = databaseacc.properties.cassandraEndpoint; + endpoint = dbAccount.properties.cassandraEndpoint; break; default: throw new Error("Unknown Terminal Kind"); @@ -27,7 +27,7 @@ export const getCommands = (terminalKind: TerminalKind, key: string) => { let config = { host: getHostFromUrl(endpoint), - name: databaseacc.name, + name: dbAccount.name, password: key, endpoint: endpoint }; @@ -46,38 +46,73 @@ export const commands = (terminalKind: TerminalKind, config?: CommandConfig): st switch (terminalKind) { case TerminalKind.Postgres: return [ - "curl -s https://ipinfo.io", - "curl -LO https://ftp.postgresql.org/pub/source/v15.2/postgresql-15.2.tar.bz2", - "tar -xvjf postgresql-15.2.tar.bz2", - "cd postgresql-15.2", - "mkdir ~/pgsql", - "curl -LO https://ftp.gnu.org/gnu/readline/readline-8.1.tar.gz", - "tar -xvzf readline-8.1.tar.gz", - "cd readline-8.1", - "./configure --prefix=$HOME/pgsql" - ]; - case TerminalKind.Mongo || TerminalKind.VCoreMongo: - return [ - "curl -s https://ipinfo.io", - "curl -LO https://downloads.mongodb.com/compass/mongosh-2.3.8-linux-x64.tgz", - "tar -xvzf mongosh-2.3.8-linux-x64.tgz", - "mkdir -p ~/mongosh && mv mongosh-2.3.8-linux-x64/* ~/mongosh/", - "echo 'export PATH=$PATH:$HOME/mongosh/bin' >> ~/.bashrc", + // 1. Fetch and display location details in a readable format + "curl -s https://ipinfo.io | jq -r '\"Region: \" + .region + \" Country: \" + .country + \" City: \" + .city + \" IP Addr: \" + .ip'", + // 2. Check if psql is installed; if not, proceed with installation + "if ! command -v psql &> /dev/null; then echo '⚠️ psql not found. Installing...'; fi", + // 3. Download PostgreSQL if not installed + "if ! command -v psql &> /dev/null; then curl -LO https://ftp.postgresql.org/pub/source/v15.2/postgresql-15.2.tar.bz2; fi", + // 4. Extract PostgreSQL package if not installed + "if ! command -v psql &> /dev/null; then tar -xvjf postgresql-15.2.tar.bz2; fi", + // 5. Create a directory for PostgreSQL installation if not installed + "if ! command -v psql &> /dev/null; then mkdir -p ~/pgsql; fi", + // 6. Download readline (dependency for PostgreSQL) if not installed + "if ! command -v psql &> /dev/null; then curl -LO https://ftp.gnu.org/gnu/readline/readline-8.1.tar.gz; fi", + // 7. Extract readline package if not installed + "if ! command -v psql &> /dev/null; then tar -xvzf readline-8.1.tar.gz; fi", + // 8. Configure readline if not installed + "if ! command -v psql &> /dev/null; then cd readline-8.1 && ./configure --prefix=$HOME/pgsql; fi", + // 9. Add PostgreSQL to PATH if not installed + "if ! command -v psql &> /dev/null; then echo 'export PATH=$HOME/pgsql/bin:$PATH' >> ~/.bashrc; fi", + // 10. Source .bashrc to update PATH (even if psql was already installed) "source ~/.bashrc", - "mongosh --version", - `mongosh --host ${config.host} --port 10255 --username ${config.name} --password ${config.password} --ssl --sslAllowInvalidCertificates` + // 11. Verify PostgreSQL installation + "psql --version" ]; + case TerminalKind.Mongo: + case TerminalKind.VCoreMongo: + return [ + // 1. Fetch and display location details in a readable format + "curl -s https://ipinfo.io | jq -r '\"Region: \" + .region + \" Country: \" + .country + \" City: \" + .city + \" IP Addr: \" + .ip'", + // 2. Check if mongosh is installed; if not, proceed with installation + "if ! command -v mongosh &> /dev/null; then echo '⚠️ mongosh not found. Installing...'; fi", + // 3. Download mongosh if not installed + "if ! command -v mongosh &> /dev/null; then curl -LO https://downloads.mongodb.com/compass/mongosh-2.3.8-linux-x64.tgz; fi", + // 4. Extract mongosh package if not installed + "if ! command -v mongosh &> /dev/null; then tar -xvzf mongosh-2.3.8-linux-x64.tgz; fi", + // 5. Move mongosh binaries if not installed + "if ! command -v mongosh &> /dev/null; then mkdir -p ~/mongosh && mv mongosh-2.3.8-linux-x64/* ~/mongosh/; fi", + // 6. Add mongosh to PATH if not installed + "if ! command -v mongosh &> /dev/null; then echo 'export PATH=$HOME/mongosh/bin:$PATH' >> ~/.bashrc; fi", + // 7. Source .bashrc to update PATH (even if mongosh was already installed) + "source ~/.bashrc", + // 8. Verify mongosh installation + "mongosh --version", + // 9. Login to MongoDB + `mongosh --host ${config.host} --port 10255 --username ${config.name} --password ${config.password} --ssl --sslAllowInvalidCertificates` + ]; case TerminalKind.Cassandra: return [ - "curl -s https://ipinfo.io", - "curl -LO https://archive.apache.org/dist/cassandra/5.0.3/apache-cassandra-5.0.3-bin.tar.gz", - "tar -xvzf apache-cassandra-5.0.3-bin.tar.gz", - "mkdir -p ~/cassandra && mv apache-cassandra-5.0.3/* ~/cassandra/", - "echo 'export PATH=$PATH:$HOME/cassandra/bin' >> ~/.bashrc", - "echo 'export SSL_VERSION=TLSv1_2' >> ~/.bashrc", - "echo 'export SSL_VALIDATE=false' >> ~/.bashrc", + // 1. Fetch and display location details in a readable format + "curl -s https://ipinfo.io | jq -r '\"Region: \" + .region + \" Country: \" + .country + \" City: \" + .city + \" IP Addr: \" + .ip'", + // 2. Check if cqlsh is installed; if not, proceed with installation + "if ! command -v cqlsh &> /dev/null; then echo '⚠️ cqlsh not found. Installing...'; fi", + // 3. Download Cassandra if not installed + "if ! command -v cqlsh &> /dev/null; then curl -LO https://archive.apache.org/dist/cassandra/5.0.3/apache-cassandra-5.0.3-bin.tar.gz; fi", + // 4. Extract Cassandra package if not installed + "if ! command -v cqlsh &> /dev/null; then tar -xvzf apache-cassandra-5.0.3-bin.tar.gz; fi", + // 5. Move Cassandra binaries if not installed + "if ! command -v cqlsh &> /dev/null; then mkdir -p ~/cassandra && mv apache-cassandra-5.0.3/* ~/cassandra/; fi", + // 6. Add Cassandra to PATH if not installed + "if ! command -v cqlsh &> /dev/null; then echo 'export PATH=$HOME/cassandra/bin:$PATH' >> ~/.bashrc; fi", + // 7. Set environment variables for SSL + "if ! command -v cqlsh &> /dev/null; then echo 'export SSL_VERSION=TLSv1_2' >> ~/.bashrc; fi", + "if ! command -v cqlsh &> /dev/null; then echo 'export SSL_VALIDATE=false' >> ~/.bashrc; fi", + // 8. Source .bashrc to update PATH (even if cqlsh was already installed) "source ~/.bashrc", - + // 9. Verify cqlsh installation + "cqlsh --version", + // 10. Login to Cassandra `cqlsh ${config.host} 10350 -u ${config.name} -p ${config.password} --ssl --protocol-version=4` ]; default: diff --git a/src/Explorer/Tabs/CloudShellTab/Data.tsx b/src/Explorer/Tabs/CloudShellTab/Data.tsx index b09a882db..9e9eb4105 100644 --- a/src/Explorer/Tabs/CloudShellTab/Data.tsx +++ b/src/Explorer/Tabs/CloudShellTab/Data.tsx @@ -4,11 +4,10 @@ import { v4 as uuidv4 } from 'uuid'; import { configContext } from "../../../ConfigContext"; +import { userContext } from '../../../UserContext'; import { armRequest } from "../../../Utils/arm/request"; import { Authorization, ConnectTerminalResponse, NetworkType, OsType, ProvisionConsoleResponse, SessionType, Settings, ShellType } from "./DataModels"; -const cloudshellToken = ""; - export const validateUserSettings = (userSettings: Settings) => { if (userSettings.sessionType !== SessionType.Ephemeral && userSettings.osType !== OsType.Linux) { return false; @@ -27,15 +26,21 @@ export const getUserRegion = async (subscriptionId: string, resourceGroup: strin }; +export const deleteUserSettings = async (): Promise => { + await armRequest({ + host: configContext.ARM_ENDPOINT, + path: `/providers/Microsoft.Portal/userSettings/cloudconsole`, + method: "DELETE", + apiVersion: "2023-02-01-preview" + }); +}; + export const getUserSettings = async (): Promise => { const resp = await armRequest({ host: configContext.ARM_ENDPOINT, path: `/providers/Microsoft.Portal/userSettings/cloudconsole`, method: "GET", - apiVersion: "2023-02-01-preview", - customHeaders: { - "Authorization": cloudshellToken // Temporily use a hardcoded token - } + apiVersion: "2023-02-01-preview" }); return { @@ -55,12 +60,7 @@ export const putEphemeralUserSettings = async (userSubscriptionId: string, userR fontSize: "Medium", fontStyle: "monospace" }, - vnetSettings: { - networkProfileResourceId: "/subscriptions/80be3961-0521-4a0a-8570-5cd5a4e2f98c/resourceGroups/neesharma-stage/providers/Microsoft.Network/networkProfiles/aci-networkProfile-eastus2", - relayNamespaceResourceId: "/subscriptions/80be3961-0521-4a0a-8570-5cd5a4e2f98c/resourceGroups/neesharma-stage/providers/Microsoft.Relay/namespaces/neesharma-stage-relay-namespace", - location: "eastus2" - }, - networkType: NetworkType.Isolated, + networkType: NetworkType.Default, sessionType: SessionType.Ephemeral, userSubscription: userSubscriptionId, } @@ -71,25 +71,19 @@ export const putEphemeralUserSettings = async (userSubscriptionId: string, userR path: `/providers/Microsoft.Portal/userSettings/cloudconsole`, method: "PUT", apiVersion: "2023-02-01-preview", - body: ephemeralSettings, - customHeaders: { - "Authorization": cloudshellToken // Temporily use a hardcoded token - } + body: ephemeralSettings }); return resp; }; -export const verifyCloudshellProviderRegistration = async(subscriptionId: string) => { +export const verifyCloudShellProviderRegistration = async(subscriptionId: string) => { return await armRequest({ host: configContext.ARM_ENDPOINT, path: `/subscriptions/${subscriptionId}/providers/Microsoft.CloudShell`, method: "GET", - apiVersion: "2022-12-01", - customHeaders: { - "Authorization": cloudshellToken // Temporily use a hardcoded token - } + apiVersion: "2022-12-01" }); }; @@ -98,10 +92,7 @@ export const registerCloudShellProvider = async (subscriptionId: string) => { host: configContext.ARM_ENDPOINT, path: `/subscriptions/${subscriptionId}/providers/Microsoft.CloudShell/register`, method: "POST", - apiVersion: "2022-12-01", - customHeaders: { - "Authorization": cloudshellToken // Temporily use a hardcoded token - } + apiVersion: "2022-12-01" }); }; @@ -118,8 +109,7 @@ export const provisionConsole = async (subscriptionId: string, location: string) method: "PUT", apiVersion: "2023-02-01-preview", customHeaders: { - 'x-ms-console-preferred-location': location, - "Authorization": cloudshellToken // Temporily use a hardcoded token + 'x-ms-console-preferred-location': location }, body: data, }); @@ -128,12 +118,12 @@ export const provisionConsole = async (subscriptionId: string, location: string) export const connectTerminal = async (consoleUri: string, size: { rows: number, cols: number }): Promise => { const targetUri = consoleUri + `/terminals?cols=${size.cols}&rows=${size.rows}&version=2019-01-01&shell=bash`; const resp = await fetch(targetUri, { - method: "post", + method: "POST", headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Content-Length': '2', - 'Authorization': cloudshellToken, + 'Authorization': userContext.authorizationToken, 'x-ms-client-request-id': uuidv4(), 'Accept-Language': getLocale(), }, @@ -145,10 +135,10 @@ export const connectTerminal = async (consoleUri: string, size: { rows: number, export const authorizeSession = async (consoleUri: string): Promise => { const targetUri = consoleUri + "/authorize"; const resp = await fetch(targetUri, { - method: "post", + method: "POST", headers: { 'Accept': 'application/json', - 'Authorization': cloudshellToken, + 'Authorization': userContext.authorizationToken, 'Accept-Language': getLocale(), "Content-Type": 'application/json' }, diff --git a/src/Explorer/Tabs/CloudShellTab/UseTerminal.tsx b/src/Explorer/Tabs/CloudShellTab/UseTerminal.tsx index f6756f132..7ec2aefb0 100644 --- a/src/Explorer/Tabs/CloudShellTab/UseTerminal.tsx +++ b/src/Explorer/Tabs/CloudShellTab/UseTerminal.tsx @@ -2,59 +2,62 @@ * Copyright (c) Microsoft Corporation. All rights reserved. */ +import { listKeys } from "Utils/arm/generatedClients/cosmos/databaseAccounts"; import { Terminal } from "xterm"; import { TerminalKind } from "../../../Contracts/ViewModels"; import { userContext } from "../../../UserContext"; import { AttachAddon } from "./AttachAddOn"; import { getCommands } from "./Commands"; -import { authorizeSession, connectTerminal, getNormalizedRegion, getUserSettings, provisionConsole, putEphemeralUserSettings, registerCloudShellProvider, validateUserSettings, verifyCloudshellProviderRegistration } from "./Data"; +import { authorizeSession, connectTerminal, deleteUserSettings, getNormalizedRegion, getUserSettings, provisionConsole, putEphemeralUserSettings, registerCloudShellProvider, validateUserSettings, verifyCloudShellProviderRegistration } from "./Data"; import { LogError, LogInfo } from "./LogFormatter"; -import { listKeys } from "Utils/arm/generatedClients/cosmos/databaseAccounts"; -export const startCloudShellterminal = async (xterminal: Terminal, shellType: TerminalKind) => { +export const startCloudShellTerminal = async (terminal: Terminal, shellType: TerminalKind) => { - // validate that the subscription id is registered in the Cloudshell namespace + // validate that the subscription id is registered in the CloudShell namespace try { - const response: any = await verifyCloudshellProviderRegistration(userContext.subscriptionId); + const response: any = await verifyCloudShellProviderRegistration(userContext.subscriptionId); if (response.registrationState !== "Registered") { await registerCloudShellProvider(userContext.subscriptionId); } } catch (err) { - xterminal.writeln(LogError('Unable to verify cloudshell provider registration.')); + terminal.writeln(LogError('Unable to verify CloudShell provider registration.')); throw err; } const region = userContext.databaseAccount?.location; - xterminal.writeln(LogInfo(`Database Acount Region identified as '${region}'`)); + terminal.writeln(LogInfo(`Database Account Region identified as '${region}'`)); - const defaultCloudshellRegion = "westus"; - const resolvedRegion = getNormalizedRegion(region, defaultCloudshellRegion); - - xterminal.writeln(LogInfo(`Requesting Cloudshell instance at '${resolvedRegion}'`)); + const defaultCloudShellRegion = "westus"; + const resolvedRegion = getNormalizedRegion(region, defaultCloudShellRegion); try { - var { socketUri, provisionConsoleResponse, targetUri } = await provisionCloudShellSession(resolvedRegion, xterminal); + var { socketUri, provisionConsoleResponse, targetUri } = await provisionCloudShellSession(resolvedRegion, terminal); } catch (err) { - xterminal.writeln(LogError(err)); - xterminal.writeln(LogError(`Unable to provision console in request region, Falling back to default region i.e. ${defaultCloudshellRegion}`)); - var { socketUri, provisionConsoleResponse, targetUri } = await provisionCloudShellSession(defaultCloudshellRegion, xterminal); + terminal.writeln(LogError(err)); + terminal.writeln(LogError(`Unable to provision console in request region, Falling back to default region i.e. ${defaultCloudShellRegion}`)); + var { socketUri, provisionConsoleResponse, targetUri } = await provisionCloudShellSession(defaultCloudShellRegion, terminal); } if(!socketUri) { - xterminal.writeln(LogError('Unable to provision console. Close and Open the terminal again to retry.')); + terminal.writeln(LogError('Unable to provision console. Close and Open the terminal again to retry.')); return{}; } let socket = new WebSocket(socketUri); - let keys = await listKeys(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name); - - const initCommands = getCommands(shellType, keys.primaryMasterKey); - socket = configureSocket(socket, socketUri, xterminal, initCommands, 0); + const dbName = userContext.databaseAccount.name; + let keys; + if (dbName) + { + keys = await listKeys(userContext.subscriptionId, userContext.resourceGroup, dbName); + } + + const initCommands = getCommands(shellType, keys?.primaryMasterKey); + socket = configureSocket(socket, socketUri, terminal, initCommands, 0); const attachAddon = new AttachAddon(socket); - xterminal.loadAddon(attachAddon); + terminal.loadAddon(attachAddon); // authorize the session try { @@ -63,13 +66,13 @@ export const startCloudShellterminal = async (xterminal: Terminal, shellType: Te const a = document.createElement("img"); a.src = targetUri + "&token=" + encodeURIComponent(cookieToken); } catch (err) { - xterminal.writeln(LogError('Unable to authroize the session')); + terminal.writeln(LogError('Unable to authorize the session')); socket.close(); throw err; } - xterminal.writeln(LogInfo("Connection Successful!!!")); - xterminal.focus(); + terminal.writeln(LogInfo("Connection Successful!!!")); + terminal.focus(); return socket; } @@ -79,22 +82,8 @@ let pingCount = 0; export const configureSocket = (socket: WebSocket, uri: string, terminal: any, initCommands: string, socketRetryCount: number) => { let jsonData = ''; - socket.onopen = () => { - socket.send(initCommands); - const keepSocketAlive = (socket: WebSocket) => { - if (socket.readyState === WebSocket.OPEN) { - if ((pingCount / 60) >= 20) { - socket.close(); - } else { - socket.send(''); - pingCount++; - keepAliveID = setTimeout(() => keepSocketAlive(socket), 1000); - } - } - }; - keepSocketAlive(socket); - }; + sendStartupCommands(socket, initCommands); socket.onclose = () => { if (keepAliveID) { @@ -154,28 +143,53 @@ export const configureSocket = (socket: WebSocket, uri: string, terminal: any, return socket; }; +const sendStartupCommands = (socket: WebSocket, initCommands: string) => { + if (socket && socket.readyState === WebSocket.OPEN) { + socket.send(initCommands); // Example startup command + } else { + socket.onopen = () => { + socket.send(initCommands); + + const keepSocketAlive = (socket: WebSocket) => { + if (socket.readyState === WebSocket.OPEN) { + if ((pingCount / 60) >= 20) { + socket.close(); + } else { + socket.send(''); + pingCount++; + keepAliveID = setTimeout(() => keepSocketAlive(socket), 1000); + } + } + }; + keepSocketAlive(socket); + }; + } +}; + const provisionCloudShellSession = async( resolvedRegion: string, - xterminal: Terminal + terminal: Terminal ): Promise<{ socketUri?: string; provisionConsoleResponse?: any; targetUri?: string }> => { return new Promise((resolve, reject) => { // Show consent message inside the terminal - xterminal.writeln(`\x1B[1;33m⚠️ Are you agreeing to continue with CloudShell terminal at ${resolvedRegion}.\x1B[0m`); - xterminal.writeln("\x1B[1;37mPress 'Y' to continue or 'N' to exit.\x1B[0m"); + terminal.writeln(`\x1B[1;33m⚠️ Are you agreeing to continue with CloudShell terminal at ${resolvedRegion}.\x1B[0m`); + terminal.writeln("\x1B[1;37mPress 'Y' to continue or 'N' to exit.\x1B[0m"); - xterminal.focus(); + terminal.focus(); // Listen for user input - const handleKeyPress = xterminal.onKey(async ({ key }: { key: string }) => { + const handleKeyPress = terminal.onKey(async ({ key }: { key: string }) => { // Remove the event listener after first execution handleKeyPress.dispose(); if (key.toLowerCase() === "y") { - xterminal.writeln("\x1B[1;32mConsent given. Requesting CloudShell. !\x1B[0m"); - + terminal.writeln("\x1B[1;32mConsent given. Requesting CloudShell. !\x1B[0m"); + terminal.writeln(LogInfo('Resetting user settings...')); + await deleteUserSettings(); + terminal.writeln(LogInfo('Applying fresh user settings...')); try { await putEphemeralUserSettings(userContext.subscriptionId, resolvedRegion); } catch (err) { - xterminal.writeln(LogError('Unable to update user settings to ephemeral session.')); + terminal.writeln(LogError('Unable to update user settings to ephemeral session.')); return reject(err); } @@ -187,7 +201,7 @@ const provisionCloudShellSession = async( throw new Error("Invalid user settings detected for ephemeral session."); } } catch (err) { - xterminal.writeln(LogError('Unable to verify user settings for ephemeral session.')); + terminal.writeln(LogError('Unable to verify user settings for ephemeral session.')); return reject(err); } @@ -196,27 +210,26 @@ const provisionCloudShellSession = async( try { provisionConsoleResponse = await provisionConsole(userContext.subscriptionId, resolvedRegion); } catch (err) { - xterminal.writeln(LogError('Unable to provision console.\n\r')); + terminal.writeln(LogError('Unable to provision console.')); return reject(err); } if (provisionConsoleResponse.properties.provisioningState !== "Succeeded") { - xterminal.writeln(LogError("Failed to provision console.\n\r")); + terminal.writeln(LogError("Failed to provision console.")); return reject(new Error("Failed to provision console.")); } - xterminal.writeln(LogInfo("Connecting to Cloudshell Terminal...\n\r")); + terminal.writeln(LogInfo("Connecting to CloudShell Terminal...")); // connect the terminal let connectTerminalResponse; try { - connectTerminalResponse = await connectTerminal(provisionConsoleResponse.properties.uri, { rows: xterminal.rows, cols: xterminal.cols }); + connectTerminalResponse = await connectTerminal(provisionConsoleResponse.properties.uri, { rows: terminal.rows, cols: terminal.cols }); } catch (err) { - xterminal.writeln(''); - xterminal.writeln(LogError('Unable to connect terminal.')); + terminal.writeln(LogError('Unable to connect terminal.')); return reject(err); } - const targetUri = provisionConsoleResponse.properties.uri + `/terminals?cols=${xterminal.cols}&rows=${xterminal.rows}&version=2019-01-01&shell=bash`; + const targetUri = provisionConsoleResponse.properties.uri + `/terminals?cols=${terminal.cols}&rows=${terminal.rows}&version=2019-01-01&shell=bash`; const termId = connectTerminalResponse.id; let socketUri = connectTerminalResponse.socketUri.replace(":443/", ""); @@ -232,8 +245,8 @@ const provisionCloudShellSession = async( } else if (key.toLowerCase() === "n") { - xterminal.writeln("\x1B[1;31m Consent denied. Exiting...\x1B[0m"); - setTimeout(() => xterminal.dispose(), 2000); // Close terminal after 2 sec + terminal.writeln("\x1B[1;31m Consent denied. Exiting...\x1B[0m"); + setTimeout(() => terminal.dispose(), 2000); // Close terminal after 2 sec return resolve({}); } }); diff --git a/src/Explorer/Tabs/Shared/CheckFirewallRules.ts b/src/Explorer/Tabs/Shared/CheckFirewallRules.ts index b44bfaeac..d2774ac40 100644 --- a/src/Explorer/Tabs/Shared/CheckFirewallRules.ts +++ b/src/Explorer/Tabs/Shared/CheckFirewallRules.ts @@ -41,4 +41,4 @@ export async function checkFirewallRules( 30000, ); } -} +} \ No newline at end of file diff --git a/src/Explorer/Tabs/TerminalTab.tsx b/src/Explorer/Tabs/TerminalTab.tsx index c4450d0c7..918f89726 100644 --- a/src/Explorer/Tabs/TerminalTab.tsx +++ b/src/Explorer/Tabs/TerminalTab.tsx @@ -114,8 +114,6 @@ export default class TerminalTab extends TabsBase { this.container = options.container; this.isAllPublicIPAddressesEnabled = ko.observable(true); - this.initializeNotebookTerminalAdapter(options); - if (options.kind === ViewModels.TerminalKind.Postgres) { checkFirewallRules( "2022-11-08", @@ -133,6 +131,9 @@ export default class TerminalTab extends TabsBase { this.isAllPublicIPAddressesEnabled, ); } + + this.initializeNotebookTerminalAdapter(options); + } private initializeNotebookTerminalAdapter(options: TerminalTabOptions): void { diff --git a/src/Utils/arm/request.ts b/src/Utils/arm/request.ts index 21434fbf1..96196e477 100644 --- a/src/Utils/arm/request.ts +++ b/src/Utils/arm/request.ts @@ -99,8 +99,15 @@ export async function armRequestWithoutPolling({ } const operationStatusUrl = (response.headers && response.headers.get("location")) || ""; - const responseBody = (await response.json()) as T; - return { result: responseBody, operationStatusUrl: operationStatusUrl }; + if(!response || response.status === 204) { + return { result: {} as T, operationStatusUrl: operationStatusUrl }; + } + + const responseBody = await response.json().catch((error) => { + console.error("armRequestWithoutPolling: Error parsing JSON response:", error); + return response.text; // Return an empty object if JSON parsing fails + }); + return { result: responseBody as T, operationStatusUrl: operationStatusUrl }; } // TODO: This is very similar to what is happening in ResourceProviderClient.ts. Should probably merge them. @@ -211,6 +218,14 @@ export async function getOfferingIdsRequest({ } const operationStatusUrl = (response.headers && response.headers.get("location")) || ""; - const responseBody = (await response.json()) as T; - return { result: responseBody, operationStatusUrl: operationStatusUrl }; + if(!response || response.status === 204) { + return { result: {} as T, operationStatusUrl: operationStatusUrl }; + } + + const responseBody = await response.json().catch((error) => { + console.error("getOfferingIdsRequest: Error parsing JSON response:", error); + return response.text; // Return an empty object if JSON parsing fails + }); + + return { result: responseBody as T, operationStatusUrl: operationStatusUrl }; }