mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-04-14 21:58:14 +01:00
fix terminals
This commit is contained in:
parent
44e85647e4
commit
83eafd4485
@ -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<CloudShellTerminalProps> = ({
|
||||
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) {
|
||||
|
@ -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:
|
||||
|
@ -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<void> => {
|
||||
await armRequest<void>({
|
||||
host: configContext.ARM_ENDPOINT,
|
||||
path: `/providers/Microsoft.Portal/userSettings/cloudconsole`,
|
||||
method: "DELETE",
|
||||
apiVersion: "2023-02-01-preview"
|
||||
});
|
||||
};
|
||||
|
||||
export const getUserSettings = async (): Promise<Settings> => {
|
||||
const resp = await armRequest<any>({
|
||||
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<ConnectTerminalResponse> => {
|
||||
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<Authorization> => {
|
||||
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'
|
||||
},
|
||||
|
@ -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({});
|
||||
}
|
||||
});
|
||||
|
@ -41,4 +41,4 @@ export async function checkFirewallRules(
|
||||
30000,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -99,8 +99,15 @@ export async function armRequestWithoutPolling<T>({
|
||||
}
|
||||
|
||||
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<T>({
|
||||
}
|
||||
|
||||
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 };
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user