mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-04-28 12:35:10 +01:00
refactor code and add casandra and mongo commands
This commit is contained in:
parent
41439cc7d4
commit
9b2cb8a1a9
126
src/Explorer/Tabs/CloudShellTab/AttachAddOn.tsx
Normal file
126
src/Explorer/Tabs/CloudShellTab/AttachAddOn.tsx
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { IDisposable, ITerminalAddon, Terminal } from 'xterm';
|
||||||
|
|
||||||
|
interface IAttachOptions {
|
||||||
|
bidirectional?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AttachAddon implements ITerminalAddon {
|
||||||
|
private _socket: WebSocket;
|
||||||
|
private _bidirectional: boolean;
|
||||||
|
private _disposables: IDisposable[] = [];
|
||||||
|
private _socketData: string;
|
||||||
|
|
||||||
|
constructor(socket: WebSocket, options?: IAttachOptions) {
|
||||||
|
this._socket = socket;
|
||||||
|
// always set binary type to arraybuffer, we do not handle blobs
|
||||||
|
this._socket.binaryType = 'arraybuffer';
|
||||||
|
this._bidirectional = !(options && options.bidirectional === false);
|
||||||
|
this._socketData = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public activate(terminal: Terminal): void {
|
||||||
|
this._disposables.push(
|
||||||
|
addSocketListener(this._socket, 'message', ev => {
|
||||||
|
let data: ArrayBuffer | string = ev.data;
|
||||||
|
const startStatusJson = 'ie_us';
|
||||||
|
const endStatusJson = 'ie_ue';
|
||||||
|
|
||||||
|
if (typeof data === 'object') {
|
||||||
|
const enc = new TextDecoder("utf-8");
|
||||||
|
data = enc.decode(ev.data as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
// for example of json object look in TerminalHelper in the socket.onMessage
|
||||||
|
if (data.includes(startStatusJson) && data.includes(endStatusJson)) {
|
||||||
|
// process as one line
|
||||||
|
const statusData = data.split(startStatusJson)[1].split(endStatusJson)[0];
|
||||||
|
data = data.replace(statusData, '');
|
||||||
|
data = data.replace(startStatusJson, '');
|
||||||
|
data = data.replace(endStatusJson, '');
|
||||||
|
} else if (data.includes(startStatusJson)) {
|
||||||
|
// check for start
|
||||||
|
const partialStatusData = data.split(startStatusJson)[1];
|
||||||
|
this._socketData += partialStatusData;
|
||||||
|
data = data.replace(partialStatusData, '');
|
||||||
|
data = data.replace(startStatusJson, '');
|
||||||
|
} else if (data.includes(endStatusJson)) {
|
||||||
|
// check for end and process the command
|
||||||
|
const partialStatusData = data.split(endStatusJson)[0];
|
||||||
|
this._socketData += partialStatusData;
|
||||||
|
data = data.replace(partialStatusData, '');
|
||||||
|
data = data.replace(endStatusJson, '');
|
||||||
|
this._socketData = '';
|
||||||
|
} else if (this._socketData.length > 0) {
|
||||||
|
// check if the line is all data then just concatenate
|
||||||
|
this._socketData += data;
|
||||||
|
data = '';
|
||||||
|
}
|
||||||
|
terminal.write(data);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this._bidirectional) {
|
||||||
|
this._disposables.push(terminal.onData(data => this._sendData(data)));
|
||||||
|
this._disposables.push(terminal.onBinary(data => this._sendBinary(data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
this._disposables.push(addSocketListener(this._socket, 'close', () => this.dispose()));
|
||||||
|
this._disposables.push(addSocketListener(this._socket, 'error', () => this.dispose()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
for (const d of this._disposables) {
|
||||||
|
d.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _sendData(data: string): void {
|
||||||
|
if (!this._checkOpenSocket()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._socket.send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _sendBinary(data: string): void {
|
||||||
|
if (!this._checkOpenSocket()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const buffer = new Uint8Array(data.length);
|
||||||
|
for (let i = 0; i < data.length; ++i) {
|
||||||
|
buffer[i] = data.charCodeAt(i) & 255;
|
||||||
|
}
|
||||||
|
this._socket.send(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _checkOpenSocket(): boolean {
|
||||||
|
switch (this._socket.readyState) {
|
||||||
|
case WebSocket.OPEN:
|
||||||
|
return true;
|
||||||
|
case WebSocket.CONNECTING:
|
||||||
|
throw new Error('Attach addon was loaded before socket was open');
|
||||||
|
case WebSocket.CLOSING:
|
||||||
|
return false;
|
||||||
|
case WebSocket.CLOSED:
|
||||||
|
throw new Error('Attach addon socket is closed');
|
||||||
|
default:
|
||||||
|
throw new Error('Unexpected socket state');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSocketListener<K extends keyof WebSocketEventMap>(socket: WebSocket, type: K, handler: (this: WebSocket, ev: WebSocketEventMap[K]) => any): IDisposable {
|
||||||
|
socket.addEventListener(type, handler);
|
||||||
|
return {
|
||||||
|
dispose: () => {
|
||||||
|
if (!handler) {
|
||||||
|
// Already disposed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
socket.removeEventListener(type, handler);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -1,3 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { configContext } from "../../../ConfigContext";
|
import { configContext } from "../../../ConfigContext";
|
||||||
import { armRequest } from "../../../Utils/arm/request";
|
import { armRequest } from "../../../Utils/arm/request";
|
||||||
@ -23,10 +27,10 @@ export const trackedApiCall = <T extends Array<any>, U>(apiCall: (...args: T) =>
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getUserRegion = trackedApiCall(async (subscriptionId: string) => {
|
export const getUserRegion = trackedApiCall(async (subscriptionId: string, resourceGroup: string, accountName: string) => {
|
||||||
return await armRequest({
|
return await armRequest({
|
||||||
host: configContext.ARM_ENDPOINT,
|
host: configContext.ARM_ENDPOINT,
|
||||||
path: `/subscriptions/${subscriptionId}/locations`,
|
path: `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
apiVersion: "2022-12-01"
|
apiVersion: "2022-12-01"
|
||||||
});
|
});
|
||||||
@ -158,3 +162,19 @@ export const getLocale = () => {
|
|||||||
const langLocale = navigator.language;
|
const langLocale = navigator.language;
|
||||||
return (langLocale && langLocale.length === 2 ? langLocale[1] : 'en-us');
|
return (langLocale && langLocale.length === 2 ? langLocale[1] : 'en-us');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const validCloudShellRegions = new Set(["westus", "southcentralus", "eastus", "northeurope", "westeurope", "centralindia", "southeastasia", "westcentralus", "eastus2euap", "centraluseuap"]);
|
||||||
|
const defaultCloudshellRegion = "westus";
|
||||||
|
|
||||||
|
export const getNormalizedRegion = (region: string) => {
|
||||||
|
if (!region) return defaultCloudshellRegion;
|
||||||
|
|
||||||
|
const regionMap: Record<string, string> = {
|
||||||
|
"centralus": "centraluseuap",
|
||||||
|
"eastus2": "eastus2euap"
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizedRegion = regionMap[region.toLowerCase()] || region;
|
||||||
|
return validCloudShellRegions.has(normalizedRegion.toLowerCase()) ? normalizedRegion : defaultCloudshellRegion;
|
||||||
|
};
|
||||||
|
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
export const enum OsType {
|
export const enum OsType {
|
||||||
Linux = "linux",
|
Linux = "linux",
|
||||||
Windows = "windows"
|
Windows = "windows"
|
||||||
|
11
src/Explorer/Tabs/CloudShellTab/LogFormatter.tsx
Normal file
11
src/Explorer/Tabs/CloudShellTab/LogFormatter.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const LogError = (message: string) => {
|
||||||
|
return `\n\r\x1B[1;37m${message}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LogInfo = (message: string) => {
|
||||||
|
return `\x1B[1;37m${message}`;
|
||||||
|
}
|
@ -1,15 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
import { Terminal } from "xterm";
|
import { Terminal } from "xterm";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { authorizeSession, connectTerminal, getUserRegion, getUserSettings, provisionConsole, putEphemeralUserSettings, registerCloudShellProvider, validateUserSettings, verifyCloudshellProviderRegistration } from "./Data";
|
import { AttachAddon } from "./AttachAddOn";
|
||||||
|
import { authorizeSession, connectTerminal, getNormalizedRegion, getUserSettings, provisionConsole, putEphemeralUserSettings, registerCloudShellProvider, validateUserSettings, verifyCloudshellProviderRegistration } from "./Data";
|
||||||
|
import { LogError, LogInfo } from "./LogFormatter";
|
||||||
|
|
||||||
export const startCloudShellterminal = async (xterminal: Terminal, intervalsToClearRef: any, authorizationToken: any) => {
|
export const startCloudShellterminal = async (xterminal: Terminal, initCommands: string, authorizationToken: any) => {
|
||||||
|
|
||||||
const tokenInterval = setInterval(async () => {
|
|
||||||
authorizationToken
|
|
||||||
}, 1000 * 60 * 10);
|
|
||||||
|
|
||||||
const intervalsToClear = intervalsToClearRef.current ?? [];
|
|
||||||
intervalsToClear.push(tokenInterval);
|
|
||||||
|
|
||||||
// validate that the subscription id is registered in the Cloudshell namespace
|
// validate that the subscription id is registered in the Cloudshell namespace
|
||||||
try {
|
try {
|
||||||
@ -18,39 +17,21 @@ export const startCloudShellterminal = async (xterminal: Terminal, intervalsToCl
|
|||||||
await registerCloudShellProvider(userContext.subscriptionId);
|
await registerCloudShellProvider(userContext.subscriptionId);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
xterminal.writeln('');
|
xterminal.writeln(LogError('Unable to verify cloudshell provider registration.'));
|
||||||
xterminal.writeln('Unable to verify cloudshell provider registration.');
|
|
||||||
intervalsToClear.forEach((val) => window.clearInterval(+val));
|
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
const region = await getUserRegion(userContext.subscriptionId).then((res) => {
|
const region = userContext.databaseAccount?.location;
|
||||||
const reqId = (res.headers as any).get("x-ms-routing-request-id");
|
xterminal.writeln(LogInfo(`Database Acount Region identified as '${region}'`));
|
||||||
const location = reqId?.split(":")?.[0]?.toLowerCase() ?? "";
|
|
||||||
const validRegions = new Set(["westus", "southcentralus", "eastus", "northeurope", "westeurope", "centralindia", "southeastasia", "westcentralus", "eastus2euap", "centraluseuap"]);
|
|
||||||
if (validRegions.has(location.toLowerCase())) {
|
|
||||||
return location;
|
|
||||||
}
|
|
||||||
if (location === "centralus") {
|
|
||||||
return "centraluseuap";
|
|
||||||
}
|
|
||||||
if (location === "eastus2") {
|
|
||||||
return "eastus2euap";
|
|
||||||
}
|
|
||||||
return "westus";
|
|
||||||
}).catch((err) => {
|
|
||||||
xterminal.writeln('');
|
|
||||||
xterminal.writeln('Unable to get user region.');
|
|
||||||
return "westus";
|
|
||||||
});
|
|
||||||
|
|
||||||
xterminal.writeln('Requested Region ' + region);
|
const resolvedRegion = getNormalizedRegion(region);
|
||||||
|
xterminal.writeln(LogInfo(`Requesting Cloudshell instance at '${resolvedRegion}'`));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// do not use the subscription from the preferred settings use the one from the context
|
// do not use the subscription from the preferred settings use the one from the context
|
||||||
await putEphemeralUserSettings(userContext.subscriptionId, region);
|
await putEphemeralUserSettings(userContext.subscriptionId, resolvedRegion);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
xterminal.writeln('Unable to update user settings to ephemeral session.');
|
xterminal.writeln(LogError('Unable to update user settings to ephemeral session.'));
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,34 +43,33 @@ export const startCloudShellterminal = async (xterminal: Terminal, intervalsToCl
|
|||||||
throw new Error("Invalid user settings detected for ephemeral session.");
|
throw new Error("Invalid user settings detected for ephemeral session.");
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
xterminal.writeln('Unable to verify user settings for ephemeral session.');
|
xterminal.writeln(LogError('Unable to verify user settings for ephemeral session.'));
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
// trigger callback to provision console internal
|
// trigger callback to provision console internal
|
||||||
let provisionConsoleResponse;
|
let provisionConsoleResponse;
|
||||||
try {
|
try {
|
||||||
provisionConsoleResponse = await provisionConsole(userContext.subscriptionId, region);
|
provisionConsoleResponse = await provisionConsole(userContext.subscriptionId, resolvedRegion);
|
||||||
// statusPaneUpdateCommands.setTerminalUri(provisionConsoleResponse.properties.uri);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
xterminal.writeln('Unable to provision console.');
|
xterminal.writeln(LogError('Unable to provision console.\n\r'));
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (provisionConsoleResponse.properties.provisioningState !== "Succeeded") {
|
if (provisionConsoleResponse.properties.provisioningState !== "Succeeded") {
|
||||||
xterminal.writeln("Failed to provision console.");
|
xterminal.writeln(LogError("Failed to provision console.\n\r"));
|
||||||
throw new Error("Failed to provision console.");
|
throw new Error("Failed to provision console.");
|
||||||
}
|
}
|
||||||
|
|
||||||
xterminal.writeln("Connecting to cloudshell...");
|
xterminal.writeln(LogInfo("Connecting to cloudshell"));
|
||||||
xterminal.writeln("Please wait...");
|
xterminal.writeln(LogInfo("Please wait..."));
|
||||||
// connect the terminal
|
// connect the terminal
|
||||||
let connectTerminalResponse;
|
let connectTerminalResponse;
|
||||||
try {
|
try {
|
||||||
connectTerminalResponse = await connectTerminal(provisionConsoleResponse.properties.uri, { rows: xterminal.rows, cols: xterminal.cols });
|
connectTerminalResponse = await connectTerminal(provisionConsoleResponse.properties.uri, { rows: xterminal.rows, cols: xterminal.cols });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
xterminal.writeln('');
|
xterminal.writeln('');
|
||||||
xterminal.writeln('Unable to connect terminal.');
|
xterminal.writeln(LogError('Unable to connect terminal.'));
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,16 +86,12 @@ export const startCloudShellterminal = async (xterminal: Terminal, intervalsToCl
|
|||||||
socketUri = 'wss://' + targetUriBodyArr[0] + '/$hc/' + targetUriBodyArr[1] + '/terminals/' + termId;
|
socketUri = 'wss://' + targetUriBodyArr[0] + '/$hc/' + targetUriBodyArr[1] + '/terminals/' + termId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// // provision appropriate first party permissions to cloudshell instance
|
|
||||||
// await postTokens(provisionConsoleResponse.properties.uri, authorizationToken).catch((err) => {
|
|
||||||
// xterminal.writeln('Unable to provision first party permissions to cloudshell instance.');
|
|
||||||
// intervalsToClear.forEach((val) => window.clearInterval(+val));
|
|
||||||
// throw err;
|
|
||||||
// });
|
|
||||||
|
|
||||||
const socket = new WebSocket(socketUri);
|
const socket = new WebSocket(socketUri);
|
||||||
|
|
||||||
configureSocket(socket, socketUri, xterminal, intervalsToClear, 0);
|
configureSocket(socket, socketUri, xterminal, initCommands, 0);
|
||||||
|
|
||||||
|
const attachAddon = new AttachAddon(socket);
|
||||||
|
xterminal.loadAddon(attachAddon);
|
||||||
|
|
||||||
// authorize the session
|
// authorize the session
|
||||||
try {
|
try {
|
||||||
@ -124,12 +100,12 @@ export const startCloudShellterminal = async (xterminal: Terminal, intervalsToCl
|
|||||||
const a = document.createElement("img");
|
const a = document.createElement("img");
|
||||||
a.src = targetUri + "&token=" + encodeURIComponent(cookieToken);
|
a.src = targetUri + "&token=" + encodeURIComponent(cookieToken);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
xterminal.writeln('Unable to authroize the session');
|
xterminal.writeln(LogError('Unable to authroize the session'));
|
||||||
socket.close();
|
socket.close();
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
xterminal.writeln("Connected to cloudshell.");
|
xterminal.writeln(LogInfo("Connection Successful!!!"));
|
||||||
xterminal.focus();
|
xterminal.focus();
|
||||||
|
|
||||||
return socket;
|
return socket;
|
||||||
@ -138,21 +114,10 @@ export const startCloudShellterminal = async (xterminal: Terminal, intervalsToCl
|
|||||||
let keepAliveID: NodeJS.Timeout = null;
|
let keepAliveID: NodeJS.Timeout = null;
|
||||||
let pingCount = 0;
|
let pingCount = 0;
|
||||||
|
|
||||||
export const configureSocket = (socket: WebSocket, uri: string, terminal: any, intervals: NodeJS.Timer[], socketRetryCount: number) => {
|
export const configureSocket = (socket: WebSocket, uri: string, terminal: any, initCommands: string, socketRetryCount: number) => {
|
||||||
let jsonData = '';
|
let jsonData = '';
|
||||||
socket.onopen = () => {
|
socket.onopen = () => {
|
||||||
terminal.writeln("Socket Opened");
|
socket.send(initCommands);
|
||||||
const initializeCommand =
|
|
||||||
`curl -s https://ipinfo.io \n` +
|
|
||||||
`curl -LO https://downloads.mongodb.com/compass/mongosh-2.3.8-linux-x64.tgz \n` +
|
|
||||||
`tar -xvzf mongosh-2.3.8-linux-x64.tgz \n` +
|
|
||||||
`mkdir -p ~/mongosh && mv mongosh-2.3.8-linux-x64/* ~/mongosh/ \n` +
|
|
||||||
`echo 'export PATH=$PATH:$HOME/mongosh/bin' >> ~/.bashrc \n` +
|
|
||||||
`source ~/.bashrc \n` +
|
|
||||||
`mongosh --version \n`;
|
|
||||||
|
|
||||||
terminal.writeln(initializeCommand);
|
|
||||||
socket.send(initializeCommand);
|
|
||||||
|
|
||||||
const keepSocketAlive = (socket: WebSocket) => {
|
const keepSocketAlive = (socket: WebSocket) => {
|
||||||
if (socket.readyState === WebSocket.OPEN) {
|
if (socket.readyState === WebSocket.OPEN) {
|
||||||
@ -169,32 +134,24 @@ export const configureSocket = (socket: WebSocket, uri: string, terminal: any,
|
|||||||
};
|
};
|
||||||
|
|
||||||
socket.onclose = () => {
|
socket.onclose = () => {
|
||||||
terminal.writeln("Socket Closed");
|
|
||||||
if (keepAliveID) {
|
if (keepAliveID) {
|
||||||
clearTimeout(keepAliveID);
|
clearTimeout(keepAliveID);
|
||||||
pingCount = 0;
|
pingCount = 0;
|
||||||
}
|
}
|
||||||
intervals.forEach((val) => {
|
|
||||||
window.clearInterval(+val);
|
|
||||||
});
|
|
||||||
|
|
||||||
terminal.writeln("Session terminated. Please refresh the page to start a new session.");
|
terminal.writeln("Session terminated. Please refresh the page to start a new session.");
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.onerror = () => {
|
socket.onerror = () => {
|
||||||
terminal.writeln("terminal reconnected");
|
|
||||||
if (socketRetryCount < 10 && socket.readyState !== WebSocket.CLOSED) {
|
if (socketRetryCount < 10 && socket.readyState !== WebSocket.CLOSED) {
|
||||||
configureSocket(socket, uri, terminal, intervals, socketRetryCount + 1);
|
configureSocket(socket, uri, terminal, initCommands, socketRetryCount + 1);
|
||||||
} else {
|
} else {
|
||||||
// log an error indicating socket connection failed
|
|
||||||
terminal.writeln("Socket connection closed");
|
|
||||||
// close the socket
|
// close the socket
|
||||||
socket.close();
|
socket.close();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.onmessage = (event: MessageEvent<string>) => {
|
socket.onmessage = (event: MessageEvent<string>) => {
|
||||||
terminal.writeln("Socket onMessage");
|
|
||||||
// if we are sending and receiving messages the terminal is not idle set ping count to 0
|
// if we are sending and receiving messages the terminal is not idle set ping count to 0
|
||||||
pingCount = 0;
|
pingCount = 0;
|
||||||
|
|
||||||
@ -210,8 +167,6 @@ export const configureSocket = (socket: WebSocket, uri: string, terminal: any,
|
|||||||
}
|
}
|
||||||
if (typeof event.data === 'string') {
|
if (typeof event.data === 'string') {
|
||||||
eventData = event.data;
|
eventData = event.data;
|
||||||
|
|
||||||
terminal.write(eventData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// process as one line or process as multiline
|
// process as one line or process as multiline
|
||||||
|
@ -1,15 +1,20 @@
|
|||||||
import React, { useEffect, useRef } from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
import { Terminal } from "xterm";
|
import { Terminal } from "xterm";
|
||||||
import "xterm/css/xterm.css";
|
import "xterm/css/xterm.css";
|
||||||
|
import { TerminalKind } from "../../Contracts/ViewModels";
|
||||||
import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
||||||
import { startCloudShellterminal } from "./CloudShellTab/UseTerminal";
|
import { startCloudShellterminal } from "./CloudShellTab/UseTerminal";
|
||||||
|
|
||||||
|
export interface CloudShellTerminalProps {
|
||||||
|
shellType: TerminalKind;
|
||||||
|
}
|
||||||
|
|
||||||
export const CloudShellTerminalComponent: React.FC = () => {
|
export const CloudShellTerminalComponent: React.FC<CloudShellTerminalProps> = ({
|
||||||
|
shellType
|
||||||
|
}: CloudShellTerminalProps) => {
|
||||||
const terminalRef = useRef(null); // Reference for terminal container
|
const terminalRef = useRef(null); // Reference for terminal container
|
||||||
const xtermRef = useRef(null); // Reference for XTerm instance
|
const xtermRef = useRef(null); // Reference for XTerm instance
|
||||||
const socketRef = useRef(null); // Reference for WebSocket
|
const socketRef = useRef(null); // Reference for WebSocket
|
||||||
const intervalsToClearRef = useRef<NodeJS.Timer[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Initialize XTerm instance
|
// Initialize XTerm instance
|
||||||
@ -26,7 +31,7 @@ export const CloudShellTerminalComponent: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const authorizationHeader = getAuthorizationHeader()
|
const authorizationHeader = getAuthorizationHeader()
|
||||||
socketRef.current = startCloudShellterminal(term, intervalsToClearRef, authorizationHeader.token);
|
socketRef.current = startCloudShellterminal(term, getCommands(shellType), authorizationHeader.token);
|
||||||
|
|
||||||
term.onData((data) => {
|
term.onData((data) => {
|
||||||
if (socketRef.current && socketRef.current.readyState === WebSocket.OPEN) {
|
if (socketRef.current && socketRef.current.readyState === WebSocket.OPEN) {
|
||||||
@ -46,3 +51,36 @@ export const CloudShellTerminalComponent: React.FC = () => {
|
|||||||
|
|
||||||
return <div ref={terminalRef} style={{ width: "100%", height: "500px" }} />;
|
return <div ref={terminalRef} style={{ width: "100%", height: "500px" }} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getCommands = (terminalKind: TerminalKind): string => {
|
||||||
|
switch (terminalKind) {
|
||||||
|
case TerminalKind.Postgres:
|
||||||
|
return `curl -s https://ipinfo.io \n` +
|
||||||
|
`curl -LO https://ftp.postgresql.org/pub/source/v15.2/postgresql-15.2.tar.bz2 \n` +
|
||||||
|
`tar -xvjf postgresql-15.2.tar.bz2 \n` +
|
||||||
|
`cd postgresql-15.2 \n` +
|
||||||
|
`mkdir ~/pgsql \n` +
|
||||||
|
`curl -LO https://ftp.gnu.org/gnu/readline/readline-8.1.tar.gz \n` +
|
||||||
|
`tar -xvzf readline-8.1.tar.gz \n` +
|
||||||
|
`cd readline-8.1 \n` +
|
||||||
|
`./configure --prefix=$HOME/pgsql \n`;
|
||||||
|
case TerminalKind.Mongo || terminalKind === TerminalKind.VCoreMongo:
|
||||||
|
return `curl -s https://ipinfo.io \n` +
|
||||||
|
`curl -LO https://downloads.mongodb.com/compass/mongosh-2.3.8-linux-x64.tgz \n` +
|
||||||
|
`tar -xvzf mongosh-2.3.8-linux-x64.tgz \n` +
|
||||||
|
`mkdir -p ~/mongosh && mv mongosh-2.3.8-linux-x64/* ~/mongosh/ \n` +
|
||||||
|
`echo 'export PATH=$PATH:$HOME/mongosh/bin' >> ~/.bashrc \n` +
|
||||||
|
`source ~/.bashrc \n` +
|
||||||
|
`mongosh --version \n`;
|
||||||
|
case TerminalKind.Cassandra:
|
||||||
|
return `curl -s https://ipinfo.io \n` +
|
||||||
|
`curl -OL http://apache.mirror.digitalpacific.com.au/cassandra/4.0.0/apache-cassandra-4.0.0-bin.tar.gz \n` +
|
||||||
|
`tar -xvzf apache-cassandra-4.0.0-bin.tar.gz \n` +
|
||||||
|
`cd apache-cassandra-4.0.0 \n` +
|
||||||
|
`mkdir ~/cassandra \n` +
|
||||||
|
`echo 'export CASSANDRA_HOME=$HOME/cassandra' >> ~/.bashrc \n` +
|
||||||
|
`source ~/.bashrc \n`;
|
||||||
|
default:
|
||||||
|
throw new Error("Unsupported terminal kind");
|
||||||
|
}
|
||||||
|
}
|
@ -49,17 +49,13 @@ class NotebookTerminalComponentAdapter implements ReactAdapter {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return this.parameters() ?
|
return this.parameters() ? (
|
||||||
( userContext.features.enableCloudShell ? (
|
|
||||||
<CloudShellTerminalComponent />
|
|
||||||
) : (
|
|
||||||
<NotebookTerminalComponent
|
<NotebookTerminalComponent
|
||||||
notebookServerInfo={this.getNotebookServerInfo()}
|
notebookServerInfo={this.getNotebookServerInfo()}
|
||||||
databaseAccount={this.getDatabaseAccount()}
|
databaseAccount={this.getDatabaseAccount()}
|
||||||
tabId={this.getTabId()}
|
tabId={this.getTabId()}
|
||||||
username={this.getUsername()}
|
username={this.getUsername()}
|
||||||
/>
|
/>): (
|
||||||
) ): (
|
|
||||||
<Spinner styles={{ root: { marginTop: 10 } }} size={SpinnerSize.large}></Spinner>
|
<Spinner styles={{ root: { marginTop: 10 } }} size={SpinnerSize.large}></Spinner>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -72,9 +68,6 @@ class CloudShellTerminalComponentAdapter implements ReactAdapter {
|
|||||||
// parameters: true: show, false: hide
|
// parameters: true: show, false: hide
|
||||||
public parameters: ko.Computed<boolean>;
|
public parameters: ko.Computed<boolean>;
|
||||||
constructor(
|
constructor(
|
||||||
private getDatabaseAccount: () => DataModels.DatabaseAccount,
|
|
||||||
private getTabId: () => string,
|
|
||||||
private getUsername: () => string,
|
|
||||||
private isAllPublicIPAddressesEnabled: ko.Observable<boolean>,
|
private isAllPublicIPAddressesEnabled: ko.Observable<boolean>,
|
||||||
private kind: ViewModels.TerminalKind,
|
private kind: ViewModels.TerminalKind,
|
||||||
) {}
|
) {}
|
||||||
@ -90,7 +83,8 @@ class CloudShellTerminalComponentAdapter implements ReactAdapter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return this.parameters() ? (
|
return this.parameters() ? (
|
||||||
<CloudShellTerminalComponent />
|
<CloudShellTerminalComponent
|
||||||
|
shellType={this.kind}/>
|
||||||
) : (
|
) : (
|
||||||
<Spinner styles={{ root: { marginTop: 10 } }} size={SpinnerSize.large}></Spinner>
|
<Spinner styles={{ root: { marginTop: 10 } }} size={SpinnerSize.large}></Spinner>
|
||||||
);
|
);
|
||||||
@ -144,9 +138,6 @@ export default class TerminalTab extends TabsBase {
|
|||||||
private initializeNotebookTerminalAdapter(options: TerminalTabOptions): void {
|
private initializeNotebookTerminalAdapter(options: TerminalTabOptions): void {
|
||||||
if (userContext.features.enableCloudShell) {
|
if (userContext.features.enableCloudShell) {
|
||||||
this.terminalComponentAdapter = new CloudShellTerminalComponentAdapter(
|
this.terminalComponentAdapter = new CloudShellTerminalComponentAdapter(
|
||||||
() => userContext?.databaseAccount,
|
|
||||||
() => this.tabId,
|
|
||||||
() => this.getUsername(),
|
|
||||||
this.isAllPublicIPAddressesEnabled,
|
this.isAllPublicIPAddressesEnabled,
|
||||||
options.kind
|
options.kind
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user