mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-03 16:22:20 +00:00
Compare commits
5 Commits
users/lang
...
users/nish
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
919a344d1e | ||
|
|
2fa95a281e | ||
|
|
ea6f3d1579 | ||
|
|
f9b0abdd14 | ||
|
|
10cda21401 |
@@ -211,3 +211,12 @@ a:focus {
|
||||
.fileImportImg img {
|
||||
filter: brightness(0) saturate(100%);
|
||||
}
|
||||
|
||||
.tabPanesContainer {
|
||||
overflow: auto !important;
|
||||
}
|
||||
|
||||
.tabs-container {
|
||||
min-height: 500px;
|
||||
min-width: 500px;
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ export interface IndexingPolicy {
|
||||
export interface VectorIndex {
|
||||
path: string;
|
||||
type: "flat" | "diskANN" | "quantizedFlat";
|
||||
diskANNShardKey?: string;
|
||||
vectorIndexShardKey?: string[];
|
||||
indexingSearchListSize?: number;
|
||||
quantizationByteSize?: number;
|
||||
}
|
||||
|
||||
@@ -1215,6 +1215,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
isFullTextSearchEnabled: this.isFullTextSearchEnabled,
|
||||
shouldDiscardContainerPolicies: this.state.shouldDiscardContainerPolicies,
|
||||
resetShouldDiscardContainerPolicyChange: this.resetShouldDiscardContainerPolicies,
|
||||
isGlobalSecondaryIndex: this.isGlobalSecondaryIndex,
|
||||
};
|
||||
|
||||
const indexingPolicyComponentProps: IndexingPolicyComponentProps = {
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface ContainerPolicyComponentProps {
|
||||
isFullTextSearchEnabled: boolean;
|
||||
shouldDiscardContainerPolicies: boolean;
|
||||
resetShouldDiscardContainerPolicyChange: () => void;
|
||||
isGlobalSecondaryIndex?: boolean;
|
||||
}
|
||||
|
||||
export const ContainerPolicyComponent: React.FC<ContainerPolicyComponentProps> = ({
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
Stack,
|
||||
TextField,
|
||||
} from "@fluentui/react";
|
||||
import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
|
||||
import { VectorEmbedding, VectorIndex } from "Contracts/DataModels";
|
||||
import { CollapsibleSectionComponent } from "Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
||||
import {
|
||||
@@ -29,6 +30,7 @@ export interface IVectorEmbeddingPoliciesComponentProps {
|
||||
discardChanges?: boolean;
|
||||
onChangesDiscarded?: () => void;
|
||||
disabled?: boolean;
|
||||
isGlobalSecondaryIndex?: boolean;
|
||||
}
|
||||
|
||||
export interface VectorEmbeddingPolicyData {
|
||||
@@ -39,8 +41,7 @@ export interface VectorEmbeddingPolicyData {
|
||||
indexType: VectorIndex["type"] | "none";
|
||||
pathError: string;
|
||||
dimensionsError: string;
|
||||
diskANNShardKey?: string;
|
||||
diskANNShardKeyError?: string;
|
||||
vectorIndexShardKey?: string[];
|
||||
indexingSearchListSize?: number;
|
||||
indexingSearchListSizeError?: string;
|
||||
quantizationByteSize?: number;
|
||||
@@ -87,6 +88,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
discardChanges,
|
||||
onChangesDiscarded,
|
||||
disabled,
|
||||
isGlobalSecondaryIndex,
|
||||
}): JSX.Element => {
|
||||
const onVectorEmbeddingPathError = (path: string, index?: number): string => {
|
||||
let error = "";
|
||||
@@ -132,12 +134,6 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
return error;
|
||||
};
|
||||
|
||||
//TODO: no restrictions yet due to this field being removed for now.
|
||||
// Uncomment and replace with validation code when field is reinstated
|
||||
// const onDiskANNShardKeyError = (shardKey: string): string => {
|
||||
// return "";
|
||||
// };
|
||||
|
||||
const initializeData = (vectorEmbeddings: VectorEmbedding[], vectorIndexes: VectorIndex[]) => {
|
||||
const mergedData: VectorEmbeddingPolicyData[] = [];
|
||||
vectorEmbeddings.forEach((embedding) => {
|
||||
@@ -147,6 +143,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
indexType: matchingIndex?.type || "none",
|
||||
indexingSearchListSize: matchingIndex?.indexingSearchListSize || undefined,
|
||||
quantizationByteSize: matchingIndex?.quantizationByteSize || undefined,
|
||||
vectorIndexShardKey: matchingIndex?.vectorIndexShardKey || undefined,
|
||||
pathError: onVectorEmbeddingPathError(embedding.path),
|
||||
dimensionsError: onVectorEmbeddingDimensionError(embedding.dimensions, matchingIndex?.type || "none"),
|
||||
});
|
||||
@@ -186,6 +183,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
type: policy.indexType,
|
||||
indexingSearchListSize: policy.indexingSearchListSize,
|
||||
quantizationByteSize: policy.quantizationByteSize,
|
||||
vectorIndexShardKey: policy.vectorIndexShardKey,
|
||||
}) as VectorIndex,
|
||||
);
|
||||
const validationPassed = vectorEmbeddingPolicyData.every(
|
||||
@@ -247,20 +245,16 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
setVectorEmbeddingPolicyData(vectorEmbeddings);
|
||||
};
|
||||
|
||||
// TODO: uncomment after Ignite
|
||||
// DiskANNShardKey was removed for Ignite due to backend problems. Leaving this here as it will be reinstated immediately after Ignite
|
||||
// const onDiskANNShardKeyChange = (index: number, event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
// const value = event.target.value.trim();
|
||||
// const vectorEmbeddings = [...vectorEmbeddingPolicyData];
|
||||
// if (!vectorEmbeddings[index]?.diskANNShardKey && !value.startsWith("/")) {
|
||||
// vectorEmbeddings[index].diskANNShardKey = "/" + value;
|
||||
// } else {
|
||||
// vectorEmbeddings[index].diskANNShardKey = value;
|
||||
// }
|
||||
// const error = onDiskANNShardKeyError(value);
|
||||
// vectorEmbeddings[index].diskANNShardKeyError = error;
|
||||
// setVectorEmbeddingPolicyData(vectorEmbeddings);
|
||||
// }
|
||||
const onShardKeyChange = (index: number, event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = event.target.value.trim();
|
||||
const vectorEmbeddings = [...vectorEmbeddingPolicyData];
|
||||
if (!vectorEmbeddings[index]?.vectorIndexShardKey?.[0] && !value.startsWith("/")) {
|
||||
vectorEmbeddings[index].vectorIndexShardKey = ["/" + value];
|
||||
} else {
|
||||
vectorEmbeddings[index].vectorIndexShardKey = [value];
|
||||
}
|
||||
setVectorEmbeddingPolicyData(vectorEmbeddings);
|
||||
};
|
||||
|
||||
const onVectorEmbeddingPolicyChange = (
|
||||
index: number,
|
||||
@@ -292,6 +286,11 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
setVectorEmbeddingPolicyData(vectorEmbeddings);
|
||||
};
|
||||
|
||||
const getQuantizationByteSizeTooltipContent = (): string => {
|
||||
const containerName: string = isGlobalSecondaryIndex ? "global secondary index" : "container";
|
||||
return `This is dynamically set by the ${containerName} if left blank, or it can be set to a fixed number`;
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack tokens={{ childrenGap: 4 }}>
|
||||
{vectorEmbeddingPolicyData &&
|
||||
@@ -402,6 +401,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
styles={labelStyles}
|
||||
>
|
||||
Quantization byte size
|
||||
<InfoTooltip>{getQuantizationByteSizeTooltipContent()}</InfoTooltip>
|
||||
</Label>
|
||||
<TextField
|
||||
disabled={
|
||||
@@ -431,26 +431,18 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
{/*TODO: uncomment after Ignite */}
|
||||
{/* DiskANNShardKey was removed for Ignite due to backend problems. Leaving this here as it will be reinstated immediately after Ignite
|
||||
<Stack
|
||||
style={{ marginLeft: "10px" }}
|
||||
>
|
||||
<Label
|
||||
disabled={disabled || vectorEmbeddingPolicy.indexType !== "diskANN"}
|
||||
styles={labelStyles}
|
||||
>DiskANN shard key</Label>
|
||||
<Stack style={{ marginLeft: "10px" }}>
|
||||
<Label disabled={disabled || vectorEmbeddingPolicy.indexType !== "diskANN"} styles={labelStyles}>
|
||||
Vector index shard key
|
||||
</Label>
|
||||
<TextField
|
||||
disabled={disabled || vectorEmbeddingPolicy.indexType !== "diskANN"}
|
||||
id={`vector-policy-diskANNShardKey-${index + 1}`}
|
||||
id={`vector-policy-vectorIndexShardKey-${index + 1}`}
|
||||
styles={textFieldStyles}
|
||||
value={String(vectorEmbeddingPolicy.diskANNShardKey || "")}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
onDiskANNShardKeyChange(index, event)
|
||||
}
|
||||
value={String(vectorEmbeddingPolicy.vectorIndexShardKey?.[0] ?? "")}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onShardKeyChange(index, event)}
|
||||
/>
|
||||
</Stack>
|
||||
*/}
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -388,6 +388,7 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
|
||||
setVectorIndexingPolicy,
|
||||
vectorPolicyValidated,
|
||||
setVectorPolicyValidated,
|
||||
isGlobalSecondaryIndex: true,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface VectorSearchComponentProps {
|
||||
vectorIndexingPolicy: VectorIndex[];
|
||||
setVectorIndexingPolicy: React.Dispatch<React.SetStateAction<VectorIndex[]>>;
|
||||
setVectorPolicyValidated: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
isGlobalSecondaryIndex?: boolean;
|
||||
}
|
||||
|
||||
export const VectorSearchComponent = (props: VectorSearchComponentProps): JSX.Element => {
|
||||
@@ -23,6 +24,7 @@ export const VectorSearchComponent = (props: VectorSearchComponentProps): JSX.El
|
||||
vectorIndexingPolicy,
|
||||
setVectorIndexingPolicy,
|
||||
setVectorPolicyValidated,
|
||||
isGlobalSecondaryIndex,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
@@ -49,6 +51,7 @@ export const VectorSearchComponent = (props: VectorSearchComponentProps): JSX.El
|
||||
setVectorIndexingPolicy(vectorIndexingPolicy);
|
||||
setVectorPolicyValidated(vectorPolicyValidated);
|
||||
}}
|
||||
isGlobalSecondaryIndex={isGlobalSecondaryIndex}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -202,6 +202,8 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
const showRetrySettings =
|
||||
(userContext.apiType === "SQL" || userContext.apiType === "Tables" || userContext.apiType === "Gremlin") &&
|
||||
!isEmulator;
|
||||
const showRegionSelection = userContext.apiType === "SQL" && userContext.authType === AuthType.AAD && !isFabric();
|
||||
const showQueryTimeout = userContext.apiType === "SQL" && !isEmulator;
|
||||
const shouldShowGraphAutoVizOption = userContext.apiType === "Gremlin" && !isEmulator;
|
||||
const shouldShowCrossPartitionOption = userContext.apiType !== "Gremlin" && !isEmulator;
|
||||
const shouldShowParallelismOption = userContext.apiType !== "Gremlin" && !isEmulator;
|
||||
@@ -406,28 +408,62 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
}
|
||||
|
||||
setIsExecuting(false);
|
||||
logConsoleInfo(
|
||||
`Updated items per page setting to ${LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage)}`,
|
||||
);
|
||||
logConsoleInfo(`${crossPartitionQueryEnabled ? "Enabled" : "Disabled"} cross-partition query feed option`);
|
||||
logConsoleInfo(
|
||||
`Updated the max degree of parallelism query feed option to ${LocalStorageUtility.getEntryNumber(
|
||||
StorageKey.MaxDegreeOfParellism,
|
||||
)}`,
|
||||
);
|
||||
logConsoleInfo(`Updated priority level setting to ${LocalStorageUtility.getEntryString(StorageKey.PriorityLevel)}`);
|
||||
setTimeout(() => {
|
||||
shouldShowQueryPageOptions &&
|
||||
logConsoleInfo(
|
||||
`Updated items per page setting to ${LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage)}`,
|
||||
);
|
||||
|
||||
if (shouldShowGraphAutoVizOption) {
|
||||
logConsoleInfo(
|
||||
`Graph result will be displayed as ${
|
||||
LocalStorageUtility.getEntryBoolean(StorageKey.IsGraphAutoVizDisabled) ? "JSON" : "Graph"
|
||||
}`,
|
||||
);
|
||||
}
|
||||
showEnableEntraIdRbac &&
|
||||
logConsoleInfo(
|
||||
`Updated Enable Entra ID RBAC to ${LocalStorageUtility.hasItem(StorageKey.DataPlaneRbacEnabled)}`,
|
||||
);
|
||||
|
||||
showRegionSelection && logConsoleInfo(`Updated region ${AppStateComponentNames.SelectedRegionalEndpoint}`);
|
||||
|
||||
showQueryTimeout &&
|
||||
(() => {
|
||||
logConsoleInfo(`${queryTimeout ? "Enabled" : "Disabled"} query timeout`);
|
||||
logConsoleInfo(
|
||||
ruThresholdEnabled
|
||||
? `RU Limit is ${LocalStorageUtility.getEntryNumber(StorageKey.RUThreshold)} units`
|
||||
: "RU limit disabled",
|
||||
);
|
||||
logConsoleInfo(
|
||||
`Updated default query result view ${LocalStorageUtility.getEntryString(
|
||||
StorageKey.DefaultQueryResultsView,
|
||||
)}`,
|
||||
);
|
||||
})();
|
||||
|
||||
showRetrySettings &&
|
||||
logConsoleInfo(`Updated retry attempts ${LocalStorageUtility.getEntryNumber(StorageKey.RetryAttempts)}`);
|
||||
|
||||
!isEmulator && logConsoleInfo(`${containerPaginationEnabled ? "Enabled" : "Disabled"} container pagination`);
|
||||
|
||||
shouldShowCrossPartitionOption &&
|
||||
logConsoleInfo(`${crossPartitionQueryEnabled ? "Enabled" : "Disabled"} cross-partition query feed option`);
|
||||
|
||||
shouldShowParallelismOption &&
|
||||
logConsoleInfo(
|
||||
`Updated the max degree of parallelism query feed option to ${LocalStorageUtility.getEntryNumber(
|
||||
StorageKey.MaxDegreeOfParellism,
|
||||
)}`,
|
||||
);
|
||||
|
||||
shouldShowPriorityLevelOption &&
|
||||
logConsoleInfo(
|
||||
`Updated priority level setting to ${LocalStorageUtility.getEntryString(StorageKey.PriorityLevel)}`,
|
||||
);
|
||||
|
||||
shouldShowGraphAutoVizOption &&
|
||||
logConsoleInfo(
|
||||
`Graph result will be displayed as ${
|
||||
LocalStorageUtility.getEntryBoolean(StorageKey.IsGraphAutoVizDisabled) ? "JSON" : "Graph"
|
||||
}`,
|
||||
);
|
||||
}, 0);
|
||||
|
||||
logConsoleInfo(
|
||||
`Updated query setting to ${LocalStorageUtility.getEntryString(StorageKey.SetPartitionKeyUndefined)}`,
|
||||
);
|
||||
refreshExplorer && (await explorer.refreshExplorer());
|
||||
closeSidePanel();
|
||||
};
|
||||
@@ -688,7 +724,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
)}
|
||||
{userContext.apiType === "SQL" && userContext.authType === AuthType.AAD && !isFabric() && (
|
||||
{showRegionSelection && (
|
||||
<AccordionItem value="3">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Region Selection</div>
|
||||
@@ -718,7 +754,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
)}
|
||||
{userContext.apiType === "SQL" && !isEmulator && (
|
||||
{showQueryTimeout && (
|
||||
<>
|
||||
<AccordionItem value="4">
|
||||
<AccordionHeader>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Accordion top class
|
||||
*/
|
||||
import { Link, makeStyles, tokens } from "@fluentui/react-components";
|
||||
import { makeStyles, tokens } from "@fluentui/react-components";
|
||||
import { DocumentAddRegular, LinkMultipleRegular } from "@fluentui/react-icons";
|
||||
import { SampleDataImportDialog } from "Explorer/SplashScreen/SampleDataImportDialog";
|
||||
import { CosmosFluentProvider } from "Explorer/Theme/ThemeUtil";
|
||||
@@ -9,7 +9,6 @@ import { isFabricNative, isFabricNativeReadOnly } from "Platform/Fabric/FabricUt
|
||||
import * as React from "react";
|
||||
import { userContext } from "UserContext";
|
||||
import CosmosDbBlackIcon from "../../../images/CosmosDB_black.svg";
|
||||
import LinkIcon from "../../../images/Link_blue.svg";
|
||||
import Explorer from "../Explorer";
|
||||
|
||||
export interface SplashScreenProps {
|
||||
@@ -186,12 +185,12 @@ export const FabricHomeScreen: React.FC<SplashScreenProps> = (props: SplashScree
|
||||
{title}
|
||||
</div>
|
||||
{getSplashScreenButtons()}
|
||||
<div className={styles.footer}>
|
||||
{/* <div className={styles.footer}>
|
||||
Need help?{" "}
|
||||
<Link href="https://aka.ms/cosmosdbfabricdocs" target="_blank">
|
||||
Learn more <img src={LinkIcon} alt="Learn more" />
|
||||
</Link>
|
||||
</div>
|
||||
</div> */}
|
||||
</CosmosFluentProvider>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -55,20 +55,16 @@ export const startCloudShellTerminal = async (terminal: Terminal, shellType: Ter
|
||||
TelemetryProcessor.trace(
|
||||
Action.CloudShellUserConsent,
|
||||
consentGranted ? ActionModifiers.Success : ActionModifiers.Cancel,
|
||||
{ dataExplorerArea: Areas.CloudShell },
|
||||
{
|
||||
dataExplorerArea: Areas.CloudShell,
|
||||
shellType: TerminalKind[shellType],
|
||||
isConsent: consentGranted,
|
||||
region: resolvedRegion,
|
||||
},
|
||||
startKey,
|
||||
);
|
||||
|
||||
if (!consentGranted) {
|
||||
TelemetryProcessor.traceCancel(
|
||||
Action.CloudShellTerminalSession,
|
||||
{
|
||||
shellType: TerminalKind[shellType],
|
||||
dataExplorerArea: Areas.CloudShell,
|
||||
region: resolvedRegion,
|
||||
isConsent: false,
|
||||
},
|
||||
startKey,
|
||||
);
|
||||
terminal.writeln(
|
||||
formatErrorMessage("Session ended. Please close this tab and initiate a new shell session if needed."),
|
||||
);
|
||||
@@ -262,28 +258,27 @@ export const configureSocketConnection = async (
|
||||
};
|
||||
|
||||
export const sendTerminalStartupCommands = (socket: WebSocket, initCommands: string): void => {
|
||||
// ensures connections don't remain open indefinitely by implementing an automatic timeout after 120 minutes.
|
||||
const keepSocketAlive = (socket: WebSocket) => {
|
||||
if (socket.readyState === WebSocket.OPEN) {
|
||||
if (pingCount >= MAX_PING_COUNT) {
|
||||
socket.close();
|
||||
} else {
|
||||
pingCount++;
|
||||
// The code uses a recursive setTimeout pattern rather than setInterval,
|
||||
// which ensures each new ping only happens after the previous one completes
|
||||
// and naturally stops if the socket closes.
|
||||
keepAliveID = setTimeout(() => keepSocketAlive(socket), 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||
socket.send(initCommands);
|
||||
keepSocketAlive(socket);
|
||||
} else {
|
||||
socket.onopen = () => {
|
||||
socket.send(initCommands);
|
||||
|
||||
// ensures connections don't remain open indefinitely by implementing an automatic timeout after 20 minutes.
|
||||
const keepSocketAlive = (socket: WebSocket) => {
|
||||
if (socket.readyState === WebSocket.OPEN) {
|
||||
if (pingCount >= MAX_PING_COUNT) {
|
||||
socket.close();
|
||||
} else {
|
||||
socket.send("");
|
||||
pingCount++;
|
||||
// The code uses a recursive setTimeout pattern rather than setInterval,
|
||||
// which ensures each new ping only happens after the previous one completes
|
||||
// and naturally stops if the socket closes.
|
||||
keepAliveID = setTimeout(() => keepSocketAlive(socket), 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
keepSocketAlive(socket);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -56,4 +56,30 @@ export abstract class AbstractShellHandler {
|
||||
|
||||
return allCommands.join("\n").concat("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup commands for MongoDB shell:
|
||||
*
|
||||
* 1. Check if mongosh is already installed
|
||||
* 2. Download mongosh package if not installed
|
||||
* 3. Extract the package to access mongosh binaries
|
||||
* 4. Move extracted files to ~/mongosh directory
|
||||
* 5. Add mongosh binary path to system PATH
|
||||
* 6. Apply PATH changes by sourcing .bashrc
|
||||
*
|
||||
* Each command runs conditionally only if mongosh
|
||||
* is not already present in the environment.
|
||||
*/
|
||||
protected mongoShellSetupCommands(): string[] {
|
||||
const PACKAGE_VERSION: string = "2.5.0";
|
||||
return [
|
||||
"if ! command -v mongosh &> /dev/null; then echo '⚠️ mongosh not found. Installing...'; fi",
|
||||
`if ! command -v mongosh &> /dev/null; then curl -LO https://downloads.mongodb.com/compass/mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
|
||||
`if ! command -v mongosh &> /dev/null; then tar -xvzf mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
|
||||
`if ! command -v mongosh &> /dev/null; then mkdir -p ~/mongosh/bin && mv mongosh-${PACKAGE_VERSION}-linux-x64/bin/mongosh ~/mongosh/bin/ && chmod +x ~/mongosh/bin/mongosh; fi`,
|
||||
`if ! command -v mongosh &> /dev/null; then rm -rf mongosh-${PACKAGE_VERSION}-linux-x64 mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
|
||||
"if ! command -v mongosh &> /dev/null; then echo 'export PATH=$HOME/mongosh/bin:$PATH' >> ~/.bashrc; fi",
|
||||
"source ~/.bashrc",
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ describe("MongoShellHandler", () => {
|
||||
const commands = mongoShellHandler.getSetUpCommands();
|
||||
|
||||
expect(Array.isArray(commands)).toBe(true);
|
||||
expect(commands.length).toBe(6);
|
||||
expect(commands.length).toBe(7);
|
||||
expect(commands[1]).toContain("mongosh-2.5.0-linux-x64.tgz");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,8 +2,6 @@ import { userContext } from "../../../../UserContext";
|
||||
import { getHostFromUrl } from "../Utils/CommonUtils";
|
||||
import { AbstractShellHandler } from "./AbstractShellHandler";
|
||||
|
||||
const PACKAGE_VERSION: string = "2.5.0";
|
||||
|
||||
export class MongoShellHandler extends AbstractShellHandler {
|
||||
private _key: string;
|
||||
private _endpoint: string | undefined;
|
||||
@@ -18,14 +16,7 @@ export class MongoShellHandler extends AbstractShellHandler {
|
||||
}
|
||||
|
||||
public getSetUpCommands(): string[] {
|
||||
return [
|
||||
"if ! command -v mongosh &> /dev/null; then echo '⚠️ mongosh not found. Installing...'; fi",
|
||||
`if ! command -v mongosh &> /dev/null; then curl -LO https://downloads.mongodb.com/compass/mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
|
||||
`if ! command -v mongosh &> /dev/null; then tar -xvzf mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
|
||||
`if ! command -v mongosh &> /dev/null; then mkdir -p ~/mongosh && mv mongosh-${PACKAGE_VERSION}-linux-x64/* ~/mongosh/; fi`,
|
||||
"if ! command -v mongosh &> /dev/null; then echo 'export PATH=$HOME/mongosh/bin:$PATH' >> ~/.bashrc; fi",
|
||||
"source ~/.bashrc",
|
||||
];
|
||||
return this.mongoShellSetupCommands();
|
||||
}
|
||||
|
||||
public getConnectionCommand(): string {
|
||||
|
||||
@@ -44,7 +44,7 @@ describe("VCoreMongoShellHandler", () => {
|
||||
const commands = vcoreMongoShellHandler.getSetUpCommands();
|
||||
|
||||
expect(Array.isArray(commands)).toBe(true);
|
||||
expect(commands.length).toBe(6);
|
||||
expect(commands.length).toBe(7);
|
||||
expect(commands[1]).toContain("mongosh-2.5.0-linux-x64.tgz");
|
||||
expect(commands[0]).toContain("mongosh not found");
|
||||
});
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { userContext } from "../../../../UserContext";
|
||||
import { AbstractShellHandler } from "./AbstractShellHandler";
|
||||
|
||||
const PACKAGE_VERSION: string = "2.5.0";
|
||||
|
||||
export class VCoreMongoShellHandler extends AbstractShellHandler {
|
||||
private _endpoint: string | undefined;
|
||||
|
||||
@@ -15,28 +13,8 @@ export class VCoreMongoShellHandler extends AbstractShellHandler {
|
||||
return "MongoDB VCore";
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup commands for MongoDB VCore shell:
|
||||
*
|
||||
* 1. Check if mongosh is already installed
|
||||
* 2. Download mongosh package if not installed
|
||||
* 3. Extract the package to access mongosh binaries
|
||||
* 4. Move extracted files to ~/mongosh directory
|
||||
* 5. Add mongosh binary path to system PATH
|
||||
* 6. Apply PATH changes by sourcing .bashrc
|
||||
*
|
||||
* Each command runs conditionally only if mongosh
|
||||
* is not already present in the environment.
|
||||
*/
|
||||
public getSetUpCommands(): string[] {
|
||||
return [
|
||||
"if ! command -v mongosh &> /dev/null; then echo '⚠️ mongosh not found. Installing...'; fi",
|
||||
`if ! command -v mongosh &> /dev/null; then curl -LO https://downloads.mongodb.com/compass/mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
|
||||
`if ! command -v mongosh &> /dev/null; then tar -xvzf mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
|
||||
`if ! command -v mongosh &> /dev/null; then mkdir -p ~/mongosh && mv mongosh-${PACKAGE_VERSION}-linux-x64/* ~/mongosh/; fi`,
|
||||
"if ! command -v mongosh &> /dev/null; then echo 'export PATH=$HOME/mongosh/bin:$PATH' >> ~/.bashrc; fi",
|
||||
"source ~/.bashrc",
|
||||
];
|
||||
return this.mongoShellSetupCommands();
|
||||
}
|
||||
|
||||
public getConnectionCommand(): string {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AbstractShellHandler } from "Explorer/Tabs/CloudShellTab/ShellTypes/AbstractShellHandler";
|
||||
import { IDisposable, ITerminalAddon, Terminal } from "@xterm/xterm";
|
||||
import { AbstractShellHandler } from "../ShellTypes/AbstractShellHandler";
|
||||
import { formatErrorMessage } from "./TerminalLogFormats";
|
||||
|
||||
interface IAttachOptions {
|
||||
bidirectional?: boolean;
|
||||
@@ -56,8 +57,27 @@ export class AttachAddon implements ITerminalAddon {
|
||||
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()));
|
||||
this._disposables.push(addSocketListener(this._socket, "close", () => this._handleSocketClose(terminal)));
|
||||
this._disposables.push(addSocketListener(this._socket, "error", () => this._handleSocketClose(terminal)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles socket close events by terminating processes and showing a message
|
||||
*/
|
||||
private _handleSocketClose(terminal: Terminal): void {
|
||||
if (terminal) {
|
||||
terminal.writeln(
|
||||
formatErrorMessage("Session ended. Please close this tab and initiate a new shell session if needed."),
|
||||
);
|
||||
|
||||
// Send exit command to terminal
|
||||
if (this._bidirectional) {
|
||||
terminal.write(formatErrorMessage("exit\r\n"));
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up resources
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user