mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-23 19:01:28 +00:00
Compare commits
7 Commits
cloudshell
...
documentdb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98d7e89712 | ||
|
|
d45e10f5ac | ||
|
|
cfb5db4df6 | ||
|
|
922ca5c523 | ||
|
|
bafe002fa3 | ||
|
|
0817acf404 | ||
|
|
8e2c46301d |
@@ -765,3 +765,10 @@ export const ShortenedQueryCopilotSampleContainerSchema = {
|
|||||||
|
|
||||||
userPrompt: "find all products",
|
userPrompt: "find all products",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum MongoGuidRepresentation {
|
||||||
|
Standard = "Standard",
|
||||||
|
CSharpLegacy = "CSharpLegacy",
|
||||||
|
JavaLegacy = "JavaLegacy",
|
||||||
|
PythonLegacy = "PythonLegacy",
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
|
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
|
||||||
|
import { getMongoGuidRepresentation } from "Shared/StorageUtility";
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import { configContext } from "../ConfigContext";
|
import { configContext } from "../ConfigContext";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
@@ -139,6 +140,9 @@ export function readDocument(
|
|||||||
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
|
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
|
||||||
? documentId.partitionKeyProperties?.[0]
|
? documentId.partitionKeyProperties?.[0]
|
||||||
: "",
|
: "",
|
||||||
|
clientSettings: {
|
||||||
|
guidRepresentation: getMongoGuidRepresentation(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
||||||
@@ -181,6 +185,9 @@ export function createDocument(
|
|||||||
partitionKey:
|
partitionKey:
|
||||||
collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "",
|
collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "",
|
||||||
documentContent: JSON.stringify(documentContent),
|
documentContent: JSON.stringify(documentContent),
|
||||||
|
clientSettings: {
|
||||||
|
guidRepresentation: getMongoGuidRepresentation(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
||||||
@@ -228,6 +235,9 @@ export function updateDocument(
|
|||||||
? documentId.partitionKeyProperties?.[0]
|
? documentId.partitionKeyProperties?.[0]
|
||||||
: "",
|
: "",
|
||||||
documentContent,
|
documentContent,
|
||||||
|
clientSettings: {
|
||||||
|
guidRepresentation: getMongoGuidRepresentation(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
||||||
|
|
||||||
@@ -274,6 +284,9 @@ export function deleteDocuments(
|
|||||||
subscriptionID: userContext.subscriptionId,
|
subscriptionID: userContext.subscriptionId,
|
||||||
resourceGroup: userContext.resourceGroup,
|
resourceGroup: userContext.resourceGroup,
|
||||||
databaseAccountName: databaseAccount.name,
|
databaseAccountName: databaseAccount.name,
|
||||||
|
clientSettings: {
|
||||||
|
guidRepresentation: getMongoGuidRepresentation(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,12 @@ export interface DatabaseAccountExtendedProperties {
|
|||||||
publicNetworkAccess?: string;
|
publicNetworkAccess?: string;
|
||||||
enablePriorityBasedExecution?: boolean;
|
enablePriorityBasedExecution?: boolean;
|
||||||
vcoreMongoEndpoint?: string;
|
vcoreMongoEndpoint?: string;
|
||||||
|
apiProperties?: ApiProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiProperties {
|
||||||
|
/* Describes the version of the MongoDB account. */
|
||||||
|
serverVersion?: "3.2" | "3.6" | "4.0" | "4.2" | "5.0" | "6.0" | "7.0";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatabaseAccountResponseLocation {
|
export interface DatabaseAccountResponseLocation {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
|||||||
import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
|
import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
|
||||||
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
|
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
|
||||||
import { featureRegistered } from "Utils/FeatureRegistrationUtils";
|
import { featureRegistered } from "Utils/FeatureRegistrationUtils";
|
||||||
|
import { getVSCodeUrl } from "Utils/VSCodeExtensionUtils";
|
||||||
import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts";
|
import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
@@ -289,40 +290,8 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a VS Code DocumentDB connection URL using the current user's MongoDB connection parameters.
|
|
||||||
* Double-encodes the updated connection string for safe usage in VS Code URLs.
|
|
||||||
*
|
|
||||||
* The DocumentDB VS Code extension requires double encoding for connection strings.
|
|
||||||
* See: https://microsoft.github.io/vscode-documentdb/manual/how-to-construct-url.html#double-encoding
|
|
||||||
*
|
|
||||||
* @returns {string} The encoded VS Code DocumentDB connection URL.
|
|
||||||
*/
|
|
||||||
private getDocumentDbUrl() {
|
|
||||||
const { adminLogin: adminLoginuserName = "", connectionString = "" } = userContext.vcoreMongoConnectionParams;
|
|
||||||
const updatedConnectionString = connectionString.replace(/<(user|username)>:<password>/i, adminLoginuserName);
|
|
||||||
const encodedUpdatedConnectionString = encodeURIComponent(encodeURIComponent(updatedConnectionString));
|
|
||||||
const documentDbUrl = `vscode://ms-azuretools.vscode-documentdb?connectionString=${encodedUpdatedConnectionString}`;
|
|
||||||
return documentDbUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getCosmosDbUrl() {
|
|
||||||
const activeTab = useTabs.getState().activeTab;
|
|
||||||
const resourceId = encodeURIComponent(userContext.databaseAccount.id);
|
|
||||||
const database = encodeURIComponent(activeTab?.collection?.databaseId);
|
|
||||||
const container = encodeURIComponent(activeTab?.collection?.id());
|
|
||||||
const baseUrl = `vscode://ms-azuretools.vscode-cosmosdb?resourceId=${resourceId}`;
|
|
||||||
const vscodeUrl = activeTab ? `${baseUrl}&database=${database}&container=${container}` : baseUrl;
|
|
||||||
return vscodeUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getVSCodeUrl(): string {
|
|
||||||
const isvCore = (userContext.apiType || userContext.databaseAccount.kind) === "VCoreMongo";
|
|
||||||
return isvCore ? this.getDocumentDbUrl() : this.getCosmosDbUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
public openInVsCode(): void {
|
public openInVsCode(): void {
|
||||||
const vscodeUrl = this.getVSCodeUrl();
|
const vscodeUrl = getVSCodeUrl();
|
||||||
const openVSCodeDialogProps: DialogProps = {
|
const openVSCodeDialogProps: DialogProps = {
|
||||||
linkProps: {
|
linkProps: {
|
||||||
linkText: "Download Visual Studio Code",
|
linkText: "Download Visual Studio Code",
|
||||||
|
|||||||
@@ -199,6 +199,12 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
LocalStorageUtility.getEntryString(StorageKey.CopilotSampleDBEnabled) === "true",
|
LocalStorageUtility.getEntryString(StorageKey.CopilotSampleDBEnabled) === "true",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [mongoGuidRepresentation, setMongoGuidRepresentation] = useState<Constants.MongoGuidRepresentation>(
|
||||||
|
LocalStorageUtility.hasItem(StorageKey.MongoGuidRepresentation)
|
||||||
|
? (LocalStorageUtility.getEntryString(StorageKey.MongoGuidRepresentation) as Constants.MongoGuidRepresentation)
|
||||||
|
: Constants.MongoGuidRepresentation.CSharpLegacy,
|
||||||
|
);
|
||||||
|
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
|
|
||||||
const explorerVersion = configContext.gitSha;
|
const explorerVersion = configContext.gitSha;
|
||||||
@@ -261,6 +267,8 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
useDatabases.getState().sampleDataResourceTokenCollection &&
|
useDatabases.getState().sampleDataResourceTokenCollection &&
|
||||||
!isEmulator;
|
!isEmulator;
|
||||||
|
|
||||||
|
const shouldShowMongoGuidRepresentationOption = userContext.apiType === "Mongo";
|
||||||
|
|
||||||
const handlerOnSubmit = async () => {
|
const handlerOnSubmit = async () => {
|
||||||
setIsExecuting(true);
|
setIsExecuting(true);
|
||||||
|
|
||||||
@@ -412,6 +420,10 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shouldShowMongoGuidRepresentationOption) {
|
||||||
|
LocalStorageUtility.setEntryString(StorageKey.MongoGuidRepresentation, mongoGuidRepresentation);
|
||||||
|
}
|
||||||
|
|
||||||
setIsExecuting(false);
|
setIsExecuting(false);
|
||||||
logConsoleInfo(
|
logConsoleInfo(
|
||||||
`Updated items per page setting to ${LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage)}`,
|
`Updated items per page setting to ${LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage)}`,
|
||||||
@@ -433,6 +445,14 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shouldShowMongoGuidRepresentationOption) {
|
||||||
|
logConsoleInfo(
|
||||||
|
`Updated Mongo Guid Representation to ${LocalStorageUtility.getEntryString(
|
||||||
|
StorageKey.MongoGuidRepresentation,
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
refreshExplorer && (await explorer.refreshExplorer());
|
refreshExplorer && (await explorer.refreshExplorer());
|
||||||
closeSidePanel();
|
closeSidePanel();
|
||||||
};
|
};
|
||||||
@@ -477,6 +497,13 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
{ key: SplitterDirection.Horizontal, text: "Horizontal" },
|
{ key: SplitterDirection.Horizontal, text: "Horizontal" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const mongoGuidRepresentationDropdownOptions: IDropdownOption[] = [
|
||||||
|
{ key: Constants.MongoGuidRepresentation.CSharpLegacy, text: Constants.MongoGuidRepresentation.CSharpLegacy },
|
||||||
|
{ key: Constants.MongoGuidRepresentation.JavaLegacy, text: Constants.MongoGuidRepresentation.JavaLegacy },
|
||||||
|
{ key: Constants.MongoGuidRepresentation.PythonLegacy, text: Constants.MongoGuidRepresentation.PythonLegacy },
|
||||||
|
{ key: Constants.MongoGuidRepresentation.Standard, text: Constants.MongoGuidRepresentation.Standard },
|
||||||
|
];
|
||||||
|
|
||||||
const handleOnPriorityLevelOptionChange = (
|
const handleOnPriorityLevelOptionChange = (
|
||||||
ev: React.FormEvent<HTMLInputElement>,
|
ev: React.FormEvent<HTMLInputElement>,
|
||||||
option: IChoiceGroupOption,
|
option: IChoiceGroupOption,
|
||||||
@@ -559,6 +586,13 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
setRefreshExplorer(false);
|
setRefreshExplorer(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOnMongoGuidRepresentationOptionChange = (
|
||||||
|
ev: React.FormEvent<HTMLInputElement>,
|
||||||
|
option: IDropdownOption,
|
||||||
|
): void => {
|
||||||
|
setMongoGuidRepresentation(option.key as Constants.MongoGuidRepresentation);
|
||||||
|
};
|
||||||
|
|
||||||
const choiceButtonStyles = {
|
const choiceButtonStyles = {
|
||||||
root: {
|
root: {
|
||||||
clear: "both",
|
clear: "both",
|
||||||
@@ -1065,15 +1099,15 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
<div className={styles.settingsSectionContainer}>
|
<div className={styles.settingsSectionContainer}>
|
||||||
<div className={styles.settingsSectionDescription}>
|
<div className={styles.settingsSectionDescription}>
|
||||||
This is a sample database and collection with synthetic product data you can use to explore using
|
This is a sample database and collection with synthetic product data you can use to explore using
|
||||||
NoSQL queries and Query Advisor. This will appear as another database in the Data Explorer UI, and
|
NoSQL queries. This will appear as another database in the Data Explorer UI, and is created by,
|
||||||
is created by, and maintained by Microsoft at no cost to you.
|
and maintained by Microsoft at no cost to you.
|
||||||
</div>
|
</div>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
styles={{
|
styles={{
|
||||||
label: { padding: 0 },
|
label: { padding: 0 },
|
||||||
}}
|
}}
|
||||||
className="padding"
|
className="padding"
|
||||||
ariaLabel="Enable sample db for Query Advisor"
|
ariaLabel="Enable sample db for query exploration"
|
||||||
checked={copilotSampleDBEnabled}
|
checked={copilotSampleDBEnabled}
|
||||||
onChange={handleSampleDatabaseChange}
|
onChange={handleSampleDatabaseChange}
|
||||||
label="Enable sample database"
|
label="Enable sample database"
|
||||||
@@ -1082,6 +1116,27 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
</AccordionPanel>
|
</AccordionPanel>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
)}
|
)}
|
||||||
|
{shouldShowMongoGuidRepresentationOption && (
|
||||||
|
<AccordionItem value="14">
|
||||||
|
<AccordionHeader>
|
||||||
|
<div className={styles.header}>Guid Representation</div>
|
||||||
|
</AccordionHeader>
|
||||||
|
<AccordionPanel>
|
||||||
|
<div className={styles.settingsSectionContainer}>
|
||||||
|
<div className={styles.settingsSectionDescription}>
|
||||||
|
GuidRepresentation in MongoDB refers to how Globally Unique Identifiers (GUIDs) are serialized and
|
||||||
|
deserialized when stored in BSON documents. This will apply to all document operations.
|
||||||
|
</div>
|
||||||
|
<Dropdown
|
||||||
|
aria-labelledby="mongoGuidRepresentation"
|
||||||
|
selectedKey={mongoGuidRepresentation}
|
||||||
|
options={mongoGuidRepresentationDropdownOptions}
|
||||||
|
onChange={handleOnMongoGuidRepresentationOptionChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</AccordionPanel>
|
||||||
|
</AccordionItem>
|
||||||
|
)}
|
||||||
</Accordion>
|
</Accordion>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
import { Stack } from "@fluentui/react";
|
import { Stack } from "@fluentui/react";
|
||||||
import { QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants";
|
|
||||||
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
|
||||||
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
||||||
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
import { SaveQueryPane } from "Explorer/Panes/SaveQueryPane/SaveQueryPane";
|
import { SaveQueryPane } from "Explorer/Panes/SaveQueryPane/SaveQueryPane";
|
||||||
import { QueryCopilotPromptbar } from "Explorer/QueryCopilot/QueryCopilotPromptbar";
|
|
||||||
import { readCopilotToggleStatus, saveCopilotToggleStatus } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
import { readCopilotToggleStatus, saveCopilotToggleStatus } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||||
import { OnExecuteQueryClick } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
import { OnExecuteQueryClick } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||||
import { QueryCopilotProps } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
import { QueryCopilotProps } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
||||||
@@ -13,7 +11,6 @@ import { QueryCopilotResults } from "Explorer/QueryCopilot/Shared/QueryCopilotRe
|
|||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
|
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import { useSidePanel } from "hooks/useSidePanel";
|
import { useSidePanel } from "hooks/useSidePanel";
|
||||||
import { ReactTabKind, TabsState, useTabs } from "hooks/useTabs";
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import SplitterLayout from "react-splitter-layout";
|
import SplitterLayout from "react-splitter-layout";
|
||||||
import QueryCommandIcon from "../../../images/CopilotCommand.svg";
|
import QueryCommandIcon from "../../../images/CopilotCommand.svg";
|
||||||
@@ -26,7 +23,8 @@ export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: Query
|
|||||||
const [copilotActive, setCopilotActive] = useState<boolean>(() =>
|
const [copilotActive, setCopilotActive] = useState<boolean>(() =>
|
||||||
readCopilotToggleStatus(userContext.databaseAccount),
|
readCopilotToggleStatus(userContext.databaseAccount),
|
||||||
);
|
);
|
||||||
const [tabActive, setTabActive] = useState<boolean>(true);
|
//TODO: Uncomment this useState when query copilot is reinstated in DE
|
||||||
|
// const [tabActive, setTabActive] = useState<boolean>(true);
|
||||||
|
|
||||||
const getCommandbarButtons = (): CommandButtonComponentProps[] => {
|
const getCommandbarButtons = (): CommandButtonComponentProps[] => {
|
||||||
const executeQueryBtnLabel = selectedQuery ? "Execute Selection" : "Execute Query";
|
const executeQueryBtnLabel = selectedQuery ? "Execute Selection" : "Execute Query";
|
||||||
@@ -70,17 +68,18 @@ export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: Query
|
|||||||
useCommandBar.getState().setContextButtons(getCommandbarButtons());
|
useCommandBar.getState().setContextButtons(getCommandbarButtons());
|
||||||
}, [query, selectedQuery, copilotActive]);
|
}, [query, selectedQuery, copilotActive]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
//TODO: Uncomment this effect when query copilot is reinstated in DE
|
||||||
return () => {
|
// React.useEffect(() => {
|
||||||
useTabs.subscribe((state: TabsState) => {
|
// return () => {
|
||||||
if (state.activeReactTab === ReactTabKind.QueryCopilot) {
|
// useTabs.subscribe((state: TabsState) => {
|
||||||
setTabActive(true);
|
// if (state.activeReactTab === ReactTabKind.QueryCopilot) {
|
||||||
} else {
|
// setTabActive(true);
|
||||||
setTabActive(false);
|
// } else {
|
||||||
}
|
// setTabActive(false);
|
||||||
});
|
// }
|
||||||
};
|
// });
|
||||||
}, []);
|
// };
|
||||||
|
// }, []);
|
||||||
|
|
||||||
const toggleCopilot = (toggle: boolean) => {
|
const toggleCopilot = (toggle: boolean) => {
|
||||||
setCopilotActive(toggle);
|
setCopilotActive(toggle);
|
||||||
@@ -90,6 +89,7 @@ export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: Query
|
|||||||
return (
|
return (
|
||||||
<Stack className="tab-pane" style={{ width: "100%" }}>
|
<Stack className="tab-pane" style={{ width: "100%" }}>
|
||||||
<div style={isGeneratingQuery ? { height: "100%" } : { overflowY: "auto", height: "100%" }}>
|
<div style={isGeneratingQuery ? { height: "100%" } : { overflowY: "auto", height: "100%" }}>
|
||||||
|
{/*TODO: Uncomment this section when query copilot is reinstated in DE
|
||||||
{tabActive && copilotActive && (
|
{tabActive && copilotActive && (
|
||||||
<QueryCopilotPromptbar
|
<QueryCopilotPromptbar
|
||||||
explorer={explorer}
|
explorer={explorer}
|
||||||
@@ -97,7 +97,7 @@ export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: Query
|
|||||||
databaseId={QueryCopilotSampleDatabaseId}
|
databaseId={QueryCopilotSampleDatabaseId}
|
||||||
containerId={QueryCopilotSampleContainerId}
|
containerId={QueryCopilotSampleContainerId}
|
||||||
></QueryCopilotPromptbar>
|
></QueryCopilotPromptbar>
|
||||||
)}
|
)} */}
|
||||||
<Stack className="tabPaneContentContainer">
|
<Stack className="tabPaneContentContainer">
|
||||||
<SplitterLayout percentage={true} vertical={true} primaryIndex={0} primaryMinSize={30} secondaryMinSize={70}>
|
<SplitterLayout percentage={true} vertical={true} primaryIndex={0} primaryMinSize={30} secondaryMinSize={70}>
|
||||||
<EditorReact
|
<EditorReact
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* Accordion top class
|
* Accordion top class
|
||||||
*/
|
*/
|
||||||
import { makeStyles, tokens } from "@fluentui/react-components";
|
import { Link, makeStyles, tokens } from "@fluentui/react-components";
|
||||||
import { DocumentAddRegular, LinkMultipleRegular } from "@fluentui/react-icons";
|
import { DocumentAddRegular, LinkMultipleRegular, OpenRegular } from "@fluentui/react-icons";
|
||||||
import { SampleDataImportDialog } from "Explorer/SplashScreen/SampleDataImportDialog";
|
import { SampleDataImportDialog } from "Explorer/SplashScreen/SampleDataImportDialog";
|
||||||
import { CosmosFluentProvider } from "Explorer/Theme/ThemeUtil";
|
import { CosmosFluentProvider } from "Explorer/Theme/ThemeUtil";
|
||||||
import { isFabricNative, isFabricNativeReadOnly } from "Platform/Fabric/FabricUtil";
|
import { isFabricNative, isFabricNativeReadOnly } from "Platform/Fabric/FabricUtil";
|
||||||
@@ -119,7 +119,7 @@ const FabricHomeScreenButton: React.FC<FabricHomeScreenButtonProps & { className
|
|||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
return (
|
return (
|
||||||
<div role="button" className={`${styles.buttonContainer} ${className}`} onClick={onClick}>
|
<div role="button" className={`${styles.buttonContainer} ${className}`} onClick={onClick} tabIndex={0}>
|
||||||
<div className={styles.buttonUpperPart}>{icon}</div>
|
<div className={styles.buttonUpperPart}>{icon}</div>
|
||||||
<div aria-label={title} className={styles.buttonLowerPart}>
|
<div aria-label={title} className={styles.buttonLowerPart}>
|
||||||
<div>{title}</div>
|
<div>{title}</div>
|
||||||
@@ -147,7 +147,7 @@ export const FabricHomeScreen: React.FC<SplashScreenProps> = (props: SplashScree
|
|||||||
{
|
{
|
||||||
title: "Sample data",
|
title: "Sample data",
|
||||||
description: "Automatically load sample data in your database",
|
description: "Automatically load sample data in your database",
|
||||||
icon: <img src={CosmosDbBlackIcon} />,
|
icon: <img src={CosmosDbBlackIcon} alt={"Azure Cosmos DB icon"} aria-hidden="true" />,
|
||||||
onClick: () => setOpenSampleDataImportDialog(true),
|
onClick: () => setOpenSampleDataImportDialog(true),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -181,16 +181,18 @@ export const FabricHomeScreen: React.FC<SplashScreenProps> = (props: SplashScree
|
|||||||
explorer={props.explorer}
|
explorer={props.explorer}
|
||||||
databaseName={userContext.fabricContext?.databaseName}
|
databaseName={userContext.fabricContext?.databaseName}
|
||||||
/>
|
/>
|
||||||
<div className={styles.title} role="heading" aria-label={title}>
|
<div className={styles.title} role="heading" aria-label={title} aria-level={1}>
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
{getSplashScreenButtons()}
|
{getSplashScreenButtons()}
|
||||||
{/* <div className={styles.footer}>
|
{
|
||||||
Need help?{" "}
|
<div className={styles.footer}>
|
||||||
<Link href="https://aka.ms/cosmosdbfabricdocs" target="_blank">
|
Need help?{" "}
|
||||||
Learn more <img src={LinkIcon} alt="Learn more" />
|
<Link href="https://learn.microsoft.com/fabric/database/cosmos-db/overview" target="_blank">
|
||||||
</Link>
|
Learn more <OpenRegular />
|
||||||
</div> */}
|
</Link>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</CosmosFluentProvider>
|
</CosmosFluentProvider>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { ReactTabKind, useTabs } from "hooks/useTabs";
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import ConnectIcon from "../../../images/Connect_color.svg";
|
import ConnectIcon from "../../../images/Connect_color.svg";
|
||||||
import ContainersIcon from "../../../images/Containers.svg";
|
import ContainersIcon from "../../../images/Containers.svg";
|
||||||
|
import CosmosDBIcon from "../../../images/CosmosDB-logo.svg";
|
||||||
import LinkIcon from "../../../images/Link_blue.svg";
|
import LinkIcon from "../../../images/Link_blue.svg";
|
||||||
import PowerShellIcon from "../../../images/PowerShell.svg";
|
import PowerShellIcon from "../../../images/PowerShell.svg";
|
||||||
import CopilotIcon from "../../../images/QueryCopilotNewLogo.svg";
|
import CopilotIcon from "../../../images/QueryCopilotNewLogo.svg";
|
||||||
@@ -120,11 +121,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private getSplashScreenButtons = (): JSX.Element => {
|
private getSplashScreenButtons = (): JSX.Element => {
|
||||||
if (
|
if (userContext.apiType === "SQL") {
|
||||||
userContext.apiType === "SQL" &&
|
|
||||||
useQueryCopilot.getState().copilotEnabled &&
|
|
||||||
useDatabases.getState().sampleDataResourceTokenCollection
|
|
||||||
) {
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
className="splashStackContainer"
|
className="splashStackContainer"
|
||||||
@@ -152,25 +149,18 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack className="splashStackRow" horizontal>
|
<Stack className="splashStackRow" horizontal>
|
||||||
{useQueryCopilot.getState().copilotEnabled && (
|
<SplashScreenButton
|
||||||
<SplashScreenButton
|
imgSrc={CosmosDBIcon}
|
||||||
imgSrc={CopilotIcon}
|
imgSize={35}
|
||||||
title={"Query faster with Query Advisor"}
|
title={"Azure Cosmos DB Samples Gallery"}
|
||||||
description={
|
description={
|
||||||
"Query Advisor is your AI buddy that helps you write Azure Cosmos DB queries like a pro. Try it using our sample data set now!"
|
"Discover samples that showcase scalable, intelligent app patterns. Try one now to see how fast you can go from concept to code with Cosmos DB"
|
||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const copilotVersion = userContext.features.copilotVersion;
|
window.open("https://azurecosmosdb.github.io/gallery/?tags=example", "_blank");
|
||||||
if (copilotVersion === "v1.0") {
|
traceOpen(Action.LearningResourcesClicked, { apiType: userContext.apiType });
|
||||||
useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot);
|
}}
|
||||||
} else if (copilotVersion === "v2.0") {
|
/>
|
||||||
const sampleCollection = useDatabases.getState().sampleDataResourceTokenCollection;
|
|
||||||
sampleCollection.onNewQueryClick(sampleCollection, undefined);
|
|
||||||
}
|
|
||||||
traceOpen(Action.OpenQueryCopilotFromSplashScreen, { apiType: userContext.apiType });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<SplashScreenButton
|
<SplashScreenButton
|
||||||
imgSrc={ConnectIcon}
|
imgSrc={ConnectIcon}
|
||||||
title={"Connect"}
|
title={"Connect"}
|
||||||
@@ -212,6 +202,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
sample data, query.
|
sample data, query.
|
||||||
</TeachingBubble>
|
</TeachingBubble>
|
||||||
)}
|
)}
|
||||||
|
{/*TODO: convert below to use SplashScreenButton */}
|
||||||
{mainItems.map((item) => (
|
{mainItems.map((item) => (
|
||||||
<Stack
|
<Stack
|
||||||
id={`mainButton-${item.id}`}
|
id={`mainButton-${item.id}`}
|
||||||
@@ -477,6 +468,34 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: Re-enable lint rule when query copilot is reinstated in DE
|
||||||
|
/* eslint-disable-next-line no-unused-vars */
|
||||||
|
private getQueryCopilotCard = (): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{useQueryCopilot.getState().copilotEnabled && (
|
||||||
|
<SplashScreenButton
|
||||||
|
imgSrc={CopilotIcon}
|
||||||
|
title={"Query faster with Query Advisor"}
|
||||||
|
description={
|
||||||
|
"Query Advisor is your AI buddy that helps you write Azure Cosmos DB queries like a pro. Try it using our sample data set now!"
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
const copilotVersion = userContext.features.copilotVersion;
|
||||||
|
if (copilotVersion === "v1.0") {
|
||||||
|
useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot);
|
||||||
|
} else if (copilotVersion === "v2.0") {
|
||||||
|
const sampleCollection = useDatabases.getState().sampleDataResourceTokenCollection;
|
||||||
|
sampleCollection.onNewQueryClick(sampleCollection, undefined);
|
||||||
|
}
|
||||||
|
traceOpen(Action.OpenQueryCopilotFromSplashScreen, { apiType: userContext.apiType });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
private decorateOpenCollectionActivity({ databaseId, collectionId }: MostRecentActivity.OpenCollectionItem) {
|
private decorateOpenCollectionActivity({ databaseId, collectionId }: MostRecentActivity.OpenCollectionItem) {
|
||||||
return {
|
return {
|
||||||
iconSrc: CollectionIcon,
|
iconSrc: CollectionIcon,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ interface SplashScreenButtonProps {
|
|||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
|
imgSize?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SplashScreenButton: React.FC<SplashScreenButtonProps> = ({
|
export const SplashScreenButton: React.FC<SplashScreenButtonProps> = ({
|
||||||
@@ -14,6 +15,7 @@ export const SplashScreenButton: React.FC<SplashScreenButtonProps> = ({
|
|||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
onClick,
|
onClick,
|
||||||
|
imgSize,
|
||||||
}: SplashScreenButtonProps): JSX.Element => {
|
}: SplashScreenButtonProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
@@ -39,7 +41,7 @@ export const SplashScreenButton: React.FC<SplashScreenButtonProps> = ({
|
|||||||
role="button"
|
role="button"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<img src={imgSrc} alt={title} aria-hidden="true" />
|
<img src={imgSrc} alt={title} aria-hidden="true" {...(imgSize ? { height: imgSize, width: imgSize } : {})} />
|
||||||
</div>
|
</div>
|
||||||
<Stack style={{ marginLeft: 16 }}>
|
<Stack style={{ marginLeft: 16 }}>
|
||||||
<Text style={{ fontSize: 18, fontWeight: 600 }}>{title}</Text>
|
<Text style={{ fontSize: 18, fontWeight: 600 }}>{title}</Text>
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ export const DISABLE_HISTORY = `set +o history`;
|
|||||||
* Used when shell initialization or connection fails.
|
* Used when shell initialization or connection fails.
|
||||||
*/
|
*/
|
||||||
export const EXIT_COMMAND = ` printf "\\033[1;31mSession ended. Please close this tab and initiate a new shell session if needed.\\033[0m\\n" && disown -a && exit`;
|
export const EXIT_COMMAND = ` printf "\\033[1;31mSession ended. Please close this tab and initiate a new shell session if needed.\\033[0m\\n" && disown -a && exit`;
|
||||||
|
/**
|
||||||
|
* Command that displays error message with MongoDB networking guidance and exits the shell session.
|
||||||
|
* Used when MongoDB shell connection fails due to networking issues.
|
||||||
|
*/
|
||||||
|
export const EXIT_COMMAND_MONGO = ` printf "\\033[1;31mSession ended. Please close this tab and initiate a new shell session if needed.\\033[0m\\n" && printf "\\033[1;36mPlease use the 'Add Azure Cloud Shell IPs' button in the Networking blade to allow Cloud Shell access, if not already configured.\\033[0m\\n" && disown -a && exit`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This command runs mongosh in no-database and quiet mode,
|
* This command runs mongosh in no-database and quiet mode,
|
||||||
@@ -40,6 +45,14 @@ export abstract class AbstractShellHandler {
|
|||||||
abstract getTerminalSuppressedData(): string[];
|
abstract getTerminalSuppressedData(): string[];
|
||||||
updateTerminalData?(data: string): string;
|
updateTerminalData?(data: string): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the exit command to use when connection fails.
|
||||||
|
* Can be overridden by subclasses to provide custom exit commands.
|
||||||
|
*/
|
||||||
|
protected getExitCommand(): string {
|
||||||
|
return EXIT_COMMAND;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs the complete initialization command sequence for the shell.
|
* Constructs the complete initialization command sequence for the shell.
|
||||||
*
|
*
|
||||||
@@ -64,7 +77,7 @@ export abstract class AbstractShellHandler {
|
|||||||
START_MARKER,
|
START_MARKER,
|
||||||
DISABLE_HISTORY,
|
DISABLE_HISTORY,
|
||||||
...setupCommands,
|
...setupCommands,
|
||||||
`{ ${connectionCommand}; } || true;${EXIT_COMMAND}`,
|
`{ ${connectionCommand}; } || true;${this.getExitCommand()}`,
|
||||||
];
|
];
|
||||||
|
|
||||||
return allCommands.join("\n").concat("\n");
|
return allCommands.join("\n").concat("\n");
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { userContext } from "../../../../UserContext";
|
import { userContext } from "../../../../UserContext";
|
||||||
import { filterAndCleanTerminalOutput, getHostFromUrl, getMongoShellRemoveInfoText } from "../Utils/CommonUtils";
|
import { filterAndCleanTerminalOutput, getHostFromUrl, getMongoShellRemoveInfoText } from "../Utils/CommonUtils";
|
||||||
import { AbstractShellHandler, DISABLE_TELEMETRY_COMMAND } from "./AbstractShellHandler";
|
import { AbstractShellHandler, DISABLE_TELEMETRY_COMMAND, EXIT_COMMAND_MONGO } from "./AbstractShellHandler";
|
||||||
|
|
||||||
export class MongoShellHandler extends AbstractShellHandler {
|
export class MongoShellHandler extends AbstractShellHandler {
|
||||||
private _key: string;
|
private _key: string;
|
||||||
@@ -48,6 +48,10 @@ export class MongoShellHandler extends AbstractShellHandler {
|
|||||||
return ["Warning: Non-Genuine MongoDB Detected", "Telemetry is now disabled."];
|
return ["Warning: Non-Genuine MongoDB Detected", "Telemetry is now disabled."];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected getExitCommand(): string {
|
||||||
|
return EXIT_COMMAND_MONGO;
|
||||||
|
}
|
||||||
|
|
||||||
updateTerminalData(data: string): string {
|
updateTerminalData(data: string): string {
|
||||||
return filterAndCleanTerminalOutput(data, this._removeInfoText);
|
return filterAndCleanTerminalOutput(data, this._removeInfoText);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { userContext } from "../../../../UserContext";
|
import { userContext } from "../../../../UserContext";
|
||||||
import { filterAndCleanTerminalOutput, getMongoShellRemoveInfoText } from "../Utils/CommonUtils";
|
import { filterAndCleanTerminalOutput, getMongoShellRemoveInfoText } from "../Utils/CommonUtils";
|
||||||
import { AbstractShellHandler, DISABLE_TELEMETRY_COMMAND } from "./AbstractShellHandler";
|
import { AbstractShellHandler, DISABLE_TELEMETRY_COMMAND, EXIT_COMMAND_MONGO } from "./AbstractShellHandler";
|
||||||
|
|
||||||
export class VCoreMongoShellHandler extends AbstractShellHandler {
|
export class VCoreMongoShellHandler extends AbstractShellHandler {
|
||||||
private _endpoint: string | undefined;
|
private _endpoint: string | undefined;
|
||||||
@@ -35,6 +35,13 @@ export class VCoreMongoShellHandler extends AbstractShellHandler {
|
|||||||
return ["Warning: Non-Genuine MongoDB Detected", "Telemetry is now disabled."];
|
return ["Warning: Non-Genuine MongoDB Detected", "Telemetry is now disabled."];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override getExitCommand to include MongoDB networking guidance
|
||||||
|
*/
|
||||||
|
protected getExitCommand(): string {
|
||||||
|
return EXIT_COMMAND_MONGO;
|
||||||
|
}
|
||||||
|
|
||||||
updateTerminalData(data: string): string {
|
updateTerminalData(data: string): string {
|
||||||
return filterAndCleanTerminalOutput(data, this._removeInfoText);
|
return filterAndCleanTerminalOutput(data, this._removeInfoText);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
import { userContext } from "../../../../UserContext";
|
|
||||||
|
|
||||||
export const CLOUDSHELL_IP_RECOMMENDATIONS = {
|
|
||||||
centralindia: [
|
|
||||||
{ startIP: "4.247.135.109", endIP: "4.247.135.109" },
|
|
||||||
{ startIP: "74.225.207.63", endIP: "74.225.207.63" },
|
|
||||||
],
|
|
||||||
southeastasia: [{ startIP: "4.194.5.74", endIP: "4.194.213.10" }],
|
|
||||||
centraluseuap: [
|
|
||||||
{ startIP: "52.158.186.182", endIP: "52.158.186.182" },
|
|
||||||
{ startIP: "172.215.26.246", endIP: "172.215.26.246" },
|
|
||||||
{ startIP: "134.138.154.177", endIP: "134.138.154.177" },
|
|
||||||
{ startIP: "134.138.129.52", endIP: "134.138.129.52" },
|
|
||||||
{ startIP: "172.215.31.177", endIP: "172.215.31.177" },
|
|
||||||
],
|
|
||||||
eastus2euap: [
|
|
||||||
{ startIP: "135.18.43.51", endIP: "135.18.43.51" },
|
|
||||||
{ startIP: "20.252.175.33", endIP: "20.252.175.33" },
|
|
||||||
{ startIP: "40.89.88.111", endIP: "40.89.88.111" },
|
|
||||||
{ startIP: "135.18.17.187", endIP: "135.18.17.187" },
|
|
||||||
{ startIP: "135.18.67.251", endIP: "135.18.67.251" },
|
|
||||||
],
|
|
||||||
eastus: [
|
|
||||||
{ startIP: "40.71.199.151", endIP: "40.71.199.151" },
|
|
||||||
{ startIP: "20.42.18.188", endIP: "20.42.18.188" },
|
|
||||||
{ startIP: "52.190.17.9", endIP: "52.190.17.9" },
|
|
||||||
{ startIP: "20.120.96.152", endIP: "20.120.96.152" },
|
|
||||||
],
|
|
||||||
northeurope: [
|
|
||||||
{ startIP: "74.234.65.146", endIP: "74.234.65.146" },
|
|
||||||
{ startIP: "52.169.70.113", endIP: "52.169.70.113" },
|
|
||||||
],
|
|
||||||
southcentralus: [
|
|
||||||
{ startIP: "4.151.247.81", endIP: "4.151.247.81" },
|
|
||||||
{ startIP: "20.225.211.35", endIP: "20.225.211.35" },
|
|
||||||
{ startIP: "4.151.48.133", endIP: "4.151.48.133" },
|
|
||||||
{ startIP: "4.151.247.225", endIP: "4.151.247.225" },
|
|
||||||
],
|
|
||||||
westeurope: [
|
|
||||||
{ startIP: "52.166.126.216", endIP: "52.166.126.216" },
|
|
||||||
{ startIP: "108.142.162.20", endIP: "108.142.162.20" },
|
|
||||||
{ startIP: "52.178.13.125", endIP: "52.178.13.125" },
|
|
||||||
{ startIP: "172.201.33.160", endIP: "172.201.33.160" },
|
|
||||||
],
|
|
||||||
westus: [
|
|
||||||
{ startIP: "20.245.161.131", endIP: "20.245.161.131" },
|
|
||||||
{ startIP: "57.154.182.51", endIP: "57.154.182.51" },
|
|
||||||
{ startIP: "40.118.133.244", endIP: "40.118.133.244" },
|
|
||||||
{ startIP: "20.253.192.12", endIP: "20.253.192.12" },
|
|
||||||
{ startIP: "20.43.245.209", endIP: "20.43.245.209" },
|
|
||||||
{ startIP: "20.66.22.66", endIP: "20.66.22.66" },
|
|
||||||
],
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export interface CloudShellIPRange {
|
|
||||||
startIP: string;
|
|
||||||
endIP: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCloudShellIPsForRegion(region: string): readonly CloudShellIPRange[] {
|
|
||||||
const normalizedRegion = region.toLowerCase();
|
|
||||||
return CLOUDSHELL_IP_RECOMMENDATIONS[normalizedRegion as keyof typeof CLOUDSHELL_IP_RECOMMENDATIONS] || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getClusterRegion(): string {
|
|
||||||
const location = userContext?.databaseAccount?.location;
|
|
||||||
if (location) {
|
|
||||||
return location.toLowerCase();
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,6 @@ const validCloudShellRegions = new Set([
|
|||||||
"westeurope",
|
"westeurope",
|
||||||
"centralindia",
|
"centralindia",
|
||||||
"southeastasia",
|
"southeastasia",
|
||||||
"westcentralus",
|
|
||||||
"usgovvirginia",
|
"usgovvirginia",
|
||||||
"usgovarizona",
|
"usgovarizona",
|
||||||
]);
|
]);
|
||||||
@@ -41,7 +40,6 @@ export const getNormalizedRegion = (region: string, defaultCloudshellRegion: str
|
|||||||
}
|
}
|
||||||
|
|
||||||
const regionMap: Record<string, string> = {
|
const regionMap: Record<string, string> = {
|
||||||
centralus: "westcentralus",
|
|
||||||
eastus2: "eastus",
|
eastus2: "eastus",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -106,6 +106,6 @@ describe("QueryTabComponent", () => {
|
|||||||
<QueryTabCopilotComponent {...propsMock} />
|
<QueryTabCopilotComponent {...propsMock} />
|
||||||
</CopilotProvider>,
|
</CopilotProvider>,
|
||||||
);
|
);
|
||||||
expect(container.find(QueryCopilotPromptbar).exists()).toBe(true);
|
expect(container.find(QueryCopilotPromptbar).exists()).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { useDialog } from "Explorer/Controls/Dialog";
|
|||||||
import { monaco } from "Explorer/LazyMonaco";
|
import { monaco } from "Explorer/LazyMonaco";
|
||||||
import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal";
|
import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal";
|
||||||
import { useCopilotStore } from "Explorer/QueryCopilot/QueryCopilotContext";
|
import { useCopilotStore } from "Explorer/QueryCopilot/QueryCopilotContext";
|
||||||
import { QueryCopilotPromptbar } from "Explorer/QueryCopilot/QueryCopilotPromptbar";
|
|
||||||
import { readCopilotToggleStatus, saveCopilotToggleStatus } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
import { readCopilotToggleStatus, saveCopilotToggleStatus } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||||
import { OnExecuteQueryClick, QueryDocumentsPerPage } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
import { OnExecuteQueryClick, QueryDocumentsPerPage } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||||
import { QueryCopilotSidebar } from "Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar";
|
import { QueryCopilotSidebar } from "Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar";
|
||||||
@@ -28,8 +27,9 @@ import { TabsState, useTabs } from "hooks/useTabs";
|
|||||||
import React, { Fragment, createRef } from "react";
|
import React, { Fragment, createRef } from "react";
|
||||||
import "react-splitter-layout/lib/index.css";
|
import "react-splitter-layout/lib/index.css";
|
||||||
import { format } from "react-string-format";
|
import { format } from "react-string-format";
|
||||||
import QueryCommandIcon from "../../../../images/CopilotCommand.svg";
|
//TODO: Uncomment next two lines when query copilot is reinstated in DE
|
||||||
import LaunchCopilot from "../../../../images/CopilotTabIcon.svg";
|
// import QueryCommandIcon from "../../../../images/CopilotCommand.svg";
|
||||||
|
// import LaunchCopilot from "../../../../images/CopilotTabIcon.svg";
|
||||||
import DownloadQueryIcon from "../../../../images/DownloadQuery.svg";
|
import DownloadQueryIcon from "../../../../images/DownloadQuery.svg";
|
||||||
import CancelQueryIcon from "../../../../images/Entity_cancel.svg";
|
import CancelQueryIcon from "../../../../images/Entity_cancel.svg";
|
||||||
import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg";
|
import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg";
|
||||||
@@ -494,53 +494,55 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.launchCopilotButton.visible && this.isCopilotTabActive) {
|
//TODO: Uncomment next section when query copilot is reinstated in DE
|
||||||
const mainButtonLabel = "Launch Copilot";
|
// if (this.launchCopilotButton.visible && this.isCopilotTabActive) {
|
||||||
const chatPaneLabel = "Open Copilot in chat pane (ALT+C)";
|
// const mainButtonLabel = "Launch Copilot";
|
||||||
const copilotSettingLabel = "Copilot settings";
|
// const chatPaneLabel = "Open Copilot in chat pane (ALT+C)";
|
||||||
|
// const copilotSettingLabel = "Copilot settings";
|
||||||
|
|
||||||
const openCopilotChatButton: CommandButtonComponentProps = {
|
// const openCopilotChatButton: CommandButtonComponentProps = {
|
||||||
iconAlt: chatPaneLabel,
|
// iconAlt: chatPaneLabel,
|
||||||
onCommandClick: this.launchQueryCopilotChat,
|
// onCommandClick: this.launchQueryCopilotChat,
|
||||||
commandButtonLabel: chatPaneLabel,
|
// commandButtonLabel: chatPaneLabel,
|
||||||
ariaLabel: chatPaneLabel,
|
// ariaLabel: chatPaneLabel,
|
||||||
hasPopup: false,
|
// hasPopup: false,
|
||||||
};
|
// };
|
||||||
|
|
||||||
const copilotSettingsButton: CommandButtonComponentProps = {
|
// const copilotSettingsButton: CommandButtonComponentProps = {
|
||||||
iconAlt: copilotSettingLabel,
|
// iconAlt: copilotSettingLabel,
|
||||||
onCommandClick: () => undefined,
|
// onCommandClick: () => undefined,
|
||||||
commandButtonLabel: copilotSettingLabel,
|
// commandButtonLabel: copilotSettingLabel,
|
||||||
ariaLabel: copilotSettingLabel,
|
// ariaLabel: copilotSettingLabel,
|
||||||
hasPopup: false,
|
// hasPopup: false,
|
||||||
};
|
// };
|
||||||
|
|
||||||
const launchCopilotButton: CommandButtonComponentProps = {
|
// const launchCopilotButton: CommandButtonComponentProps = {
|
||||||
iconSrc: LaunchCopilot,
|
// iconSrc: LaunchCopilot,
|
||||||
iconAlt: mainButtonLabel,
|
// iconAlt: mainButtonLabel,
|
||||||
onCommandClick: this.launchQueryCopilotChat,
|
// onCommandClick: this.launchQueryCopilotChat,
|
||||||
commandButtonLabel: mainButtonLabel,
|
// commandButtonLabel: mainButtonLabel,
|
||||||
ariaLabel: mainButtonLabel,
|
// ariaLabel: mainButtonLabel,
|
||||||
hasPopup: false,
|
// hasPopup: false,
|
||||||
children: [openCopilotChatButton, copilotSettingsButton],
|
// children: [openCopilotChatButton, copilotSettingsButton],
|
||||||
};
|
// };
|
||||||
buttons.push(launchCopilotButton);
|
// buttons.push(launchCopilotButton);
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (this.props.copilotEnabled) {
|
//TODO: Uncomment next section when query copilot is reinstated in DE
|
||||||
const toggleCopilotButton: CommandButtonComponentProps = {
|
// if (this.props.copilotEnabled) {
|
||||||
iconSrc: QueryCommandIcon,
|
// const toggleCopilotButton: CommandButtonComponentProps = {
|
||||||
iconAlt: "Query Advisor",
|
// iconSrc: QueryCommandIcon,
|
||||||
keyboardAction: KeyboardAction.TOGGLE_COPILOT,
|
// iconAlt: "Query Advisor",
|
||||||
onCommandClick: () => {
|
// keyboardAction: KeyboardAction.TOGGLE_COPILOT,
|
||||||
this._toggleCopilot(!this.state.copilotActive);
|
// onCommandClick: () => {
|
||||||
},
|
// this._toggleCopilot(!this.state.copilotActive);
|
||||||
commandButtonLabel: this.state.copilotActive ? "Disable Query Advisor" : "Enable Query Advisor",
|
// },
|
||||||
ariaLabel: this.state.copilotActive ? "Disable Query Advisor" : "Enable Query Advisor",
|
// commandButtonLabel: this.state.copilotActive ? "Disable Query Advisor" : "Enable Query Advisor",
|
||||||
hasPopup: false,
|
// ariaLabel: this.state.copilotActive ? "Disable Query Advisor" : "Enable Query Advisor",
|
||||||
};
|
// hasPopup: false,
|
||||||
buttons.push(toggleCopilotButton);
|
// };
|
||||||
}
|
// buttons.push(toggleCopilotButton);
|
||||||
|
// }
|
||||||
|
|
||||||
if (!this.props.isPreferredApiMongoDB && this.state.isExecuting) {
|
if (!this.props.isPreferredApiMongoDB && this.state.isExecuting) {
|
||||||
const label = "Cancel query";
|
const label = "Cancel query";
|
||||||
@@ -725,6 +727,7 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<CosmosFluentProvider id={this.props.tabId} className={this.props.styles.queryTab} role="tabpanel">
|
<CosmosFluentProvider id={this.props.tabId} className={this.props.styles.queryTab} role="tabpanel">
|
||||||
|
{/*TODO: Uncomment this section when query copilot is reinstated in DE
|
||||||
{this.props.copilotEnabled && this.state.currentTabActive && this.state.copilotActive && (
|
{this.props.copilotEnabled && this.state.currentTabActive && this.state.copilotActive && (
|
||||||
<QueryCopilotPromptbar
|
<QueryCopilotPromptbar
|
||||||
explorer={this.props.collection.container}
|
explorer={this.props.collection.container}
|
||||||
@@ -732,7 +735,7 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
databaseId={this.props.collection.databaseId}
|
databaseId={this.props.collection.databaseId}
|
||||||
containerId={this.props.collection.id()}
|
containerId={this.props.collection.id()}
|
||||||
></QueryCopilotPromptbar>
|
></QueryCopilotPromptbar>
|
||||||
)}
|
)} */}
|
||||||
{/* Set 'key' to the value of vertical to force re-rendering when vertical changes, to work around https://github.com/johnwalley/allotment/issues/457 */}
|
{/* Set 'key' to the value of vertical to force re-rendering when vertical changes, to work around https://github.com/johnwalley/allotment/issues/457 */}
|
||||||
<Allotment
|
<Allotment
|
||||||
key={vertical.toString()}
|
key={vertical.toString()}
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
import { configContext } from "ConfigContext";
|
|
||||||
import * as DataModels from "Contracts/DataModels";
|
|
||||||
import { userContext } from "UserContext";
|
|
||||||
import { armRequest } from "Utils/arm/request";
|
|
||||||
import {
|
|
||||||
CloudShellIPRange,
|
|
||||||
getCloudShellIPsForRegion,
|
|
||||||
getClusterRegion,
|
|
||||||
} from "../CloudShellTab/Utils/CloudShellIPUtils";
|
|
||||||
import { getNormalizedRegion } from "../CloudShellTab/Utils/RegionUtils";
|
|
||||||
|
|
||||||
// Constants
|
|
||||||
const DEFAULT_CLOUDSHELL_REGION = "westus";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if user has added all CloudShell IPs for their normalized region
|
|
||||||
* @param apiVersion - The API version to use for the ARM request
|
|
||||||
* @returns Promise<boolean> - true if all CloudShell IPs are configured (don't show screenshot), false if missing (show screenshot)
|
|
||||||
*/
|
|
||||||
export async function checkCloudShellIPsConfigured(apiVersion: string): Promise<boolean> {
|
|
||||||
const clusterRegion = getClusterRegion();
|
|
||||||
|
|
||||||
if (!clusterRegion) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalizedRegion = getNormalizedRegion(clusterRegion, DEFAULT_CLOUDSHELL_REGION);
|
|
||||||
const cloudShellIPs = getCloudShellIPsForRegion(normalizedRegion);
|
|
||||||
|
|
||||||
if (cloudShellIPs.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const firewallRulesUri = `${userContext.databaseAccount.id}/firewallRules`;
|
|
||||||
const response: any = await armRequest({
|
|
||||||
host: configContext.ARM_ENDPOINT,
|
|
||||||
path: firewallRulesUri,
|
|
||||||
method: "GET",
|
|
||||||
apiVersion: apiVersion,
|
|
||||||
});
|
|
||||||
|
|
||||||
const firewallRules: DataModels.FirewallRule[] = response?.data?.value || response?.value || [];
|
|
||||||
|
|
||||||
const missingIPs: Array<{ startIP: string; endIP: string; reason?: string }> = [];
|
|
||||||
const foundIPs: Array<{ startIP: string; endIP: string; ruleName?: string }> = [];
|
|
||||||
|
|
||||||
for (const cloudShellIP of cloudShellIPs) {
|
|
||||||
const matchingRule = firewallRules.find((rule) => {
|
|
||||||
const startMatch = rule.properties.startIpAddress === cloudShellIP.startIP;
|
|
||||||
const endMatch = rule.properties.endIpAddress === cloudShellIP.endIP;
|
|
||||||
return startMatch && endMatch;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (matchingRule) {
|
|
||||||
foundIPs.push({ ...cloudShellIP, ruleName: matchingRule.name });
|
|
||||||
} else {
|
|
||||||
missingIPs.push({ ...cloudShellIP, reason: "No exact IP match in firewall rules" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const allConfigured = missingIPs.length === 0;
|
|
||||||
return allConfigured;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the normalized region and its CloudShell IPs for display in the guide
|
|
||||||
* @returns Object with region and IPs for the guide
|
|
||||||
*/
|
|
||||||
export function getCloudShellGuideInfo(): { region: string; cloudShellIPs: readonly CloudShellIPRange[] } {
|
|
||||||
const clusterRegion = getClusterRegion();
|
|
||||||
const normalizedRegion = getNormalizedRegion(clusterRegion || "", DEFAULT_CLOUDSHELL_REGION);
|
|
||||||
const cloudShellIPs = getCloudShellIPsForRegion(normalizedRegion);
|
|
||||||
|
|
||||||
return {
|
|
||||||
region: normalizedRegion,
|
|
||||||
cloudShellIPs: cloudShellIPs,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ import { QuickstartFirewallNotification } from "Explorer/Quickstart/QuickstartFi
|
|||||||
import { getShellNameForDisplay } from "Explorer/Tabs/CloudShellTab/Utils/CommonUtils";
|
import { getShellNameForDisplay } from "Explorer/Tabs/CloudShellTab/Utils/CommonUtils";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import FirewallRuleScreenshot from "../../../../images/firewallRule.png";
|
import FirewallRuleScreenshot from "../../../../images/firewallRule.png";
|
||||||
import VcoreFirewallRuleScreenshot from "../../../../images/vcoreMongoFirewallRule.png";
|
|
||||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
@@ -22,30 +21,18 @@ export abstract class BaseTerminalComponentAdapter implements ReactAdapter {
|
|||||||
protected getUsername: () => string,
|
protected getUsername: () => string,
|
||||||
protected isAllPublicIPAddressesEnabled: ko.Observable<boolean>,
|
protected isAllPublicIPAddressesEnabled: ko.Observable<boolean>,
|
||||||
protected kind: ViewModels.TerminalKind,
|
protected kind: ViewModels.TerminalKind,
|
||||||
protected isCloudShellIPsConfigured?: ko.Observable<boolean>,
|
) {}
|
||||||
) { }
|
|
||||||
|
|
||||||
public renderComponent(): JSX.Element {
|
public renderComponent(): JSX.Element {
|
||||||
const publicIPEnabled = this.isAllPublicIPAddressesEnabled();
|
if (this.kind === ViewModels.TerminalKind.Mongo || this.kind === ViewModels.TerminalKind.VCoreMongo) {
|
||||||
const cloudShellConfigured = this.isCloudShellIPsConfigured ? this.isCloudShellIPsConfigured() : true;
|
return this.renderTerminalComponent();
|
||||||
let shouldShowScreenshot: boolean;
|
|
||||||
|
|
||||||
if (this.isCloudShellIPsConfigured) {
|
|
||||||
shouldShowScreenshot = !cloudShellConfigured;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
shouldShowScreenshot = !publicIPEnabled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldShowScreenshot) {
|
if (!this.isAllPublicIPAddressesEnabled()) {
|
||||||
return (
|
return (
|
||||||
<QuickstartFirewallNotification
|
<QuickstartFirewallNotification
|
||||||
messageType={this.getMessageType()}
|
messageType={this.getMessageType()}
|
||||||
screenshot={
|
screenshot={FirewallRuleScreenshot}
|
||||||
this.kind === ViewModels.TerminalKind.Mongo || this.kind === ViewModels.TerminalKind.VCoreMongo
|
|
||||||
? VcoreFirewallRuleScreenshot
|
|
||||||
: FirewallRuleScreenshot
|
|
||||||
}
|
|
||||||
shellName={getShellNameForDisplay(this.kind)}
|
shellName={getShellNameForDisplay(this.kind)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { checkFirewallRules } from "Explorer/Tabs/Shared/CheckFirewallRules";
|
import { checkFirewallRules } from "Explorer/Tabs/Shared/CheckFirewallRules";
|
||||||
import { checkCloudShellIPsConfigured } from "Explorer/Tabs/Shared/CloudShellIPChecker";
|
|
||||||
import { CloudShellTerminalComponentAdapter } from "Explorer/Tabs/ShellAdapters/CloudShellTerminalComponentAdapter";
|
import { CloudShellTerminalComponentAdapter } from "Explorer/Tabs/ShellAdapters/CloudShellTerminalComponentAdapter";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
||||||
@@ -24,13 +23,11 @@ export default class TerminalTab extends TabsBase {
|
|||||||
private container: Explorer;
|
private container: Explorer;
|
||||||
private notebookTerminalComponentAdapter: ReactAdapter;
|
private notebookTerminalComponentAdapter: ReactAdapter;
|
||||||
private isAllPublicIPAddressesEnabled: ko.Observable<boolean>;
|
private isAllPublicIPAddressesEnabled: ko.Observable<boolean>;
|
||||||
private isCloudShellIPsConfigured: ko.Observable<boolean>;
|
|
||||||
|
|
||||||
constructor(options: TerminalTabOptions) {
|
constructor(options: TerminalTabOptions) {
|
||||||
super(options);
|
super(options);
|
||||||
this.container = options.container;
|
this.container = options.container;
|
||||||
this.isAllPublicIPAddressesEnabled = ko.observable(true);
|
this.isAllPublicIPAddressesEnabled = ko.observable(true);
|
||||||
this.isCloudShellIPsConfigured = ko.observable(true); // Start optimistic, will be updated
|
|
||||||
|
|
||||||
const commonArgs: [
|
const commonArgs: [
|
||||||
() => DataModels.DatabaseAccount,
|
() => DataModels.DatabaseAccount,
|
||||||
@@ -39,33 +36,18 @@ export default class TerminalTab extends TabsBase {
|
|||||||
ko.Observable<boolean>,
|
ko.Observable<boolean>,
|
||||||
ViewModels.TerminalKind,
|
ViewModels.TerminalKind,
|
||||||
] = [
|
] = [
|
||||||
() => userContext?.databaseAccount,
|
() => userContext?.databaseAccount,
|
||||||
() => this.tabId,
|
() => this.tabId,
|
||||||
() => this.getUsername(),
|
() => this.getUsername(),
|
||||||
this.isAllPublicIPAddressesEnabled,
|
this.isAllPublicIPAddressesEnabled,
|
||||||
options.kind,
|
options.kind,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (userContext.features.enableCloudShell) {
|
if (userContext.features.enableCloudShell) {
|
||||||
this.notebookTerminalComponentAdapter = new CloudShellTerminalComponentAdapter(
|
this.notebookTerminalComponentAdapter = new CloudShellTerminalComponentAdapter(...commonArgs);
|
||||||
() => userContext?.databaseAccount,
|
|
||||||
() => this.tabId,
|
|
||||||
() => this.getUsername(),
|
|
||||||
this.isAllPublicIPAddressesEnabled,
|
|
||||||
options.kind,
|
|
||||||
this.isCloudShellIPsConfigured,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
|
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
|
||||||
const cloudShellConfigured = this.isCloudShellIPsConfigured();
|
return this.isTemplateReady() && this.isAllPublicIPAddressesEnabled();
|
||||||
return this.isTemplateReady() && cloudShellConfigured;
|
|
||||||
});
|
|
||||||
|
|
||||||
checkCloudShellIPsConfigured("2023-03-01-preview").then(result => {
|
|
||||||
this.isCloudShellIPsConfigured(result);
|
|
||||||
}).catch(error => {
|
|
||||||
console.error(`CloudShell IP Check failed for ${ViewModels.TerminalKind[options.kind]} terminal:`, error);
|
|
||||||
this.isCloudShellIPsConfigured(false);
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.notebookTerminalComponentAdapter = new NotebookTerminalComponentAdapter(
|
this.notebookTerminalComponentAdapter = new NotebookTerminalComponentAdapter(
|
||||||
@@ -83,25 +65,22 @@ export default class TerminalTab extends TabsBase {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only run legacy firewall checks for NON-CloudShell terminals cloudShell terminals use the CloudShell IP checker instead
|
if (options.kind === ViewModels.TerminalKind.Postgres) {
|
||||||
if (!userContext.features.enableCloudShell) {
|
checkFirewallRules(
|
||||||
if (options.kind === ViewModels.TerminalKind.Postgres) {
|
"2022-11-08",
|
||||||
checkFirewallRules(
|
(rule) => rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255",
|
||||||
"2022-11-08",
|
this.isAllPublicIPAddressesEnabled,
|
||||||
(rule) => rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255",
|
);
|
||||||
this.isAllPublicIPAddressesEnabled,
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.kind === ViewModels.TerminalKind.VCoreMongo) {
|
if (options.kind === ViewModels.TerminalKind.VCoreMongo) {
|
||||||
checkFirewallRules(
|
checkFirewallRules(
|
||||||
"2023-03-01-preview",
|
"2023-03-01-preview",
|
||||||
(rule) =>
|
(rule) =>
|
||||||
rule.name.startsWith("AllowAllAzureServicesAndResourcesWithinAzureIps") ||
|
rule.name.startsWith("AllowAllAzureServicesAndResourcesWithinAzureIps") ||
|
||||||
(rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255"),
|
(rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255"),
|
||||||
this.isAllPublicIPAddressesEnabled,
|
this.isAllPublicIPAddressesEnabled,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { MongoGuidRepresentation } from "Common/Constants";
|
||||||
import { SplitterDirection } from "Common/Splitter";
|
import { SplitterDirection } from "Common/Splitter";
|
||||||
import * as LocalStorageUtility from "./LocalStorageUtility";
|
import * as LocalStorageUtility from "./LocalStorageUtility";
|
||||||
import * as SessionStorageUtility from "./SessionStorageUtility";
|
import * as SessionStorageUtility from "./SessionStorageUtility";
|
||||||
@@ -33,6 +34,7 @@ export enum StorageKey {
|
|||||||
DocumentsTabPrefs,
|
DocumentsTabPrefs,
|
||||||
DefaultQueryResultsView,
|
DefaultQueryResultsView,
|
||||||
AppState,
|
AppState,
|
||||||
|
MongoGuidRepresentation,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const hasRUThresholdBeenConfigured = (): boolean => {
|
export const hasRUThresholdBeenConfigured = (): boolean => {
|
||||||
@@ -65,4 +67,13 @@ export const getDefaultQueryResultsView = (): SplitterDirection => {
|
|||||||
return SplitterDirection.Horizontal;
|
return SplitterDirection.Horizontal;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getMongoGuidRepresentation = (): MongoGuidRepresentation => {
|
||||||
|
const mongoGuidRepresentation: string | null = LocalStorageUtility.getEntryString(StorageKey.MongoGuidRepresentation);
|
||||||
|
if (mongoGuidRepresentation) {
|
||||||
|
return mongoGuidRepresentation as MongoGuidRepresentation;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MongoGuidRepresentation.CSharpLegacy;
|
||||||
|
};
|
||||||
|
|
||||||
export const DefaultRUThreshold = 5000;
|
export const DefaultRUThreshold = 5000;
|
||||||
|
|||||||
55
src/Utils/VSCodeExtensionUtils.ts
Normal file
55
src/Utils/VSCodeExtensionUtils.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { useTabs } from "hooks/useTabs";
|
||||||
|
import { userContext } from "UserContext";
|
||||||
|
import { getHostFromUrl } from "./../Explorer/Tabs/CloudShellTab/Utils/CommonUtils";
|
||||||
|
|
||||||
|
export const DOCUMENTDB_VSCODE_EXTENSION_BASEURL = "vscode://ms-azuretools.vscode-documentdb";
|
||||||
|
export const COSMOSDB_VSCODE_EXTENSION_BASEURL = "vscode://ms-azuretools.vscode-cosmosdb";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a VS Code DocumentDB connection URL using the current user's MongoDB connection parameters.
|
||||||
|
* Double-encodes the updated connection string for safe usage in VS Code URLs.
|
||||||
|
*
|
||||||
|
* The DocumentDB VS Code extension requires double encoding for connection strings.
|
||||||
|
* See: https://microsoft.github.io/vscode-documentdb/manual/how-to-construct-url.html#double-encoding
|
||||||
|
*
|
||||||
|
* @returns {string} The encoded VS Code DocumentDB connection URL.
|
||||||
|
*/
|
||||||
|
export const getVSCodeUrl = (): string => {
|
||||||
|
const isvCore = (userContext.apiType || userContext.databaseAccount.kind) === "VCoreMongo";
|
||||||
|
const isMongo =
|
||||||
|
userContext.apiType === "Mongo" && userContext.databaseAccount?.properties?.apiProperties?.serverVersion !== "3.2";
|
||||||
|
return isvCore ? getDocumentDbUrl() : isMongo ? getMongoRUUrl() : getCosmosDbUrl();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCosmosDbUrl = () => {
|
||||||
|
const activeTab = useTabs.getState().activeTab;
|
||||||
|
const resourceId = encodeURIComponent(userContext.databaseAccount.id);
|
||||||
|
const database = encodeURIComponent(activeTab?.collection?.databaseId);
|
||||||
|
const container = encodeURIComponent(activeTab?.collection?.id());
|
||||||
|
const baseUrl = `${COSMOSDB_VSCODE_EXTENSION_BASEURL}?resourceId=${resourceId}`;
|
||||||
|
const vscodeUrl = activeTab ? `${baseUrl}&database=${database}&container=${container}` : baseUrl;
|
||||||
|
return vscodeUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getMongoRUUrl = () => {
|
||||||
|
const activeTab = useTabs.getState().activeTab;
|
||||||
|
const databaseAccount = userContext.databaseAccount;
|
||||||
|
const host = getHostFromUrl(databaseAccount.properties?.mongoEndpoint);
|
||||||
|
const port = 10255;
|
||||||
|
const database = activeTab?.collection?.databaseId;
|
||||||
|
const container = activeTab?.collection?.id();
|
||||||
|
const encodedUpdatedConnectionString = encodeURIComponent(`mongodb://${databaseAccount?.name}@${host}:${port}`);
|
||||||
|
const documentDbUrl = `${DOCUMENTDB_VSCODE_EXTENSION_BASEURL}?connectionString=${encodedUpdatedConnectionString}${
|
||||||
|
database ? `&database=${database}` : ""
|
||||||
|
}${container ? `&collection=${container}` : ""}`;
|
||||||
|
|
||||||
|
return documentDbUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDocumentDbUrl = () => {
|
||||||
|
const { adminLogin: adminLoginuserName = "", connectionString = "" } = userContext.vcoreMongoConnectionParams;
|
||||||
|
const updatedConnectionString = connectionString.replace(/<(user|username)>:<password>/i, adminLoginuserName);
|
||||||
|
const encodedUpdatedConnectionString = encodeURIComponent(encodeURIComponent(updatedConnectionString));
|
||||||
|
const documentDbUrl = `${DOCUMENTDB_VSCODE_EXTENSION_BASEURL}?connectionString=${encodedUpdatedConnectionString}`;
|
||||||
|
return documentDbUrl;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user